Home > other >  How to point to underlying class of <T> in generic factory
How to point to underlying class of <T> in generic factory

Time:07-22

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();
}
  • Related