2

I have this:

var newFoo = new Foo<Car>();

DoSomething(newFoo);


void DoSomething(Foo inputFoo)
{
    if (inputFoo is Foo<IDrivable> f) Console.WriteLine("It is a Foo<IDrivable>.");
    else Console.WriteLine("Nope, not a Foo<IDrivable>.");
}

public abstract class Foo { }
public class Foo<T>:Foo { }
public interface IDrivable { }
public class Car : IDrivable { }

This prints "Nope, not a Foo<IDrivable>".

Is it possible to perform a contravariant check on T somehow? How do I refactor the above to print "It is a Foo<IDrivable>"given a Foo<Car>?

NWoodsman
  • 423
  • 4
  • 10

2 Answers2

3

You can check whether there is a generic parameter and whether (the first one) is assignable to IDrivable.

var firstTypeParameter = inputFoo.GetType().GenericTypeArguments.FirstOrDefault();
if (firstTypeParameter != null && firstTypeParameter.IsAssignableTo(typeof(IDrivable)))
    Console.WriteLine("It is a Foo<IDrivable>");

Edit:

As noted in a comment, a more elegant solution can be achieved if you're willing to introduce another interface:

public interface IFoo<out T> { }
public class Foo<T> : Foo, IFoo<T> { }

The check then becomes

if (inputFoo is IFoo<IDrivable> f) 
    Console.WriteLine("It is a Foo<IDrivable>.");

Regarding the comment:

Of course, using IFoo has the limitation that T can only be used in output positions within that interface.

That is accurate but also not relevant since the purpose of that interface is to make it less cumbersome to interpret metadata about Foo<T>. To understand that limitation, consider the following:

public interface IFoo<out T> 
{ 
    T ThisCompiles();
    // void ThisDoesNotCompile(T input);
}
public class Foo<T> : Foo, IFoo<T> 
{ 
    public T ThisCompiles() => default;
    public T ThisCompilesBecauseNotPartOfIFooInterface(T input) 
        => default;
}

The class can use T as an input or output parameter. Methods on IFoo<T> could only use T as an output parameter, but since no methods need be defined on that interface, that restriction is irrelevant.

Eric J.
  • 147,927
  • 63
  • 340
  • 553
  • Of course, using `IFoo` has the limitation that `T` can only be used in output positions within that interface. – Johnathan Barclay Jan 31 '23 at 08:43
  • I don't think that's relevant in this case, but still a good point to call out. I updated my answer to reflect your input. – Eric J. Jan 31 '23 at 21:01
0
var newFoo = new Foo<Car>();

DoSomething(newFoo);


void DoSomething(Foo inputFoo)
{
    if (inputFoo.GetType().GetGenericTypeDefinition() == typeof(Foo<>) && typeof(IDrivable).IsAssignableFrom(inputFoo.GetType().GetGenericArguments()[0])) Console.WriteLine("It is a Foo<IDrivable>.");
    else Console.WriteLine("Nope, not a Foo<IDrivable>.");
}

public abstract class Foo { }
public class Foo<T>:Foo { }
public interface IDrivable { }
public class Car : IDrivable { }
hatcyl
  • 2,190
  • 2
  • 21
  • 24