C# Protecting private collections from modification

C# Protecting private collections from modification

Why do we want to hide data?

One of the big advantages of object oriented programming is claimed to be possibilities to encapsulate data and hide internal implementation details. This enables us to make changes to our implementation without impacting our users and ensures our internal data does not get modified in ways that we do not control. In other words, it helps us keep sane.

Protecting data in a class

In C# fields in a class are protected from outside access by using the private keyword.

public class MyClass
{
  private List<int> _myInts;
  ...
}

Now _myInts is only accessible from within MyClass.

Breaking encapsulation

Now, you might want users of MyClass to be able to read the data that _myInts contain. The easiest way to do this is to make _myInts public.

public class MyClass
{
  public List<int> _myInts;
  ...
}

But now you no longer have control over what happens to _myInts. Any external user might alter the list or even change the reference to point at a completely different list object (or null).

Re-adding the protection

In an attempt to prevent a user from modifying the private List you might add a getter method, let’s say by creating a read only property.

public class MyClass
{
  private List<int> _myInts;
  public List<int> MyInts => _myInts;
  ...
}

This code can be simplified by converting to an auto property.

public class MyClass
{
  public List<int> MyInts { get; } = new List<int>();
  ...
}

Under the hood a private field is still created, this is called a backing field in C# lingo. By only supplying a get and no set you prevent an external user from setting the backing field to reference another list (or null). However, an external user may still modify the contents of the original list. For example var m = new MyClass(); m.MyInts.Add(1); is still possible to do.

To prevent this you might attempt to give an external user access to the list by supplying it via an IEnumerable or IReadOnlyCollection interface. But doing that prevents you from modifying the list yourself, so you will have to re-add the _myInts field.

public class MyClass
{
  private readonly List<int> _myInts = new List<int>();
  public IEnumerable<int> MyInts => _myInts;
  // Alternatively
  public IReadOnlyCollection<int> MyInts => _myInts;
  ...
}

Ok, so now we must be safe, right? Well, what if the user of your class does something like this.

var m = new MyClass();
var i = m.MyInts as List<int>;
i.Add(1);

Now he is still able to modify your private readonly List. But don’t give up! There is still one last thing to try. Here comes the AsReadOnly method to the rescue!

public class MyClass
{
  private readonly List<int> _myInts = new List<int>();
  public IEnumerable<int> MyInts => _myInts.AsReadOnly();
  // Alternatively
  public IReadOnlyCollection<int> MyInts => _myInts.AsReadOnly();
}

The AsReadOnly method adds a read only wrapper around the list. Any attempts to cast it to a writable collection will fail. However, casting code will still compile, but fail in runtime.

var m = new MyClass();
var i = m.MyInts as List<int>; // Would set i to null
i.Add(1); // Would throw a null reference exception
...
var j = (List<int>) m.MyInts; // Would throw an InvalidCastException

Conclusion

To prevent modifications to a collection, expose it only through a ReadOnlyCollection wrapper. The easiest way to do this is to use the AsReadOnly method. However it is also possible to wrap the collection by creating a new ReadOnlyCollection object, new ReadOnlyCollection<int>(_myInts); for example.

Also note that this is a O(1) operation. The elements in the collection are not copied so there is only a really minor overhead cost to wrapping it.

2 kommentarer

Ahraz Publicerat8:18 f m - Aug 28, 2018

Awesome! Never would have come across this normally. Thanks for the insights

    Eric Bäckhage Publicerat10:38 f m - Aug 28, 2018

    Thanks for the feedback. Glad you enjoyed it!