Since 3.0 C# has great syntax sugar like auto-properties which a lot simplify implementation of encapsulation principle. This is good if you use it with atomic values, so you can replace encapsulation pattern like this:
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
with just one line:
public string FirstName { get; set; }
I very like this great feature as it saves a lot of developers time.
But things are not so great when you create property that points to collection. Usually I see collection properties implemented in one of two ways.
1) Without auto-properties at all to be able to use field initializer:
private List<string> _names = new List<string>();
public List<string> Names
{
get { return _names; }
}
2) Using auto-properties. This approach is ok if class has only one constructor:
public List<string> Names { get; private set; }
public .ctor()
{
Names = new List<string>();
}
But when you deal with mutable collections like lists such a way, you break encapsulation, as user of this property can modify collection without letting container know (or even replace collection if you forget to make setter private).
As for me, regarding to Encapsulate Collection pattern correct implementation of collection encapsulation should look like this:
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();
}
Honestly, I do not remember if I've met this kind of implementation in the real code, even in framework sources. I think this is because people are lazy and avoid writing such amount of code just to make encapsulation just a little bit stronger.
I wondering why C# team does not provide some clear and easy way to define collection auto-properties, so developers can please their laziness still creating robust code?
TL;DR, The C# compiler doesn't have auto-collections because there are lots of different ways of exposing collections. When exposing a collection you should think carefully about how you want the collection to be encapsulated and use the correct method.
The reason why the C# compiler provides auto-properties is because they are common and almost always work the same way, however as you are discovering the situation is rarely as simple when dealing with collections - there are many different ways of exposing a collection, the correct method always depends on the situation, to name a few:
1) A collection which can be changed
Often there is no real need to place any real restrictions on the exposed collection:
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>();
Its can be a good idea to make sure that the collection is never null, otherwise you can just use auto-properties. Unless I have a good reason for wanting more encapsulation of my collection I always use the this method for simplicity.
2) A collection that can be modified, but not swapped
You can code this any way you like, but the idea is the same - the exposed collection allows items to be modified but the underlying collection itself cannot be replaced with another collection. For example:
public IList<T> Collection
{
get
{
return this.collection;
}
}
private ObservableCollection<T> collection = new ObservableCollection<T>();
I tend to use this simple pattern when dealing with things like observable collections when the consumer should be able to modify the collection but I've subscribed to change notifications - If you let consumers swap the entire collection then you would just cause headaches.
3) Expose a read-only copy of a collection
Frequently you want to prevent consumers from modifying an exposed collection - usually however you do want the exposing class to be able to modify the collection. An easy way to do this is by exposing a read-only copy of your collection:
public ReadOnlyCollection<T> Collection
{
get
{
return new ReadOnlyCollection<T>(this.collection);
}
}
private List<T> collection = new List<T>();
This comes with the property that the returned collection never changes, even if the underlying collection changes. This is often a good thing as it allows consumers to iterate through the returned collection without fear that it might be changed:
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
}
However this isn't always the expected (or desired) behaviour - for example the Controls
property doesn't work this way. You should also consider that copying many large collections in this way is potentially inefficient.
When exposing collections that are read only always be aware that the items in the control can still be modified. Again this might be a good thing, but if you want the exposed collection to be "completely" unmodifiable then make sure that the items in the collection are also read-only / immutable (e.g. System.String
).
4) Collections that can be modified, but only in a certain way
Suppose you want to expose a collection that items can be added to, but not removed? You could expose properties on the exposing class itself:
public ReadOnlyCollection<T> Collection
{
get
{
return new ReadOnlyCollection<T>(this.collection);
}
}
private List<T> collection = new List<T>();
public AddItem(T item);
However if your object has many such collections then your interface can quickly get confusing and messy. Also I find this pattern to be potentially counter-intuitive at times:
var collection = MyClass.Collection;
int count = collection.Count;
MyClass.AddItem(item);
Debug.Assert(collection.Count > count, "huh?");
Its a lot more effort, but IMO a neater method is to expose a custom collection that encapsulates your "real" collection and the rules about how the collection can and can't be changed, for example:
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);
}
}
Example use:
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>();
The exposed collection now encapsualtes our rules on how the collection can and can't be modified and also immediately reflects changes made to the underlying collection (which may or may not be a good thing). Its also potentially more efficient for large collections.
private IList<string> _list = new List<string>();
public IEnumerable<string> List
{
get
{
///return _list;
return _list.ToList();
}
}
Whenever I have to expose a collection I tend to use IEnumerable<T>
but you can not use Auto-properties to do this obviously.
A solution I would really like to see implemented in .NET is the concept of immutable collections. This would really solve the problem you are talking about as auto-properties would work perfectly fine this way:
public ImmutableList<Foo> {get; private set; }
Anybody who modifies the list in any way would get a new instance of the ImmutableList
and not the original list itself.
You can of course implement your own ImmutableList
but I find it kind of strange that the .NET Framework doesn't include this kind of element.
Exposing ICollection<string>
is not a good idea because it allows adding and removing operations.
public sealed class CollectionHolderSample
{
private readonly List<string> names;
public CollectionHolderSample()
{
this.names = new List<string>();
}
public ReadOnlyCollection<string> Items
{
get
{
return this.names;
}
}
public void AddItem(string item)
{
this.names.Add(item);
}
}
EDIT:
As you mentioned Add() and Remove() methods which comes from explicit ReadOnlyCollection<T>.ICollection<T>
implementation will throw NotSupportedException
exception so collection is read only.
Also, to ensure that collection under the hood is really readonly you can check IsReadOnly Property.
MSDN says that:
ReadOnlyCollection.ICollection.IsReadOnly Property true if the ICollection is read-only; otherwise, false. In the default implementation of ReadOnlyCollection, this property always returns true.
来源:https://stackoverflow.com/questions/7444395/encapsulate-collection-in-c-sharp