NZJames
NZJames

Reputation: 5055

Array co-variance in C# generic list

I have an example where I want an abstract class interface to return something like this

abstract class AnimalProcessor {
    public abstract IList<Animal> ProcessResults();
}

Then the concrete examples

class GiraffeProcessor : AnimalProcessor {
    public override IList<Animal> ProcessResults() {
        return new List<Giraffe>();
    }
}

class LionProcessor : AnimalProcessor {
    public override IList<Animal> ProcessResults() {
        return new List<Lion>();
    }
}

The problem is that the concrete classes need to have the same signature to override the ProcessResults() method so they need to return an IList<Animal>, however the ACTUAL data I want to return is an IList<Lion>, IList<Giraffe> etc, but then the calling code has to do

GiraffeProcessor processor = new GiraffeProcessor();
IList<Animal> results = processor.GetResults();

Which does not give me an Ilist which is what I want.

Problems

1) Above code does not compile. The giraffeProcessor has to return a concrete List<Animal>, you can populate it with Giraffe objects but the object type you construct to return has to be List<Animal>. Not ideal.

2) When you return the results, you can only get an IList<Animal>, not IList<Giraffe>. I have tried casting explicitly to IList<Giraffe> with IList<Giraffe> results = (IList<Giraffe>) processor.GetResults(); which gives a runtime error, presumably because the object returned is NOT an IList<Giraffe>, it is an IList<Animal> which CONTAINS Giraffe objects.

Can anyone suggest what I am doing wrong here with my design as Im a bit stumped as to the best way to accomplish this.

Upvotes: 4

Views: 230

Answers (3)

phoog
phoog

Reputation: 43056

If you are using C# 4.0, you can ask yourself whether the processor should return IEnumerable<T> rather than IList<T>. If the answer is "yes", then you can profit from covariance:

abstract class AnimalProcessor { 
    public abstract IEnumerable<Animal> ProcessResults(); 
} 

class GiraffeProcessor : AnimalProcessor { 
    public override IEnumerable<Animal> ProcessResults() { 
        return new List<Giraffe>(); 
    } 
} 

class LionProcessor : AnimalProcessor { 
    public override IEnumerable<Animal> ProcessResults() { 
        return new List<Lion>(); 
    } 
} 

You have a couple of advantages here. First, you could implement these as iterator blocks:

class GiraffeProcessor : AnimalProcessor { 
    public override IEnumerable<Animal> ProcessResults() { 
        yield break;
    } 
} 

Second, and less trivially, you allow the client code to decide what kind of collection to dump the animals into -- if any. For example, consider that the consumer might want a LinkedList<Animal>:

var animals = new LinkedList<Animal>(animalProcessor.ProcessResults());

Or consider that the client might need only to iterate the sequence:

foreach (var animal in animalProcessor.ProcessResults())
    { /*... do something ...*/ }

In either case, if you were using a ToList() call in ProcessResults, you'd be creating a list for nothing. If the consumer really wants a List<Animal>, that can be accomplished very easily:

var animals = new List<Animal>(animalProcessor.ProcessResults());

Finally, you can also benefit from the generic approach, even if you change the interface type of the method's return value:

abstract class AnimalProcessor<T> where T : Animal { 
    public abstract IEnumerable<T> ProcessResults(); 
} 

class GiraffeProcessor : AnimalProcessor<Giraffe> { 
    public override IEnumerable<Giraffe> ProcessResults() { 
        yield break;
    } 
} 

class LionProcessor : AnimalProcessor<Lion> { 
    public override IEnumerable<Lion> ProcessResults() { 
        return Enumerable.Empty<Lion>();
    } 
} 

Upvotes: 1

You could resolve this by declaring AnimalProcessor with a generic type constraint, e.g.

public abstract class AnimalProcessor<T> where T : Animal 
{    
    public abstract IList<T> ProcessResults(); 
} 

If that doesnt work, you could use the LINQ Cast operator, for example:

public class GiraffeProcessor : AnimalProcessor 
{     
    public override IList<Animal> ProcessResults() 
    {         
        return new List<Giraffe>().Cast<Animal>();
    } 
}

Or, store the list internally as Animal but add Giraffe's to it, e.g.

public class GiraffeProcessor : AnimalProcessor 
{     
    private List<Giraffe> _innerList = new List<Giraffe>();
    public override IList<Animal> ProcessResults() 
    {         
        return new List<Animal>(innerList );        } 
}

Best regards,

Upvotes: 1

Marc Gravell
Marc Gravell

Reputation: 1063358

How about:

abstract class AnimalProcessor<T> where T : Animal {
    public abstract IList<T> ProcessResults();
}

class GiraffeProcessor : AnimalProcessor<Giraffe> {
    public override IList<Giraffe> ProcessResults() {
        return new List<Giraffe>();
    }
}

class LionProcessor : AnimalProcessor<Lion> {
    public override IList<Lion> ProcessResults() {
        return new List<Lion>();
    }
}

Upvotes: 6

Related Questions