Generic type inference with multi-implemented covariant interfaces, how to work around this problem?

advertisements

Consider this silly program that does nothing:

interface I<out T> { }
class A1 : I<A1> { }
class A2 : A1, I<A2> { }
class B1 { }
class B2 : B1, I<B2> { }
class C1 : I<A1> { }
class C2 : C1, I<A2> { }

static class Program
{
    static void f<T>(I<T> obj)
    {
    }

    static void Main()
    {
        f<A1>(new A2());
        f<A2>(new A2());
        f<B1>(new B2());
        f<B2>(new B2());
        f<A1>(new C2());
        f<A2>(new C2());
    }
}

This shows that A2 and C2 implement both I<A1> and I<A2>, and that B2 implements both I<B1> and I<B2>.

However, modifying this to

static void Main()
{
    f(new A2());
    f(new B2());
    f(new C2());
}

shows that on the first and third lines, f's generic type argument cannot be inferred from the passed argument, yet on the second line, it can be.

I understand what it is that the compiler is doing here, so that doesn't need explaining. But how can I work around this? Is there some way to modify this so that I can define the interface on both the base and the derived class, yet have type inference work when passing the derived class?

What I had in mind was to look for a way to "hide" a base class's implemented interfaces, so that the compiler doesn't see them and use them, even though they do exist. However, C# doesn't seem to provide an option to do so.

Clarification: in my silly example program, A1 implements I with itself as the generic type argument. I do have that in my real code, but I also have classes that implement I with a different generic type argument, and have added C1 and C2 to my example code for that reason.


Using two F variants (second one just for type inference that calls the other) and an "override" interface J inheriting from I which does nothing it could be done like so:

using System;
using System.Threading;

interface I<out T>
{
    void Print();
}

interface J<out T> : I<T> { }

class A : I<C>
{
    void I<C>.Print()
    {
        Console.WriteLine("A: I<C>");
    }
}

class B {}

class C : B { }

class D1 : I<A>
{
    void I<A>.Print()
    {
        Console.WriteLine("D1: I<A>");
    }
}

class D2 : D1, J<B>
{
    void I<B>.Print()
    {
        Console.WriteLine("D2: I<B>");
    }
}

class D3 : D1, J<C>
{
    void I<C>.Print()
    {
        Console.WriteLine("D3: I<C>");
    }
}

class D4 : A, J<B>
{
    void I<B>.Print()
    {
        Console.WriteLine("D4: I<B>");
    }
}

static class Program
{
    static void f<T>(J<T> obj)
    {
        f((I<T>)obj);
    }

    static void f<T>(I<T> obj)
    {
        obj.Print();
    }

    static void Main()
    {

        f<A>(new D2());
        f(new D2());

        f(new D3());

        f(new D4());
        f<C>(new D4());

        Console.ReadKey();
    }
}

Output:

D1: I<A>
D2: I<B>
D3: I<C>
D4: I<B>
A: I<C>