-1

I can't seem to wrap my head around how nullability works in C# 8. In the below IF expression it is clear that myType is not null. Still I am getting a compiler error saying it might be MyType OR null instead of MyType.

public Method(MyType mytpe)
{
    System.Console.WriteLine("Meh");
}

public class AwesomeType
{
    public MyType? MyType { get; set; }
}

//...
var awesome = new AwesomeType();
var myType = awesome.MyType;
if (myType != null)
{
    Method(myType); // CS1503 Cannot convert MyType? to MyType
}

The following works fine

var awesome = new AwesomeType();
var myType = awesome.MyType;
if (myType != null)
{
    Method(myType.Value);
}

I thought the first approach ought to work because the compiler can infert non-nullability within the true block of the if condition. Why does the compiler still complain?

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
baouss
  • 1,312
  • 1
  • 22
  • 52
  • Can you please add definition for `MyType`? – Guru Stron May 14 '21 at 23:22
  • `The following works fine` And that is what you need to do. The compiler does not do that for you (unlike say some JS / TS compilers). As an example of why the compiler doesn't allow it, put a breakpoint on `Method(myType);`. When you hit the breakpoint set `myType` to `null`. The nature of the type system / language thus pretty much requires you to be explicit. – mjwills May 14 '21 at 23:25
  • 1
    @baouss Your tag says `nullable reference type` but your code sample appears to be for a nullable struct. Which is it? – mjwills May 14 '21 at 23:33
  • 1
    This code seems has nothing to do with nullable reference types from C# 8, but is using nullable value types which were introduced earlier. Why - because this is how the language is designed. There is [no implicit conversion](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/conversions#implicit-nullable-conversions) from nullable value type to value type. – Guru Stron May 14 '21 at 23:36
  • @baouss I'd suggest that https://stackoverflow.com/a/47423583/34092 may be a good starting point (if you are interested in nullable structs). – mjwills May 14 '21 at 23:37
  • @Sergey The OP knows how it can be fixed `The following works fine`. – mjwills May 14 '21 at 23:51
  • Given the expression `myType.Value`, it's apparent you are, contrary to the tag you added to your question, **not** dealing with nullable reference types but instead nullable value types. See duplicate for how to deal with converting nullable value types to non-nullable value types. – Peter Duniho May 15 '21 at 00:09
  • Thanks for all your answers. Indeed I should not have used the NRT tag. When writing the question, I tried to generalize and this make it easier, but in fact achieved the contrary. I should have just used a value type like bool as in the answer provided by DekuDesu. Thank you for offering your thoughts, solutions, time and links to further information! Much appreciated. – baouss May 15 '21 at 09:07

1 Answers1

3

Why does the compiler still complain?

This has to do with how nullable objects work in C#. The ? operator denoting that an object can be null basically tells the compiler 'Hey compiler - this object might be null, and that's okay.'

What this does for you, for as an example with bool? is allow you to denote a state that is not normally intended.

Normally a bool can only be true and false and if it was never initialized it could be null. If the bool was never initialized accessing it's value would throw an NullReferenceException.

Meanwhile bool?(denoting nullable bool) says to the compiler 'if the bool is null, treat null as it's value, instead of throwing an exception and complaining it doesn't have a value.'(in a simplified sense)

The trick with using nullable types, is that they are not implicitly treated like their non-nullable types. They're basically completely different objects, until you say so.

When you think about it it makes a lot of sense. It would be pretty weird if you could set a normal bool, like bool isLightOn = true; and set it to isLightOn = null;, what would that even mean? Does the light turn off, no that's what false would be.., would it flash the light on and off? The compiler has no clue what you intend to happen when you set a variable to a value that variable doesn't normally use(like null). That's a good thing! It would cause all sorts of weird issues.

This leads into the issue you are having.

bool? isLightOn = false;

// compiler checks to see if a bool? can have a null state
// it determines that bool? can be true, false, or null
// it says this line is okay
if(isLightOn != null)
{
    // compiler says the variable isn't null, 
    // but it still CAN be null, for example, if something within this code block changes it's value,
    //  or if that variable is accessed by another thread
    // becuase of this the compiler will not consider this light 
    // as either true, false(a normal bool) it says 'there's still
    // a possibility that by the time it's used it may not be 
    // either true, or false, therefor it should be considered as
    //  being maybe null in my eyes(the compiler).'

    // compiler checks to see if the object you're passing as a parameter is a valid object
    // compiler sees that your object can be true, false, or null
    // compiler says 'if i give this method this object without asking the programmer if it's okay, it may break the program becuase the method doesn't know how to deal with null values' 
    TestLight(isLightOn);

    // compiler checks to see if the object you're passing as a parameter is a valid object
    // compiler sees that the programmer explicitly told it to ignore the null value and treat is as if it were false, and allows you to pass that variable as parameter
    TestLight((bool)isLightOn);
}
void TestLight(bool lightOn)
{
}

When you check a nullable object if it's null, that just checking it's value. Since that's a valid state for the object to be in, the compiler goes about it's day and doesn't complain.

When you try actually use that value for something that uses a non-nullable object as a parameter, we run into that issue of, how is the compiler supposed to know what you want to do if the null object state isn't a valid state for that object?

So it treats the nullable and non-nullable versions of your object as completely different types, BUT with an explicit cast between the two.

The reason for this can be boiled down to you're trying to use a variable in a way that would lose information, or a lossy operation, if you will.

The compiler wont make an asumption that you want to ignore a valid value for the nullable object.

It forces you to say 'hey compiler, treat this nullable object like a non-nullable object, if it's value isn't expected, change it to an expected value(default). For bool? if the valid value was null and you said 'treat it like a non-nullable bool' (by using explicit casting (bool)bool?, the compiler would say 'Okay! null is basically default so that's it's new value when you pass it as a parameter'.

DekuDesu
  • 2,224
  • 1
  • 5
  • 19
  • 3
    While impressively long, I don't see how any of the above addresses the question asked, which was "why is converting the nullable value type required, even though the value is known to be non-null at that point". After all, the compiler _could_ have included the same kind of static analysis for nullable value types that it now includes for nullable reference types and implicitly cast away the nullable aspect instead of requiring it to be explicit. And nothing in the post above explains why the compiler _doesn't_ do that. – Peter Duniho May 15 '21 at 00:25
  • 1
    You're totally right, there is nothing stopping the compiler from implicitly casting and avoiding the use of explicit casts. However, when C# 8.0 was implemented the compiler was explicitly designed that no behavior is changed implicitly, and always prefers to lead to the side of caution when any assignment, or use of a nullable type would be lossy. At least according to [John Skeet](https://livebook.manning.com/book/c-sharp-in-depth-fourth-edition/chapter-15/53), i probably just didn't convey it as best as possible. – DekuDesu May 15 '21 at 00:42