Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

public void DoFoo<T>(T foo) where T : ISomeInterface<T> {     //possible compare of value type with 'null'.     if (foo == null) throw new ArgumentNullException("foo"); } 

I'm purposely only checking against null because I don't want to restrict a ValueType from being equal to its default(T). My code compiles and works just fine this way (ReSharper complains, but not CodeAnalysis). Though I do wonder:

  • Is there a more standard way to handle this situation?
  • Is there any chance of an issue arrising from this?
  • What truly happens under the hood when I make a call and pass in a value type?
like image 682
michael Avatar asked Jan 11 '12 16:01

michael


People also ask

Can a generic type be null?

The definition of a generic type should never be nullable. The nullability should be defined by the one using this generic. With the current Kotlin version, you can have a "String?" implementation and a "String" implementation.

Is nullable int a value type?

Nullable types are neither value types nor reference types.

How to null value in C#?

In C#, you can assign the null value to any reference variable. The null value simply means that the variable does not refer to an object in memory. You can use it like this: Circle c = new Circle(42); Circle copy = null; // Initialized ... if (copy == null) { copy = c; // copy and c refer to the same object ... }

Is a non nullable value type?

Nullable variables may either contain a valid value or they may not — in the latter case they are considered to be nil . Non-nullable variables must always contain a value and cannot be nil . In Oxygene (as in C# and Java), the default nullability of a variable is determined by its type.


2 Answers

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.

like image 189
Eric Lippert Avatar answered Oct 04 '22 08:10

Eric Lippert


No, there won't be any problems, but if you want the warning to disappear, you can use the following:

public void DoFoo<T>(T foo) where T : ISomeInterface<T> {     if (ReferenceEquals(foo, null)) throw new ArgumentNullException("foo"); } 

Alternatively you can do something like this:

// when calling this with an actual T parameter, you have to either specify the type // explicitly or cast the parameter to T?. public void DoFoo<T>(T? foo) where T : struct, ISomeInterface<T> {     if (foo == null)     {         // throw...     }      DoFooInternal(foo.Value); }  public void DoFoo<T>(T foo) where T : class, ISomeInterface<T> {     if (foo == null)     {         // throw...     }      DoFooInternal(foo);  }  private void DoFooInternal<T>(T foo) where T : ISomeInterface<T> {     // actual implementation } 
like image 42
Nuffin Avatar answered Oct 04 '22 07:10

Nuffin