8

While writing a class that contains a generic variable

public class ServiceInvoker<TService> : IDisposable
{
    private TService _Service;

    public ServiceInvoker()
    {
        _Service = Activator.CreateInstance<TService>();
    }

    public void Invoke(Action<TService> action)
    {
        // CAN use null
        if (_Service == null)
            throw new ObjectDisposedException("ServiceInvoker");

        ....
    }

    public void Dispose()
    {
        // CAN'T use null
        this._Service = default(TService);
    }
}

I noticed that the compiler allows me to check for null on a generic variable, but that, of course, don't allow me to set it to null, hence why we have to use default(TService).

Shouldn't the compiler warn me that I use null? Or is it using boxing conversion to an object to do a test for null here?

I read about the proper null evaluation for generic, but I'm interested in knowing why, instead of just how.

Community
  • 1
  • 1
Pierre-Alain Vigeant
  • 22,635
  • 8
  • 65
  • 101

4 Answers4

12

Why can I test a generic for null when it may not be nullable or may not be an object?

The question is ill-posed; the value of any expression of generic type will always be either an object or a null reference at runtime. I think you meant to ask:

Why can I test a generic for null when it its runtime type might be neither a nullable value type nor a reference type?

The C# specification guarantees that equality comparison against the literal null is legal in section 7.6.10, which I quote here for your convenience:


If an operand of a type parameter type T is compared to null, and the run-time type of T is a value type, the result of the comparison is false. [...] 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.


Note that there is a small error in the spec here; the last sentence should end "non-nullable value type."

I'm never sure if I've actually answered a "why?" question satisfactorily or not. If that's not satisfactory, try asking a more specific question.

Shouldn't the compiler warn me that I use null?

No. Why do you believe that the compiler should warn you for using a feature of the language correctly?

is it using boxing conversion to an object to do a test for null here?

Well, that's a bit of a tricky point. On the one hand, section 7.6.10 states:


The predefined reference type equality operators never cause boxing operations to occur for their operands. It would be meaningless to perform such boxing operations, since references to the newly allocated boxed instances would necessarily differ from all other references.


However, when we generate IL for the comparison of a generic T to null, we of course actually do generate a box of T, a load of null, and a comparison. The jitter can be smart enough to elide the actual memory allocation of the boxing; if T is a reference type then it needs no boxing, if it is a nullable value type then it could be turned into a call to check the HasValue property, and if it is a non-nullable value type then the boxing and check can be turned into simply generating "false". I do not know exactly what the various different jit compiler implementations actually do; if you're interested, check and see!

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Well, a why can be answered satisfactory by stating the spec like you did. A big thank you for taking the time to answer. Concerning the warning, I though that, yes, a warning could be useful, but it is not required. I was able to see that my code wasn't right by reading it, so a little review helped me fix a potential problem. After fixing it, I though that it was weird that I was able to compare to null, so I though I ask. – Pierre-Alain Vigeant Feb 22 '11 at 16:50
  • 1
    @Pierre-Alain: Happy to help. People often respond to "because the spec says so" with "but why does the spec say so?" And then I say "because the spec authors thought it was a good idea" And then you say "but *why* did they think it was a good idea?" and then I say "the benefits outweigh the costs" and then you say "what are all the costs and all the benefits and how do you compare them?" and that could take hours to explain, and raise even more questions. "Why" questions are hard! – Eric Lippert Feb 22 '11 at 17:04
  • 1
    Great conversation :O In the last question though, when you said "what are all the costs and all the benefits and how do you compare them?", are the costs and benefits all documented too? (As they only contribute to the end decision, so might not be kept on record.) But I assume it's based on what people who were involved at the time can recall, right? – Joan Venge Feb 22 '11 at 17:54
  • 2
    @Joan: We have extensive notes, many of which say "we considered the costs and benefits and decided..." but never document the costs and benefits. So it is then down to memory and educated guesses. – Eric Lippert Feb 22 '11 at 20:02
  • Thanks, it's good to know you guys are diligent with your notes. I am sure they prove to be useful times after times. – Joan Venge Feb 23 '11 at 00:12
  • I note that MonoDevelop's code analysis (and presumably ReSharper, judging for the results for the search for a more recommended approach that led me here) **does** give a warning here. Slightly annoying right now as the behaviour I want is precisely to detect when a type is both a reference type, and indeed null, but I suppose it's reasonable enough a warning for something intended precisely to offer warnings beyond what a compiler would. – Jon Hanna Jan 03 '14 at 21:33
  • Just leaving links here to some answers which go into more detail about the JIT optimisations: https://stackoverflow.com/a/48685902/397817 and https://stackoverflow.com/a/21345338/397817 – Stephen Kennedy Feb 08 '18 at 13:58
4

The CLR knows that a non-nullable value type can never be null, so it probably just always returns false. Is that what you're asking?

Gabe
  • 84,912
  • 12
  • 139
  • 238
  • Then it is a conditional `always return false`, because if the TService is a class, then it will work, but if, for some reason, TService is a struct, it will return false no matter what. That will obviously break the behaviour of my class depending on how it is used. – Pierre-Alain Vigeant Feb 22 '11 at 16:15
  • 2
    @Pierre, you can compare structs to nulls all day long. `bool worthless = (1 == null);` is perfectly legal. For your code, you could simply have an `isDisposed` member that you set when your object is disposed. You can check that member internally rather than comparing your `TService` object to the default (the default may very well be in a perfectly valid state). – Anthony Pegram Feb 22 '11 at 16:20
  • @Anthony Good idea about changing the check to null to a Boolean. I just did that. – Pierre-Alain Vigeant Feb 22 '11 at 16:52
1

This compiles, though with a warning:

int l = 0;
if (l == null) {
    //whatever
}

The warning is this:

 warning CS0472: The result of the expression is always 'false'
 since a value of type 'int' is never equal to 'null' of type 'int?'

So it works, though with not-very-useful results. With a generic, though, that could be a useful test.

Andrew Barber
  • 39,603
  • 20
  • 94
  • 123
  • But he asked specifically about generics, we all know that with an int type we always get that warning. – Aidiakapi Feb 22 '11 at 16:22
  • 1
    My point is that it is only a *warning* when used that way, not an *error*. If it was an error to do that comparison, then I would expect the comparison with a non-constrained generic to be a warning/error. But since it isn't, that seems to be a valid (though useful?) comparison. – Andrew Barber Feb 22 '11 at 16:34
  • The JIT compiler is smart enough to ignore null checks which are done on null-nulable value types. That's why it's perfectly legal to use it even if it's useless. With generics there might be a use-case because you don't know the type beforehand. – Tim Schmelter Feb 08 '18 at 13:12
1

Why do you think the compiler should warn you? The compiler will give warnings when you are using something like this:

if (1 == 2) //always false
if (Dog is Cat) //never true

But in this case, the condition is possible to be true(when TService is a refernece type and it's null), or false(when it's a value type, or a reference type but not null). It perfectly represents the usage of if statement(sometimes true, and sometimes false).

Cheng Chen
  • 42,509
  • 16
  • 113
  • 174