Consider a bird:
public interface IBird
{
}
public class Duck : IBird
{
public string DuckyProperty { get; set; } //this property is Duck specific!
}
And a bird processor:
public interface IBirdProcessor<out T> where T : IBird
{
double GetBirdWeight(T obj); //this does not compile
}
public class DuckProcessor : IBirdProcessor<Duck>
{
public double GetBirdWeight(Duck duck)
{
return double.Parse(duck.DuckyProperty);
}
}
And a factory to get a bird processor:
public class BirdProcessorFactory
{
public IBirdProcessor<T> GetBirdProcessor<T>(T obj) where T : IBird
{
return (IBirdProcessor<T>)new DuckProcessor();
}
}
Startup:
static void Main()
{
var bird = new Duck() { DuckyProperty = "23" } as IBird; //the cast is necessary to simulate a real world scenario
var factory = new BirdProcessorFactory();
var provider = factory.GetBirdProcessor(bird);
var weight = provider.GetBirdWeight(bird);
}
I want to have an abstract generic bird factory but I get the following error instead:
The type parameter 'T' must be contravariantly valid on 'Program.IBirdProcessor<T>.GetBirdWeight(T)'. 'T' is covariant
If I remove 'out' keyword then I get:
System.InvalidCastException: 'Unable to cast object of type 'DuckProcessor' to type 'IBirdProcessor`1[NotVegetables.Program IBird]'.'
The cast won't work!
As a workaround I can do this:
public class DuckProcessor : IBirdProcessor<IBird>
{
public double GetBirdWeight(IBird bird)
{
var duck = bird as Duck;
return double.Parse(duck.DuckyProperty);
}
}
But this defies usage of generics at all, I feel like DuckProcessor should work with ducks, not with abstract birds.
Am I doing something completely wrong?
CodePudding user response:
All the types in C# generics have to be known at compile time. Only IBird
is known at compile time, and the factory has to return an IBirdProcessor<IBird>
. Since covariancy isn't an option (because DuckProcessor.GetBirdWeight
only accepts Duck
s), a DuckProcessor
cannot be returned.
You can kind of get the behavior you are wanting with:
public interface IBirdProcessor
{
double GetBirdWeight(IBird obj);
}
public abstract class BirdProcessorBase<T> : IBirdProcessor where T : IBird
{
public double GetBirdWeight(IBird bird) => GetBirdWeightInternal((T)bird);
protected abstract double GetBirdWeightInternal(T bird);
}
public class DuckProcessor : BirdProcessorBase<Duck>
{
protected override double GetBirdWeightInternal(Duck duck)
{
return double.Parse(duck.DuckyProperty);
}
}
public class BirdProcessorFactory
{
public IBirdProcessor GetBirdProcessor(IBird bird)
{
if (bird.GetType().IsAssignableTo(typeof(Duck)))
return new DuckProcessor();
throw new Exception($"No processor for {bird.GetType().Name}");
}
}
Though if you ever call the processor returned from the factory with a type that doesn't match, it will throw exceptions at you.