Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Encapsulate Collection in C#

Tags:

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?

like image 546
Vitaliy Ulantikov Avatar asked Sep 16 '11 12:09

Vitaliy Ulantikov


People also ask

What is encapsulated collection?

If collection elements are contained inside a non-primitive container (standard collection class), by encapsulating the collection you can restrict access to unwanted standard methods of the collection (such as by restricting addition of new elements).

Is there encapsulation in C?

It is one of the core features of Object-oriented languages. However, it is not only limited to OOP languages only. In C, encapsulation has been despite the absence of private and public keywords. Encapsulation is being used by various other programming languages like C#, C++, PHP, JAVA as well.

What is the main reason for encapsulating collections on domain classes?

Domain models should encapsulate logic operations so that there is only one way to perform a given logical operation. That means avoiding exposing entity state and ensuring operations flow through specific methods.

What means encapsulation?

In general, encapsulation is the inclusion of one thing within another thing so that the included thing is not apparent. Decapsulation is the removal or the making apparent a thing previously encapsulated.


1 Answers

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.

like image 69
Justin Avatar answered Oct 12 '22 02:10

Justin