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

5.1k views Asked by At
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?
2

There are 2 answers

3
Eric Lippert On BEST ANSWER

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.

1
Nuffin On

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
}