Jelle Druyts .NET Consultant
Just another ignorant weirdo from Antwerp, Belgium trying to make sense out of it all
Setting The Stage: Invariance
I had a rather interesting problem using generics in C# 2.0 today, but before I dig in to the actual issue, let me briefly introduce you to the "invariant" nature of generics.
As you know, you can certainly assign a string to a variable of type object because string is a subclass of object:
string
object
string s = "some value"; object o = s; // No problem
Now suppose you have a List<object> and a List<string>, which are generic types describing a list of objects and a list of strings, respectively. Given that string is a subclass of object, you might assume that you can assign this List<string> to a List<object> as well:
List<object>
List<string>
List<string> strings = new List<string>(); List<object> objects = strings; // Fails
The compiler, however, rudely disagrees with you: error CS0029: Cannot implicitly convert type 'System.Collections.Generic.List<string>' to 'System.Collections.Generic.List<object>'. This is because generic types in .NET are said to be invariant, which means that they don't adhere to the same inheritance tree as their type parameters (string and object in this case).
Although similar in nature, arrays don't share this nature and are said to be covariant, which means that the following code is perfectly legal:
string[] strings = new string[] {}; object objects[] = strings; // No problem
For more information and the reasoning behind this, check out Rick Byers' excellent post on Generic type parameter variance in the CLR.
The Problem Manifestation
With that in mind, imagine the following class hierarchy:
So there are two interfaces describing a document (IDocument) and a cabinet (ICabinet) which basically holds a generic list of these IDocuments. The Document class implements the IDocument interface in the obvious way. The interesting part is the Cabinet class:
IDocument
ICabinet
Document
Cabinet
public interface ICabinet{ string Name { get; } string Location { get; } List<IDocument> Documents { get; }}
public class Cabinet : ICabinet{ private string name = null; private string location = null; private List<IDocument> documents = new List<IDocument>(); public string Name { get { return name; } } public string Location { get { return location; } } public List<IDocument> Documents { get { return documents; } }}
To implement the interface, the Cabinet class has to expose the List<IDocument> through the Documents property, and since generics are invariant, this means that the return type cannot be List<Document> (note the use of the class here, not the interface). This implies that the private documents backing field must also be a List<IDocument> in stead of a List<Document>, for the same reason.
List<IDocument>
Documents
List<Document>
documents
Now there's currently no link between the concrete cabinet type and the concrete document type and I really want this Documents property on my Cabinet class to expose a List<Document>, so that's a problem. The reason I need this, is that my cabinet class will be exposed over a Web Service and must therefore be serializable. As you might know, serializable types cannot publicly expose interface types because the serializer cannot handle them. But there could be other valid reasons why you would want the concrete cabinet type to return the concrete document classes.
Trying To Solve The Problem
I came up with a moderately satisfying solution... I implemented the private backing field and the Documents property using the "class version", and explicitly implemented the interface as such:
[Serializable]public class Cabinet : ICabinet{ private string name = null; private string location = null; private List<Document> documents = new List<Document>(); public string Name { get { return name; } } public string Location { get { return location; } } public List<Document> Documents { get { return documents; } } [XmlIgnore] List<IDocument> ICabinet.Documents // Explicit implementation { get { return this.Documents.ConvertAll<IDocument>( delegate(Document target) { return (IDocument)target; } ); } }}
The explicit interface implementation will not only "hide" the property from normal consumers of the class and resolve the obvious naming conflict of the two Documents properties, but the xml serializer will also ignore the explicitly implemented property (and just to be sure, I also applied the [XmlIgnore] attribute to it). If this property is queried anyway, it will convert the "class version" to the "interface version" using the generic ConvertAll method, which returns a new list of another type by converting each list item through an anonymous method. In this case, a simple cast is enough because they are covariant. This yields the following picture:
[XmlIgnore]
ConvertAll
An Additional Problem
There's still an issue with this approach, though: if you come in through the interface and try to manipulate the documents list (for example, if you add an item to it), you will be working on this freshly-created copy of the list and your changes won't show up in the original list:
ICabinet cabinet = new Cabinet(); // Declared as the interface cabinet.Documents.Add(new Document()); // Won't work as expectedAssert.AreEqual(cabinet.Documents.Count, 0);
Luckily, I had the option to change the interface to return a ReadOnlyCollection<IDocument> so that modifications weren't even possible and the problem went away. Luckily.
ReadOnlyCollection<IDocument>
But if I weren't as blessed, I would probably have given up and steered away from these generic types altogether in this scenario since they generate quite some issues when used in inheritance hierarchies as you've seen. I'm a big fan of generics and they've surprised me in many interesting ways (just look at the sheer beauty of that ConvertAll combined with the anonymous delegate above), but this time I think I've stretched them too far...