Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

interface generic hierarchy

I have some generic interface linked one to each other.

public interface IA
{
    int val { get; set; }
}
public interface IB<T> where T:IA
{
    T a_val { get; set; }
}
public interface IC<T> where T : IB<IA>
{
    T b_val { get; set; }
}

public class a:IA
{
    public int val { get; set; }
}
public class b:IB<a>
{
    public a a_val { get; set; }
}
public class c:IC<b>
{
    public b b_val { get; set; }
}

For the last class c, i have an error:

The type 'b' cannot be used as type parameter 'T' in the generic type or method 'IC'. There is no implicit reference conversion from 'b' to 'IB'.

How can I properly use generic interfaces in this case ?

like image 456
Alex Troto Avatar asked Dec 27 '22 08:12

Alex Troto


2 Answers

T in IC<T> is required to be an IB<IA>. You have given it an IB<A>. You have no guarantee that an IB<A> can be used as an IB<IA> just because A implements IA.

Think about it this way: if IB<T> means "I can eat anything of type T", and IA means "I am a fruit", and A means "apple", then IB<IA> means "I can eat any fruit", and IB<A> means "I can eat any apple". If some code wants to feed you bananas and grapes then it needs to take an IB<IA>, not an IB<A>.

Let's suppose IB<A> could be converted to IB<IA> and see what goes wrong:

class AppleEater : IB<Apple> 
{  
    public Apple a_val { get; set; }
}
class Apple : IA 
{
    public int val { get; set; }
}
class Orange : IA 
{
    public int val { get; set; }
}
...
IB<Apple> iba = new AppleEater();
IB<IA> ibia = iba; // Suppose this were legal. 
ibia.a_val = new Orange(); // ibia.a_val is of type IA and Orange implements IA

And now we just set iba.val, which is a property of type Apple to a reference to an object of type Orange.

That's why the conversion has to be illegal.

So how can you make this legal?

As the code stands, you cannot, because as I've just shown, it isn't typesafe.

You can make it legal by marking T as out like this: interface IB<out T>. However, it is then illegal to use T in any "input context". In particular, you cannot have any property of type T that has a setter. If we make that restriction then the problem disappears because a_val cannot be set to an instance of Orange because it is read-only.

This question is asked extremely frequently on SO. Look for questions about "covariance and contravariance" in C# for plenty of examples.

like image 64
Eric Lippert Avatar answered Jan 09 '23 13:01

Eric Lippert


I don't know if it can be done easier (and a bit more clean), but this code compiles:

public interface IA
{
    int val { get; set; }
}
public interface IB<T> where T : IA
{
    T a_val { get; set; }
}
public interface IC<T, U> where T : IB<U> where U : IA
{
    T b_val { get; set; }
}

public class a : IA
{
    public int val { get; set; }
}
public class b : IB<a>
{
    public a a_val { get; set; }
}
public class c : IC<b, a>
{
    public b b_val { get; set; }
}

What's even more important, it don't let you do something like that:

public class a1 : IA
{
    public int val { get; set; }
}

public class c : IC<b, a1>
{
    public b b_val { get; set; }
}

Compiler throws following error:

The type 'ConsoleApplication2.b' cannot be used as type parameter 'T' in the generic type or method 'ConsoleApplication2.IC'. There is no implicit reference conversion from 'ConsoleApplication2.b' to 'ConsoleApplication2.IB'.

And that is really cool feature, isn't it?

like image 31
MarcinJuraszek Avatar answered Jan 09 '23 12:01

MarcinJuraszek