在C#中封装集合

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

private string _name; public string Name { get { return _name; } set { _name = value; } } 

只有一行:

 public string FirstName { get; set; } 

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


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

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

 private List _names = new List(); public List Names { get { return _names; } } 

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

 public List Names { get; private set; } public .ctor() { Names = new List(); } 

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

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

 private readonly List _names = new List(); public ICollection Names { get { return new ReadOnlyCollection(_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(); } 

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

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

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


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

1)可以更改的集合

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

 public List Collection { get { return this.collection; } set { if (value == null) { throw new ArgumentNullException(); } this.collection = value; } } private List collection = new List(); 

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

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

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

 public IList Collection { get { return this.collection; } } private ObservableCollection collection = new ObservableCollection(); 

当消费者应该能够修改集合但我订阅了更改通知时,我倾向于在处理像observable集合之类的东西时使用这个简单模式 – 如果你让消费者交换整个集合那么你只会引起麻烦。

3)公开集合的只读副本

通常,您希望阻止使用者修改公开的集合 – 通常,您通常希望公开类能够修改集合。 一种简单的方法是通过公开集合的只读副本:

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

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

 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 } 

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

暴露只读的集合时,请始终注意控件中的项仍然可以修改。 同样,这可能是一件好事,但如果您希望公开的集合“完全”不可修改,那么请确保集合中的项目也是只读/不可变的(例如System.String )。

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

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

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

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

 var collection = MyClass.Collection; int count = collection.Count; MyClass.AddItem(item); Debug.Assert(collection.Count > count, "huh?"); 

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

 public sealed class CustomCollection : IList { private IList wrappedCollection; public CustomCollection(IList 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.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); } } 

使用示例:

 public MyClass() { this.wrappedCollection = new CustomCollection(this.collection) } public CustomCollection Collection { get { return this.wrappedCollection; } } private CustomCollection wrappedCollection; private List collection = new List(); 

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

 private IList _list = new List(); public IEnumerable List { get { ///return _list; return _list.ToList(); } } 

每当我必须公开一个集合时,我倾向于使用IEnumerable但你不能使用Auto-properties来做到这一点。

我真的希望在.NET中实现的解决方案是不可变集合的概念。 这将真正解决您所谈论的问题,因为自动属性将以这种方式完美地工作:

  public ImmutableList {get; private set; } 

以任何方式修改列表的任何人都将获得ImmutableList的新实例而不是原始列表本身。

您当然可以实现自己的ImmutableList但我发现.NET Framework不包含这种元素有点奇怪。

公开ICollection不是一个好主意,因为它允许添加和删除操作。

  public sealed class CollectionHolderSample { private readonly List names; public CollectionHolderSample() { this.names = new List(); } public ReadOnlyCollection Items { get { return this.names; } } public void AddItem(string item) { this.names.Add(item); } } 

编辑:

正如您所提到的, Add()和Remove()方法来自显式ReadOnlyCollection.ICollection实现将抛出NotSupportedExceptionexception,因此集合是只读的。

此外,为了确保引擎盖下的集合真正只读,您可以检查IsReadOnly属性 。

MSDN说:

ReadOnlyCollection.ICollection.IsReadOnly如果ICollection是只读的,则属性为 true; 否则,是的。 在ReadOnlyCollection的默认实现中,此属性始终返回true。