1

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.

9
  • What about it do you not understand? Commented Feb 26, 2024 at 2:18
  • Try it yourself bool Test<T>(T val) => val == null;. Commented Feb 26, 2024 at 2:21
  • FYI the technical reason, is that you are calling the (object) == (object) operator. Any value type will be boxed, which would always result in a non-null reference. Commented Feb 26, 2024 at 2:35
  • @JeremyLakeman if T=Int And Val=0 then return False ---___---- if T=String and Val="" then return false Too ............ Microsoft : the comparison will always return false if the type argument is a value type. sring is not value type Commented Feb 26, 2024 at 2:36
  • The generated IL is ldarg.0, box !!T, ldnull, ceq, the box operation will do nothing to an existing reference; sharplab.io/… null is not exactly the same as default(T). Unbound types don't have an == operator, so you have to use EqualityComparer<T>.Default.Equals() to compare against default(T). The caller gets to specify the type argument, and if the type argument is a value. eg Test<int>(0); Commented Feb 26, 2024 at 2:40

1 Answer 1

1

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);
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.