Got some vegetables going on:
public interface IVegetable
{
}
public class Potato : IVegetable
{
}
public class Onion : IVegetable
{
}
We'd focus on the onion and process it: I have single generic interface for a generic vegetable processor and one onion-specific:
public interface IVegetableProcessor<T> where T : IVegetable
{
string GetColor(T vegetable);
}
public interface IOnionProcessor : IVegetableProcessor<Onion>
{
void MakeCry(Onion onion);
}
public class OnionProcessor : IOnionProcessor
{
public string GetColor(Onion onion)
{
return "Purple";
}
public void MakeCry(Onion onion)
{
Console.WriteLine($"{onion} made you cry!");
}
}
As well as a generic factory:
interface IVegetableProcessorFactory
{
IVegetableProcessor<T> GetVegetableProcessor<T>(T vegetable) where T : IVegetable;
}
internal class VegetableProcessorFactory : IVegetableProcessorFactory
{
public IVegetableProcessor<T> GetVegetableProcessor<T>(T vegetable) where T : IVegetable
{
object processor = vegetable switch
{
Onion => new OnionProcessor(),
_ => throw new NotImplementedException($"Other vegetables not here yet")
};
return (IVegetableProcessor<T>)processor; //this will fail later
}
}
And finally this does not work:
static void Main(string[] args)
{
var onion = new Onion() as IVegetable;
var factory = new VegetableProcessorFactory();
var processor = factory.GetVegetableProcessor(onion);
Console.WriteLine(processor.GetColor(onion));
Console.ReadLine();
}
The error is:
System.InvalidCastException: 'Unable to cast object of type 'OnionProcessor' to type 'IVegetableProcessor`1[Vegetables.Program IVegetable]'.'
How to make it understand the underlying class of IVegetable and cast the processor to it's corresponding type?
CodePudding user response:
Your input is already contravariant, but by casting your Onion instance to IVegetable
, it is no longer able to be cast back to IVetetableProcessor<T>
in your factory, because at that point you need IVetetableProcessor<Onion>
, but what you have is a IVegetableProcessor<IVegetable>
, and your interface is not covariant.
By simply removing your initial cast of new Onion
to IVegetable
, your code works as is:
static void Main(string[] args)
{
var onion = new Onion();
var factory = new VegetableProcessorFactory();
var processor = factory.GetVegetableProcessor(onion);
Console.WriteLine(processor.GetColor(onion));
Console.ReadLine();
}
public interface IVegetable { }
public class Potato : IVegetable { }
public class Onion : IVegetable { }
public interface IVegetableProcessor<T> where T : IVegetable
{
string GetColor(T vegetable);
}
public interface IOnionProcessor : IVegetableProcessor<Onion>
{
void MakeCry();
}
public class OnionProcessor : IOnionProcessor
{
public string GetColor(Onion vegetable)
{
return "Purple";
}
public void MakeCry()
{
Console.WriteLine("You cry now!");
}
}
interface IVegetableProcessorFactory
{
IVegetableProcessor<T> GetVegetableProcessor<T>(T vegetable) where T : IVegetable;
}
internal class VegetableProcessorFactory : IVegetableProcessorFactory
{
public IVegetableProcessor<T> GetVegetableProcessor<T>(T vegetable) where T : IVegetable
{
var processor = vegetable switch
{
Onion => new OnionProcessor(),
_ => throw new NotImplementedException($"Other vegetables not here yet")
};
return (IVegetableProcessor<T>)processor;
}
}
CodePudding user response:
The fact that you have to cast in the first place is your hint that something is broken in your composition. As Selvin mentioned in the comments, implementing IVegetableProcessor<Onion>
is NOT the same thing as implementing IVegetableProcessor<IVegetable>
.
Your processor interfaces should implement IVegetableProcessor<IVegetable>
and take IVegetable instances, allowing contravariance for the input parameters:
public interface IVegetableProcessor<T> where T : IVegetable
{
string GetColor(T vegetable);
}
public interface IOnionProcessor : IVegetableProcessor<IVegetable>
{
void MakeCry();
}
public class OnionProcessor : IOnionProcessor
{
public string GetColor(IVegetable vegetable)
{
return "Purple";
}
public void MakeCry()
{
Console.WriteLine("You cry now!");
}
}
interface IVegetableProcessorFactory
{
IVegetableProcessor<IVegetable> GetVegetableProcessor(IVegetable vegetable);
}
internal class VegetableProcessorFactory : IVegetableProcessorFactory
{
public IVegetableProcessor<IVegetable> GetVegetableProcessor(IVegetable vegetable)
{
var processor = vegetable switch
{
Onion => new OnionProcessor(),
_ => throw new NotImplementedException($"Other vegetables not here yet")
};
return processor;
}
}
This correctly outputs "Purple" when run via:
static void Main(string[] args)
{
var onion = new Onion();
var factory = new VegetableProcessorFactory();
var processor = factory.GetVegetableProcessor(onion);
Console.WriteLine(processor.GetColor(onion));
Console.ReadLine();
}