Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is class constraint not detected for generic class using an inherited type?

Phrasing the question was hard, I hope the following code snippet makes things clear:

public class DemoClass<TBase> where TBase : class
{
    public void DemoMethod<T>(T target) where T : TBase
    {
        //The following line causes a design-time error: Type argument 'T' does not satisfy the 'Class' constraint for type parameter 'T'.
        WeakReference<T> demoRef = new WeakReference<T>(target);
    }
}

The WeakReference requires a type, T, that satisfies a class constraint. So far, so good, but...

Why can the compiler not detect that T actually does, because (practically) T : TBase : class?

like image 495
mike Avatar asked Jul 04 '17 22:07

mike


People also ask

Can generic classes be inherited?

You cannot inherit a generic type. // class Derived20 : T {}// NO!

Can a generic class have multiple constraints?

Multiple interface constraints can be specified. The constraining interface can also be generic.

What are the constraints in generics?

The where clause in a generic definition specifies constraints on the types that are used as arguments for type parameters in a generic type, method, delegate, or local function. Constraints can specify interfaces, base classes, or require a generic type to be a reference, value, or unmanaged type.

How do you add a generic constraint?

You can specify one or more constraints on the generic type using the where clause after the generic type name. The following example demonstrates a generic class with a constraint to reference types when instantiating the generic class.


2 Answers

Why can the compiler not detect that T actually does, because (practically) T : TBase : class?

Because that is simply not true. On top of what Poke points out in his answer, this is also illegal due to the fact that all value types inherit from object:

 var dc = new DemoClass<object>();
 dc.DemoMethod(1); //woops, just attempted to create a WeakReference<int>

Your reasoning simply falls apart when value types are involved. Contrived? Yes, but perfectly legal so the compiler doesn't have a choice and has to consider your code illegal.

UPDATE

Addressing Jon Hana's comment below that in the code above T is not really int, its object and 1 is boxed implicitly, that is absolutely not true. Consider the following variation of DemoMethod:

public T DemoMethod<T>(T target) where T : TBase
{
    return target;
}

And the following code:

var dc = new DemoClass<object>();
var i = dc.DemoMethod(1);

i is int, its not object. Moreover, the following will execute correctly:

long i = dc.DemoMethod(1);

Which also proves the T can not be a boxed int because the implicit conversion would fail at runtime; you can't unbox a value type to anything but the type itself.

And of course, you can always set T explicitly, which also compiles just fine:

dc.DemoMethod<int>(1);
like image 192
InBetween Avatar answered Nov 03 '22 00:11

InBetween


Let’s check the documentation on what T : class actually means:

where T : class

The type argument must be a reference type; this applies also to any class, interface, delegate, or array type.

Unfortunately, this is already satisfied if T is for example an interface. So you can construct a simple example where you can see that applying T : class transitively will not work:

public interface ITest { }
public struct Test : ITest { }

If you now create a DemoClass<ITest>, you are satisfying the type constraint since ITest is a “class” here. But when you call the method DemoMethod<Test>, then you do not have a reference type for T although Test does inherit ITest.

In general, those special generic type constraints do not follow the rules of inheritance. That is why they are defined separately and are not already established by the type system. They are there as a special syntax because the type system is not able to express the constaints otherwise.

like image 4
poke Avatar answered Nov 02 '22 22:11

poke