Avoid Exposing Collections Directly as Properties
 
Published: 22 Feb 2011
Unedited - Community Contributed
Abstract
.NET makes it easy to create strongly typed collections and expose them as properties of our classes. However, this generally results in a design that fails at encapsulation and exposes too much of the class's internal state. Learn a few simple techniques to keep from going down this path in this article.
by Steven Smith
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 28680/ 41

Introduction

Sometimes your domain objects have one-to-many or many-to-many relationships with other objects.  For instance, your Customers might have collections of Orders associated with them.  The simplest way to model this is to expose a List<Order> field or property off of your Customer class.  Unfortunately, this has a number of negative consequences because of how it breaks encapsulation.  Fortunately, there are some effective ways to shield yourself from these issues while still exposing the collection's data to your class's clients.

Doing It Wrong

Doing it the simplest way that could possibly work would mean simply exposing the internal state of the Customer class by creating a public field of List<Order>.  The listing below shows what this might look like, and a couple of tests that show the required functionality (and currently pass):

 

At first glance, this appears to solve the requirements of the system.  Now let's add a few more tests to reveal the weaknesses in this approach, and refactor it to improve things.

Guarding Against Destructive Actions

The current implementation would allow a user of Customer to set its OrderHistory field to null.  This test shows the problem:

The simplest fix to this is to change our OrderHistory to use a property, and to make it readonly by giving it a private setter.  With this change in place, the above test no longer even compiles, so we should be safe from this problem.

However, users of the system can still perform destructive actions on the OrderHistory by using the List<Order> type's methods.  For instance, they can Clear() the collection, as this next test shows:

In order to remove access to the List<T> class's methods, we can hide the implementation details of our internal representation behind a read-only interface, like IEnumerable.  However, changing to IEnumerable also means we can no longer add directly to OrderHistory - this is also a good thing, since that was exposing too much internal state.  Thus we add a method to Customer specifically for adding orders to the order history.  The resulting class and tests are shown below:

Overriding Intended Behavior

Even if you only expose your type as an IEnumberable, sneaky clients of your class can still get to its underlying methods if they guess correctly.  As the next test shows, if a consumer of your class is able to guess (or determine through reflection or a decompiler) the actual underlying type of your interface, then all bets are off and they can destroy your data at will:

We can probably safely say that any developer who so blatantly overcomes your attempts at encapsulating data deserves the bugs they create by doing so.  However, there is one more alternative approach

Wrapping Collections

The built-in .NET class, ReadOnlyCollection<T> is the standard way to wrap a collection and make available only a read-only version of the collection's contents.  The collection lives in the System.Collections.ObjectModel namespace.  Once we update our Order class to expose this collection as the type of its OrderHistory property, our SneakyOrderHistoryCannotBeCleared() test no longer compiles, since it's not possible to cast a ReadOnlyCollection<T> to a List<T>.  The final version of the Customer class looks like this:

 

Summary

Encapsulation of object state is fundamental to proper object oriented programming and design.  C# makes it very easy to create properties and to work with strongly typed collections of objects, and often this results in object models that expose too much functionality when it comes to collections.  By using interfaces or wrapper classes like the ReadOnlyCollection<T>, we can ensure our classes' internal state remains safe from calling code that might inadvertently introduce bugs by changing it inappropriately.



User Comments

Title: Picture links all broken (404)   
Name: Anonymous
Date: 2012-12-12 8:46:55 AM
Comment:
The images are blank and result in a 404 if opened individually.
Title: Very nice   
Name: Thanigainathan
Date: 2011-03-28 2:36:03 PM
Comment:
Hi,

The article is very nice. So you mean only the parent of the list property can modify its state. Will this be sort of restricting the features of collections ?

Thanks,
Thani






Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-04-19 11:37:43 PM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search