How to use generic type parameters for interface contravariance

Viewed 41

I have something that looks like the following scenario:

interface IExample<T>
{
  public IExample<T> First();
  public IExample<T2> Second<T2>();
}

class Foo<T> : IExample<T>
{
  public Foo() {}
  public IExample<T> First() => new Foo<T>() {};
  public IExample<T2> Second<T2>() => new Foo<T2>() {};
  public Foo<T> Third() => new Foo<T>() {};
}

public static class Main {
  public static void Run() {
    var bar = (new Foo<string>()).First().Third();
  }
}

This does not work, because First() returns an IExample<T>, which does not have Third().

So in order to make it return itself, I have tried these two things:

interface IExample<T, out ExampleDerivedType>
{
  public ExampleDerivedType First();

  // Doesn't work because it expects Foo<T> instead of Foo<T2>
  public ExampleDerivedType Second<T2>();
}

class Foo<T> : IExample<T, Foo<T>>
interface IExample<T, out ExampleDerivedType>
// // Infinite recursion?
// where ExampleDerivedType : IExample<T, ExampleDerivedType> 

// // Does this kind of constraint even exist?
// where ExampleDerivedType is generic with one argument
{
  public ExampleDerivedType<T> First();

  public ExampleDerivedType<T2> Second<T2>();
}

class Foo<T> : IExample<T, Foo>

I know I can explicitly redefine the methods in this manner:

class Foo<T> : IExample<T>
{
  public Foo() {}
  IExample<T> IExample<T>.First() => First();
  public Foo<T> First() => new Foo<T>() {};

  IExample<T2> IExample<T>.Second<T2>() => Second<T2>();
  public Foo<T2> Second<T2>() => new Foo<T2>() {};

  public Foo<T> Third() => new Foo<T>() {};
}

But the solution I'm aiming for above is more appealing. I'm wondering whether it is

  • Possible
  • Possible in theory, but C# can not do it without help from either reflection or roslyn generators.
  • Impossible, because it is logically flawed and the compiler lacks the information it needs.

Right now, I'm guessing it's the middle one, but I'd be happy to be proven wrong.

1 Answers

As you said:

First() returns an IExample<T>, which does not have Third().*

So compiler does not know about existence Third(). In my view, it is possible to give a clue to compiler about that there is a method Third() by casting explicitly to Foo<T> as IExample<T> does not have Third():

var bar = (Foo<string>)new Foo<string>().First();
var test = bar.Third();
Related