Object pattern matching results in uninitialized variable

96 Views Asked by At

Pattern matching to find whether a value is not null was done using the is keyword with the respective expected type (included a cast if necessary). The pattern !(obj is object instance) returns true if obj is not null and false if obj is null. At the same time if obj is not null it is captured in the variable instance.

[Fact]
public void Old_pattern_matching_for_non_null_object()
{
    var obj = new object();
    if (!(obj is object instance)) // false
        return;

    if (instance is null) // false
        throw new Exception("Never reached");

    // this does not throw an exception (test passes)
}

With C#8 a new way of writing this was introduced using { } to denote non-null (without casting) which "simplified" the code to

[Fact]
public void Csharp_8_pattern_matching_for_non_null_object()
{
    var obj = new object();
    if (!(obj is { } instance)) // false
        return;

    if (instance is null) // false
        throw new Exception("Never reached");

    // this does not throw an exception (test passes)
}

With C#9 the new keyword not was introduced which was intended to simplify patterns such as !(x is null) to x is not null. If I now try to apply the new not keyword to a pattern includig { } I get a quite surprising result:

[Fact]
public void Csharp_9_pattern_matching_for_non_null_object()
{
    var obj = new object();
    if (obj is not { } instance) // false
        return;

    if (instance is null) // true
        throw new Exception("Why the heck is this null");

    // this does throw an exception (test fails)
}

Even though that obj is not null the instance variable is not initialized. And to get one better, by avoiding the obj variable instance will be initialized:

[Fact]
public void Csharp_9_pattern_matching_for_non_null_object_without_obj_variable()
{
    if (new object() is not { } instance) // false
        return;

    if (instance is null) // false
        throw new Exception("Never reached");
        
    // this does not throw an exception (test passes)
}

Does anybody know if this is a desired behavior? If yes, what is the intent behind it?

1

There are 1 best solutions below

0
On BEST ANSWER

As Yari Halberstadt pointed out this is actually a compiler bug. I posted a question regarding this on the Csharplang discussions and was informed that this was already fixed.

As canton7 pointed out the issue was fixed recently and has at this point in time not been released yet.

Once released all example test-cases should pass. The failing test-case is actually a valid usecase according to this C#9 release blog where the following example is used:

if (e is not Customer c) { throw ... } // if this branch throws or returns...
var n = c.FirstName; // ... c is definitely assigned here