Reputation: 2457
I am currently struggling with a class design involving generic lists:
Code says more than thousand words:
class Document
{
public List<Result> Results { get; } = new List<Result>();
}
class Result
{
}
class SpecialDocument : Document
{
public new List<SpecialResult> Results { get; } = new List<SpecialResult>();
}
class SpecialResult : Result
{
}
What I don't like of the current design is that SpecialDocument.Results
hides Document.Results
. If one has the Document
view on a SpecialDocument
, there are no results at all, even there could be the Result
view of all SpecialResult
elements.
What I would like to accomplish is:
SpecialDocument doc = new SpecialDocument();
doc.Results.Add(new SpecialResult());
Assert.AreEqual(1, (doc as Document).Results.Count); // Here my design obviously fails right now
... I'd like to accomplish that without loosing type safety (as that's actually the reason for List<T>
not being covariant).
I forgot to mention that SpecialDocument
and Document
actually need to have the same successor (or implement the same interface), such that they can coexist within one collection:
List<Document> documents = new List<Document>()
{
new Document(),
new SpecialDocument()
};
Upvotes: 1
Views: 66
Reputation: 16991
If it is acceptable for IDocument.Results
to be of type IEnumerable<Result>
instead of IList<Result>
, this will work.
The Result
property must be typed this way due to co-variance / contra-variance rules.
Code is based on @Kyle B's excellent answer
void Main()
{
IDocument doc = new SpecialDocument();
//I added AddResult() to the interface to allow adding results, instead of calling Add() directly on the list.
doc.AddResult(new SpecialResult());
Assert.AreEqual(1, doc.Results.Count);
// prooving that the items can be added to a list, and that list can handle all the result types.
var docs = new List<IDocument>();
docs.Add(new Document());
docs.Add(new SpecialDocument());
var results = docs.SelectMany(d => d.Results)
// results now contains all results from all documents
}
abstract class Document<T> : IDocument where T : Result
{
public List<T> Results { get; } = new List<T>();
IEnumerable<Result> IDocument.Results => Results;
void IDocument.AddResult(Result result)
{
this.Results.Add((T)result);
}
}
abstract class Result
{
}
class Document : Document<Result>
{
}
class SpecialDocument : Document<SpecialResult>
{
}
class SpecialResult : Result
{
}
interface IDocument
{
IEnumerable<Result> Results { get; }
AddResult(Result result);
}
Upvotes: 1
Reputation: 2889
Have you tried generics?
abstract class Document<T> where T : Result
{
public List<T> Results { get; } = new List<T>();
}
abstract class Result
{
}
class SpecialDocument : Document<SpecialResult>
{
}
class SpecialResult : Result
{
}
SpecialDocument
will automatically instantiate List<SpecialResult>
.
Upvotes: 4