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
?
You cannot inherit a generic type. // class Derived20 : T {}// NO!
Multiple interface constraints can be specified. The constraining interface can also be generic.
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.
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.
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);
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With