Null or Default Comparison of Generic Argument in C#

Null or default comparison of generic argument in C#

To avoid boxing, the best way to compare generics for equality is with EqualityComparer<T>.Default. This respects IEquatable<T> (without boxing) as well as object.Equals, and handles all the Nullable<T> "lifted" nuances. Hence:

if(EqualityComparer<T>.Default.Equals(obj, default(T))) {
return obj;
}

This will match:

  • null for classes
  • null (empty) for Nullable<T>
  • zero/false/etc for other structs

Comparison of a generic type with its default value, without a generic class constraint, gives a compile time error

You cannot assume that every value type overrides the == operator. (And even if they did, there would be no way to call it using generics; it's a static method)

Instead, you should write

    return !(ReferenceEquals((object)result, (object)default(TDataSource)) 
|| result.Equals(default(TDataSource)));

If result is null (and a reference type), the ReferenceEquals call will return true, so Equals won't be called and won't throw a NullReferenceException.

If TDataSource is a value type, the ReferenceEquals will compare two different boxed references (which may happen to contain the same value, but will still be different), so it will pass on to the Equals call.

How to check if generic function is called with nullable generic argument

Let me explain why your approach is not possible for reference types at all. (at least until c# 8. this behavior might change in future c# versions)

Because reference types are always nullable at runtime, there is not difference in the Type itself.

this is probably why you can't do: typeof(string?)
You'll get an error like: "typeof cannot be used for nullable reference types"

But that doesn't neccessarily mean that the compile doesn't add a distinguishable attribute or something similar. (which indeed could be)

But we can say, if GetType() of both type of strings will result in the same type instance, this canno be a different type at all.

Check it yourself with the following Code.

    string nonNullableString = "3434";
string? nullableString = "3434";

var nnStringType = nonNullableString.GetType();
var stringType = nullableString.GetType();

var isEqual = nnStringType == stringType; // true
if(object.ReferenceEquals(nnStringType, stringType)) // true
Debug.Print("it's the same object");

Nullable and non nullable string are of the same type.

How to check if a generic parameter is not null without boxing

I came up with this helper:

public static class Null
{
public static bool IsNull<T>(T obj) => !Data<T>.HasValue(obj);

private static class Data<T>
{
public static readonly Func<T, bool> HasValue = InitHasValue();

private static Func<T, bool> InitHasValue()
{
var type = typeof(T);
if (!type.IsValueType)
return obj => obj != null;

if (type.IsGenericType &&
type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
var value = Expression.Parameter(type, "value");
var getter = type.GetProperty("HasValue").GetMethod;
var call = Expression.Call(value, getter);
var lambda = Expression.Lambda<Func<T, bool>>(call, value);
return lambda.Compile();
}

return obj => true;
}
}
}

So I can do this:

void Foo<T>(T obj)
where T : IBar
{
if (!Null.IsNull(obj)) // Does not box.
obj.Baz();
}

Default value check using generic types


public bool EqualsDefaultValue<T>(T value)
{
return EqualityComparer<T>.Default.Equals(value, default(T));
}

C#: default of generic T? is not null; behavior changes with generic constraint

If you want to use Nullable<int> you shouldn't use int, so use:

Gen<int?>.Write("int?");

Then the output will be

Default of int? is null
Default of string? is null


The code in the question is an example. The real class does not have a
Write method and never uses the string of the type. However as I
indicated by 'other stuff' it uses T as well as T?. So it is not
desired to instantiate it with int? instead of int.

First i want to explain why it's not odd that the struct constraint in the generic class changes the behavior. Because actually that constraint makes it compile if you are < C#8. Then T? means Nullable<T>, so if you use Gen<int>.Write("int?") the field t will be a Nullable<int>. But then Gen<string>.Write("string") won't compile at all since string is not a struct. So it has a completely different meaning with the constraint.

With C#8 enabled you can remove the struct constrained, then t remains an int and the string will be a nullable string. So the question mark has the meaning: in case of a reference type it's a nullable reference type, otherwise it's just what it is.

You can't have both, a generic type that can be a nullable reference type or a nullable value type without using the desired generic type, so use int? if it must be nullable.

What is the behaviour of the '==' operator for a generic type value and the 'default' keyword?

In the context of == where the operand type is a generic parameter, value == default seems to emit equivalent IL to value == null, which always evaluates to false for a non-nullable value type operand.

Given:

static bool IsDefault<T>(T value) => value == default;
static bool IsNull<T>(T value) => value == null;

We get the IL:

.method private hidebysig static 
bool IsDefault<T> (
!!T 'value'
) cil managed
{
// Method begins at RVA 0x2050
// Code size 10 (0xa)
.maxstack 8

IL_0000: ldarg.0
IL_0001: box !!T
IL_0006: ldnull
IL_0007: ceq
IL_0009: ret
} // end of method C::IsDefault

.method private hidebysig static
bool IsNull<T> (
!!T 'value'
) cil managed
{
// Method begins at RVA 0x205b
// Code size 10 (0xa)
.maxstack 8

IL_0000: ldarg.0
IL_0001: box !!T
IL_0006: ldnull
IL_0007: ceq
IL_0009: ret
} // end of method C::IsNull

You could be forgiven for finding this surprising. It means, for example, that when T is bound to a non-nullable value type like int, the expression value == default evaluates to false for a value of 0. This contrasts with the inlined expression 0 == default, which evaluates to true.

Console.WriteLine(IsDefault<int>(0));     // False
Console.WriteLine(IsNull<int>(0)); // False
Console.WriteLine(IsDefault<int?>(null)); // True
Console.WriteLine(IsNull<int?>(null)); // True
Console.WriteLine(IsDefault<int?>(0)); // False
Console.WriteLine(IsNull<int?>(0)); // False

So, clearly, for a value of an unconstrained generic parameter type, the expressions value == default and value == default(T) are not equivalent. If legal, the latter would (presumably) evaluate to true if the value were null, false, or a "zeroed-out" value type (e.g., a value type where all constituent values are also defaults).

As to why value == default(T) does not compile, the answer is simple: the compiler does not know how to evaluate == for a type that is not known at compile time. If you were to add the constraint where T : class, then the compiler could at least perform a reference comparison. But as long as T could be a primitive type, custom value type, or reference type, the compiler doesn't know how to emit the comparison. The correct implementation for a given instantiation of T might be a built-in primitive comparison, an op_Equality overload, or a reference comparison. More importantly, it may not be a supported operator at all. It's that last possibility that really poses a problem.

While the C#/.NET engineers could have come up with a way to defer figuring out the correct comparison until runtime, that would also mean trading a compile-time error for a runtime exception in cases where the operator is simply not applicable, and I don't find that trade very appealing

Comparing a generic against null that could be a value or reference type?


I'm purposely only checking against null because I don't want to restrict a ValueType from being equal to its default(T)

That is a good insight, but don't worry, you are already covered there. It is not legal to compare a T against default(T) using == in the first place; overload resolution will not find a unique best == operator.

Of course, you could do the comparison with .Equals but then you run the risk of crashing if the receiver is null, which is precisely what you are attempting to avoid.

Is there a more standard way to handle this situation?

No. Comparing to null is the right thing to do here.

As the C# specification says in section 7.10.6: "The x == null construct is permitted even though T could represent a value type, and the result is simply defined to be false when T is a value type."

Is there any chance of an issue arrising from this?

Sure. Just because code compiles does not mean that it has the semantics you intend. Write some tests.

What truly happens under the hood when I make a call and pass in a value type?

The question is ambiguous. Let me rephrase it into two questions:

What truly happens under the hood when I make a call on the generic method with a type argument that is a non-nullable value type?

The jitter compiles the method on the first invocation with that construction. When the jitter detects the null check, it replaces it with "false" because it knows that no non-nullable value type will ever be equal to null.

What truly happens under the hood when I make a call on the generic method with a type argument that is a reference type but an argument that is a struct type? For example:

interface IFoo : ISomeInterface<IFoo> {}
struct SFoo : IFoo { whatever }
...
DoFooInternal<IFoo>(new SFoo());

In that case the jitter cannot elide the null check and the call site cannot avoid the boxing. The SFoo instance will be boxed, and the reference to the boxed SFoo will be checked to see if it is null.

generic way to check null or empty for any type like int, string, double


public static bool CheckNullOrEmpty<T>(T value)
{
if (typeof(T) == typeof(string))
return string.IsNullOrEmpty(value as string);

return value == null || value.Equals(default(T));
}

How to use:

class Stub { }

bool f1 = CheckNullOrEmpty(""); //true
bool f2 = CheckNullOrEmpty<string>(null); //true
bool f3 = CheckNullOrEmpty(0); //true
bool f4 = CheckNullOrEmpty<Stub>(null); //true

Default value for generics


Question 1:

Dim variable As T
' or '
Dim variable As T = Nothing
' or '
Dim variable As New T()

Notice that the latter only works if you specify the Structure constraint for the generic type (for reference types, New T() in VB does something else than default(T) in C#).

Question 2:

For value types all members of the struct are “nulled” out, i.e. all reference type members are set to null (Nothing) and all value types are in turn nulled out.

And no, since string is a reference type, it does not result in "" for strings as suggested in the other answer.

Question 3:

No, there's no way to specify this. There are some threads about this on Stack Overflow already, e.g. here. Jon has posted an excellent explanation why this is.



Related Topics



Leave a reply



Submit