Search code examples
c#genericstype-inferencegeneric-variance

Accessing the generic parameter of generic parameter?


I'm having a hard time with a problem in C#.

In the project I'm working on, there are some consumable classes which store information and there are consumer classes which uses those consumable classes.

I've replicated the thing in a simpler way like this:

using System;
using System.Collections.Generic;

interface IBase
{
    int x {get;}
}

class BaseClass : IBase
{
    public int x {get; set;}
}

interface IDerived : IBase
{
    int y {get;}
}

class DerivedClass : BaseClass, IDerived
{
    public int y {get; set;}
}

interface IConsumer<in T> where T: class, IBase
{
    void Feed(T arg);
}

class BaseConsumer : IConsumer<IBase>
{
    public void Feed(IBase arg)
    {
        Console.WriteLine(arg.x);
    }
}

class DerivedConsumer : IConsumer<IDerived>
{
    public void Feed(IDerived arg)
    {
        Console.WriteLine(arg.y);       
    }
}

public class Program
{
    public static void Main()
    {
        List<IConsumer<IDerived>> consumers = new List<IConsumer<IDerived>>();
        consumers.Add(new BaseConsumer());
        consumers.Add(new DerivedConsumer());

        DerivedClass d = new DerivedClass() { x = 3, y = 5};

        foreach (IConsumer<IDerived> consumer in consumers)
            consumer.Feed(d);
    }
}

This works completely fine. However, I want to have some middle consumers too, which takes another consumer object in its constructor (or another method), which redirects the Feed call to the objects. For example:

class MiddleConsumer<T> : IConsumer<T> where T : class, IBase
{
    private IConsumer<T> _consumer;
    public MiddleConsumer(IConsumer<T> consumer)
    {
        _consumer = consumer;
    }

    public void Feed(T arg)
    {
        _consumer.Feed(arg);
    }
}

And I'd use it like this:

consumers.Add(new MiddleConsumer<IBase>(new BaseConsumer());
consumers.Add(new MiddleConsumer<IDerived>(new DerivedConsumer());

But I'll be using MiddleConsumer with other concrete Consumer classes, such as BaseConsumer and DerivedConsumer. Therefore I shouldn't have to specify the T parameter explicitly, it can just get it from the actual Consumers type. Let me clarify. I want to have something like this: (note this isn't valid C# code)

class MiddleConsumer<IConsumer<T>> : IConsumer<T>
{
    private IConsumer<T> _consumer;
    public MiddleConsumer(IConsumer<T> consumer)
    {
        _consumer = consumer;
    }

    public void Feed(IDerived arg)
    {
        _consumer.Feed(arg);
    }
}

However, -as expected- this doesn't work due to having that weird generic parameter.

I want to be able to create a MiddleConsumer object with a Consumer which I don't know its base type. I mean, I want to be able to do this:

consumers.Add(new MiddleConsumer<BaseConsumer>(new BaseConsumer());
consumers.Add(new MiddleConsumer<DerivedConsumer>(new DerivedConsumer());

I don't want to check the base type of -for example- BaseConsumer every time I want to use it with MiddleConsumer, since the compiler can just look at it when I use a Consumer with MiddleConsumer. Notice that there could be dozens of different Consumer classes.

My question is: is there a way to make C# compiler infer the consumable type for MiddleConsumer from its generic parameter?

Thank you for your help!


Solution

  • C# doesn't support type inference on constructors, but what you can do is delegate calls to the constructors through a static method:

    public static class MiddleConsumer
    {
        public static MiddleConsumer<T> Create<T>( IConsumer<T> consumer ) where T : class, IBase
        {
            return new MiddleConsumer<T>( consumer );
        }
    }
    

    Now you shouldn't need to specify a generic argument at all:

    consumers.Add(MiddleConsumer.Create(new BaseConsumer());