Search code examples
c#genericsnullconstraintsnullable

Rules of Unbounded type parameters in Generics ( C# )


I am unable to understand below points about unbounded type parameters.

Type parameters that have no constraints, such as T in public class SampleClass{}, are called unbounded type parameters.

Unbounded type parameters have the following rules: You can compare to null. If an unbounded parameter is compared to null, the comparison will always return false if the type argument is a value type.

I did not find any example of above points. It will be great if somebody give me example to understand the points.


Solution

  • Both of these functions result in the same IL;

    bool Test1<T>(T val) => val == null;
    bool Test2<T>(T val) where T:class => val == null;
    
    IL_0000: ldarg.0
    IL_0001: box !!T
    IL_0006: ldnull
    IL_0007: ceq
    IL_0009: ret
    

    If the caller passes in a value type parameter;

    Test1<int>(0) == false;
    

    Then the integer argument will be boxed in an object, which will always result in a non-null reference. So comparing to null will always be false.

    Though, if the caller passes in a Nullable<T> parameter. Even though this is a value type, boxing the value will result in a null.

    Test1<int?>(null) == true;
    

    Boxing the value this way is an implementation detail, which might change in future versions. So the language reference you are quoting instead talks about the side effect of this implementation, rather than the cause.

    For completeness, there's more that can be said about generic functions and equality.

    Passing in an explicit Nullable<T> argument is also fine, and results in the same IL

    bool Test3<T>(T? val) where T:struct => val == null;
    bool Test4<T>(T? val) where T:struct => !val.HasValue;
    
    IL_0000: ldarga.s val
    IL_0002: call instance bool valuetype [System.Runtime]System.Nullable`1<!!T>::get_HasValue()
    IL_0007: ldc.i4.0
    IL_0008: ceq
    IL_000a: ret
    

    But there is no default == operator for value types;

    bool Test5<T>(T val) where T:struct => val == null;
    bool Test6<T>(T val) where T:struct => val == default(T);
    
    error CS0019: Operator '==' cannot be applied to operands of type 'T' and '<null>'
    error CS0019: Operator '==' cannot be applied to operands of type 'T' and 'T'
    

    Instead you can use the default comparer;

    public bool Test7<T>(T val) => EqualityComparer<T>.Default.Equals(val, default(T));
    

    Since .net 7 you can add a constraint which is implemented by .net numeric types;

    public bool Test8<T>(T val) where T : IEqualityOperators<T,T,bool> => val == default(T);