在C#中封装集合

Vit*_*kov 22 .net c# automatic-properties

由于3.0 C#具有很好的语法糖,如自动属性,这大大简化了封装原理的实现.如果你将它与原子值一起使用,这是很好的,所以你可以像这样替换封装模式:

private string _name;

public string Name 
{
  get { return _name; }
  set { _name = value; }
}
Run Code Online (Sandbox Code Playgroud)

只有一行:

public string FirstName  { get; set; }
Run Code Online (Sandbox Code Playgroud)

我非常喜欢这个很棒的功能,因为它节省了很多开发人员的时间.


但是当你创建指向集合的属性时,事情并不是那么好.通常我会看到以两种方式之一实现的集合属性.

1)根本没有自动属性可以使用字段初始化程序:

private List<string> _names = new List<string>();

public List<string> Names
{
    get { return _names; }
}
Run Code Online (Sandbox Code Playgroud)

2)使用自动属性.如果类只有一个构造函数,这种方法就可以了:

public List<string> Names { get; private set; }

public .ctor()
{
    Names = new List<string>();
}
Run Code Online (Sandbox Code Playgroud)

但是当你以这样的方式处理像列表这样的可变集合时,你会破坏封装,因为这个属性的用户可以修改集合而不让容器知道(如果忘记将setter设为私有,甚至可以替换集合).

至于我,关于Encapsulate Collection模式,集合封装的正确实现应如下所示:

private readonly List<string> _names = new List<string>();

public ICollection<string> Names
{
    get { return new ReadOnlyCollection<string>(_names); }
}

public void Add_Name(string name)
{
    _names.Add(name);
}

public void Remove_Names(string name)
{
    _names.Remove(name);
}

public void Clear_Names()
{
    _names.Clear();
}
Run Code Online (Sandbox Code Playgroud)

老实说,我不记得我是否在实际代码中遇到过这种实现,即使在框架源代码中也是如此.我认为这是因为人们懒惰并且避免编写这么多代码只是为了使封装更强一些.

我想知道为什么C#团队没有提供一些明确而简单的方法来定义集合自动属性,所以开发人员可以取悦他们的懒惰仍然创建健壮的代码?

Jus*_*tin 20

TL; DR,C#编译器没有自动集合,因为有很多不同的方式来公开集合.在展示集合时,您应该仔细考虑如何封装集合并使用正确的方法.


C#编译器提供自动属性的原因是因为它们很常见并且几乎总是以相同的方式工作,但是当您发现处理集合时情况很少这么简单 - 有许多不同的方式来公开集合,正确的方法总是取决于具体情况,仅举几例:

1)可以更改的集合

通常不需要对暴露的集合施加任何真正的限制:

public List<T> Collection
{
    get
    {
        return this.collection;
    }
    set
    {
        if (value == null)
        {
            throw new ArgumentNullException();
        }
        this.collection = value;
    }
}
private List<T> collection = new List<T>();
Run Code Online (Sandbox Code Playgroud)

确保集合永远不为空是一个好主意,否则你可以只使用自动属性.除非我有充分的理由想要更多地封装我的集合,否则我总是使用这种方法来简化.

2)可以修改但不能交换的集合

您可以按照自己喜欢的方式编写代码,但想法是一样的 - 暴露的集合允许修改项目,但底层集合本身不能用其他集合替换.例如:

public IList<T> Collection
{
    get
    {
        return this.collection;
    }
}
private ObservableCollection<T> collection = new ObservableCollection<T>();
Run Code Online (Sandbox Code Playgroud)

当消费者应该能够修改集合但我订阅了更改通知时,我倾向于在处理可观察集合之类的事情时使用这种简单模式- 如果让消费者交换整个集合,那么你只会引起麻烦.

3)公开集合的只读副本

经常要防止消费者修改暴露收集-通常但是你千万要曝光类能够修改集合.一种简单的方法是公开集合的只读副本:

public ReadOnlyCollection<T> Collection
{
    get
    {
        return new ReadOnlyCollection<T>(this.collection);
    }
}
private List<T> collection = new List<T>();
Run Code Online (Sandbox Code Playgroud)

这带来了返回集合永远不会更改的属性,即使基础集合发生更改也是如此.这通常是一件好事,因为它允许消费者迭代返回的集合,而不用担心它可能会被更改:

foreach (var item in MyClass.Collection)
{
    // This is safe - even if MyClass changes the underlying collection
    // we won't be affected as we are working with a copy
}
Run Code Online (Sandbox Code Playgroud)

但是,这并不总是预期的(或期望的)行为 - 例如,Controls属性不会以这种方式工作.您还应该考虑以这种方式复制许多大型集合可能效率低下.

当暴露是只读的藏品总是知道的是,在控制项目仍然被修改.同样,这可能是一件好事,但如果您希望公开的集合"完全"不可修改,那么请确保集合中的项目也是只读/不可变的(例如System.String).

4)可以修改但只能以某种方式修改的集合

假设您要公开一个可以添加项目但不删除项目的集合?您可以在公开类本身上公开属性:

public ReadOnlyCollection<T> Collection
{
    get
    {
        return new ReadOnlyCollection<T>(this.collection);
    }
}
private List<T> collection = new List<T>();

public AddItem(T item);
Run Code Online (Sandbox Code Playgroud)

但是,如果您的对象有很多这样的集合,那么您的界面很快就会变得混乱和混乱.此外,我发现这种模式有时可能违反直觉:

var collection = MyClass.Collection;
int count = collection.Count;

MyClass.AddItem(item);

Debug.Assert(collection.Count > count, "huh?");
Run Code Online (Sandbox Code Playgroud)

它需要付出更多努力,但IMO更简洁的方法是公开一个自定义集合,它封装了您的"真实"集合以及有关如何更改集合的规则,例如:

public sealed class CustomCollection<T> : IList<T>
{
    private IList<T> wrappedCollection;

    public CustomCollection(IList<T> wrappedCollection)
    {
        if (wrappedCollection == null)
        {
            throw new ArgumentNullException("wrappedCollection");
        }
        this.wrappedCollection = wrappedCollection;
    }

    // "hide" methods that don't make sense by explicitly implementing them and
    // throwing a NotSupportedException
    void IList<T>.RemoveAt(int index)
    {
        throw new NotSupportedException();
    }

    // Implement methods that do make sense by passing the call to the wrapped collection
    public void Add(T item)
    {
        this.wrappedCollection.Add(item);
    }
}
Run Code Online (Sandbox Code Playgroud)

使用示例:

public MyClass()
{
    this.wrappedCollection = new CustomCollection<T>(this.collection)
}

public CustomCollection<T> Collection
{
    get
    {
        return this.wrappedCollection;
    }
}
private CustomCollection<T> wrappedCollection;
private List<T> collection = new List<T>();
Run Code Online (Sandbox Code Playgroud)

现在,公开的集合将我们的规则包含在如何修改集合的规则中,并立即反映对底层集合所做的更改(这可能是也可能不是一件好事).对于大型馆藏来说,它也可能更有效.


mir*_*ych 5

private IList<string> _list = new List<string>();

public IEnumerable<string> List
{
  get
  {
    ///return _list;
     return _list.ToList();
  }
}
Run Code Online (Sandbox Code Playgroud)

  • @Justin 为什么要重新发明轮子?ReadOnlyCollection 完全够用了。另请注意,ControlCollection 不是一成不变的:http://msdn.microsoft.com/en-us/library/system.windows.forms.control.controlcollection.aspx。 (2认同)