I like to keep a tight leash on my classes. No unnecessary exposure. Consider the following simple class:
class PersonnelManager {
List<Person> _people = new List<Person>();
public void Add(Person person) {
// do some validation
_people.Add(person);
// maybe some extras
}
}
I want to expose the currently managed Person objects for interested parties. One way to do this would be:
public List<Person> People {
get { return _people; }
}
This may be obvious – but I’ve seen this too many times. Exposing the property as List<>, IList<> or ICollection<> defeats the purpose. Anyone can call Add on the returned object.
A better alternative is to use IEnumerable<>:
public IEnumerable<Person> People {
get { return _people; }
}
This is much better: no Add method visible. Still, curious (and malicious) clients may try this:
var mgr = new PersonnelManager();
var list = mgr.People as List<Person>;
if(list != null)
list.Add(new Person { Age = 10, Name = "Bart" });
Or better (lower common denominator):
var list = mgr.People as ICollection<Person>;
if(list != null)
list.Add(new Person { Age = 10, Name = "Bart" });
This will work with other lists, such as ObservableCollection<>.
Despite our best efforts, Person objects can still be added “behind our backs”. Although it’s less likely than the first alternatives, I still would have liked something “stronger”.
The best I could come up with is using yield:
public IEnumerable<Person> People {
get {
foreach(var p in _people)
yield return p;
}
}
This has the desired effect: No cast to ICollection<> or IList<> would succeed, as the compiler re-writes the code and creates its own implementation of IEnumerable<Person> (which does not include other interfaces). The downside, of course, is the added code we had to write.
Is this really bulletproof? Almost. A full trust application can still theoretically use reflection to get to the private List<Person> member and call Add on it directly, but it’s a lot less likely, as such code is, naturally, cumbersome and fragile.