Roslyn: Pattern matching crashes if lambda captures pattern variable

Created on 22 Dec 2016  路  8Comments  路  Source: dotnet/roslyn

Version Used:
Microsoft Visual Studio Enterprise 2017 RC
Version 15.0.26014.0 D15REL
Microsoft .NET Framework
Version 4.6.01055

Steps to Reproduce:
Run the following app:

using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp2017
{
    class Program
    {
        class A
        {
        }

        static void Main(string[] args)
        {                           
            var dummy = new List<int>();

            switch((object)new A())
            {
                case A a:
                    {
                        dummy.Any(x => a != null);
                    }
                    break;
            }
        }
    }
}

Expected Behavior:
App just runs, no output.

Actual Behavior:
Null-reference on the switch statement, when trying to initialize not created instance of a hidden class <>c__DisplayClass27_0 CS$<>8__locals0;

Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object.
at ConsoleApp2017.Program.Main(String[] args) in D:\Temp\ConsoleApp2017\ConsoleApp2017\Program.cs:line 16

Area-Compilers Blocked Bug New Language Feature - Pattern Matching Resolution-Fixed

Most helpful comment

The extent. Not the scope. It gives the variable a place to live that doesn't have lifetime concerns as switch cases get entered/exited.

The variable will still be scoped such that it can only be legally referenced inside the appropriate switch-section.

All 8 comments

@gafter, why are these display classes even being used. I wasn't expecting any extra allocations when using pattern matching.

There's a lambda that captures the pattern variable.

Doh!

But it seems related to that.

In my case I was able to work around the problem by extracting the code in the cases to other methods.

Another workaround is simply assigning the pattern variable to another temp one. Change this:

 class C { public int A; }

    class Program
    {
        static void Main(string[] args)
        {
            switch (new C())
            {
                case C c:
                    Func<bool> f = () => c.A > 10;
                    break;
                default:
                    break;
            }
        }
    }

to this:

 class C { public int A; }

    class Program
    {
        static void Main(string[] args)
        {
            switch (new C())
            {
                case C c:
                    var rcWorkaround = c;
                    Func<bool> f = () => rcWorkaround.A > 10;
                    break;
                default:
                    break;
            }
        }
    }

@AlekseyTs Is working on making an LDM-approved change so that the extent of a case-block scoped expression variable will be the switch block. That should fix this issue as a side-effect. Leaving it open until we can verify that.

@AlekseyTs Is working on making an LDM-approved change so that the extent of a case-block scoped expression variable will be the switch block. That should fix this issue as a side-effect. Leaving it open until we can verify that.

Say what?

The extent. Not the scope. It gives the variable a place to live that doesn't have lifetime concerns as switch cases get entered/exited.

The variable will still be scoped such that it can only be legally referenced inside the appropriate switch-section.

Was this page helpful?
0 / 5 - 0 ratings