【问题标题】:forbid access to public List's methods [duplicate]禁止访问公共列表的方法[重复]
【发布时间】:2017-10-13 08:46:46
【问题描述】:

这是一个极其简化的问题描述。鉴于我有这个:

interface IMyClass {
    IList<int> MyList { get; set; }
    void AddToList(int newVal);
}

public class MyClass: IMyClass {
    public IList<int> MyList { get; set; }
    public void AddToList(int newVal)
    {
        // custom implementation goes here
        MyList.Add(newVal);
    }
}

我得到了一个接口IMyClass,我无法更改。任务是实现这个接口以及addToList 方法。

问题是,即使我实现了addToList 方法——用户仍然有可能通过MyList 属性的直接访问来添加项目!

有没有办法禁止用户直接在列表中使用Add()Remove()Insert()

到目前为止,我已经尝试实现一个自定义的MyList&lt;T&gt; 类,但问题是我必须实现所有可能的IList&lt;T&gt; 方法,但我只需要其中一些方法的自定义实现,而不是全部..或者有其他方法吗?

【问题讨论】:

  • 您应该将 MyList 属性设为私有。
  • 根据您的列表授予对只读列表的访问权限并将您的列表设为私有
  • Ummm... 我要采取相反的观点并说:你根本不应该这样做。您将其视为,“嘿,这打破了封装,让任何人从任何地方设置我的列表。”但是...这就是接口指示的内容。如果我正在编程并使用实现该接口的东西,我会期望我可以一次添加一个项目,或者直接设置完整列表。如果你做你想做的事,你基本上是在违反合同——你让它的行为与界面所指示的不同。
  • 稍微扩展前面的评论:假设您有 List。您应该能够遍历该列表,在每个 item.MyList 上调用 Set。它可以工作......除非列表中的一项是您对接口的特定实现(阻止列表设置功能)。我越想越觉得:接口可能有问题,但如果你不能改变它,如果你不得不用你的类,你应该根据它的规范来实现它。

标签: c# oop


【解决方案1】:

既然您说您不能更改界面,您将不得不忍受该属性被公开为IList&lt;int&gt;。即使您显式实现接口(以隐藏公共属性),也不会阻止用户将实例转换为接口类型以访问公共IList&lt;int&gt; 属性。

但是由于ReadOnlyCollection&lt;T&gt; type 实现了IList&lt;T&gt; 接口,您可以使用它来围绕私有列表创建只读包装器,因此您可以通过公共属性仅公开该只读包装器,但保留您的实际列出私有,从而防止用户更改它。比如这样:

interface IMyClass {
    IList<int> MyList { get; set; }
    void AddToList(int newVal);
}

public class MyClass: IMyClass {
    private readonly List<int> myPrivateList;

    public IList<int> MyList { get; set; }

    public MyClass()
    {
        // Create private list used internally
        myPrivateList = new List<int>();

        // Create read-only wrapper around myPrivateList used for the public MyList property
        MyList = new ReadOnlyCollection<int>(myPrivateList);
    }

    public void AddToList(int newVal)
    {
        // Use myPrivateList instead of MyList
        myPrivateList.Add(newVal);
    }
}

虽然这可能对其他开发人员有点误导,但恕我直言,这样的解决方案应该在实施中正确记录。

【讨论】:

  • 我认为您应该提到ReadOnlyCollection&lt;T&gt; 会尝试更改集合 - 取决于 OP 的用例,这可能正是他想要的,或者他可能想避免这种情况并默默吞下这样的尝试。
  • 嗯,我需要运行一些测试。我还找到了一个名为AsReadOnly() 的列表方法,它返回一个只读集合。这似乎正是我想要的。
【解决方案2】:

您可以将列表保密而不向消费者公开。这样,消费者就无法修改您的列表。

此外,通过使用 IReadOnlyList,消费者甚至不需要添加或删除。

private List<int> myList;
public IReadOnlyList<int> MyList => myList.ToArray();

但是,每次转换为 Array 都会降低性能。如果不想浪费资源,又绝对不能改变接口,则需要自己创建 IList 实现:

public class MyClass: IMyClass {
    private IList<int> myInnerList;
    public IList<int> MyList { get; }


    MyClass() 
    {
        myInnerList = new List<int>();
        MyList = new ProtectedList(myInnerList);
    }

    public void AddToList(int newVal)
    {
        myInnerList.Add(newVal);
    }
}

/// <summary>
/// Encapsulates an IList in a way, so that you can only change it
/// through the original IList reference.
/// </summary>
class ProtectedList<T> : IList<T>, IReadOnlyList<T>
{
    private IList<T> innerList;
    public ProtectedList(IList<T> innerList) => this.innerList = innerList;

    public T this[int index]
    {
        get => innerList[index];
        set => throw new InvalidOperationException();
    }
    public int Count => innerList.Count;
    public bool IsReadOnly => true;
    public void Add(T item) => throw new InvalidOperationException();
    public void Clear() => throw new InvalidOperationException();
    public bool Contains(T item) => innerList.Contains(item);
    public void CopyTo(T[] array, int arrayIndex) => innerList.CopyTo(array, arrayIndex);
    public IEnumerator<T> GetEnumerator() => innerList.GetEnumerator();
    public int IndexOf(T item) => innerList.IndexOf(item);
    public void Insert(int index, T item) => throw new InvalidOperationException();
    public bool Remove(T item) => throw new InvalidOperationException();
    public void RemoveAt(int index) => throw new InvalidOperationException();
    IEnumerator IEnumerable.GetEnumerator() => innerList.GetEnumerator();
}

}

【讨论】:

  • 是的,但在中间我有IList&lt;int&gt;,因此我无法将其实现为IReadOnlyList&lt;int&gt;..
  • Array 也实现了 ilist,因此上述方法有效。但是,如果性能是关键,您应该创建自己的 ilist 实现。
【解决方案3】:

您可以只围绕实现IList&lt;T&gt; 接口的IList&lt;T&gt; 进行包装。此包装器会将所有功能委托给其内部IList&lt;T&gt;,并禁止直接操作该列表。比如:

public MyList<T> : IList<T>
{
    private IList<T> innerList;
    public MyList(IList<T> innerList) { this.innerList = innerList; }

    // valid operations are just delegated to the inner list...
    public int Count { get { return innerList.Count; } }
    // those you want to prohibit either do nothing or throw - depends on your desired outcome
    public void Add(T item) { return; }

    // and so on ...
}

【讨论】:

    猜你喜欢
    • 2012-06-02
    • 1970-01-01
    • 2016-12-27
    • 1970-01-01
    • 2013-04-18
    • 2011-10-31
    • 2011-11-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多