|<<>>|15 of 339 Show listMobile Mode

A subtle failure to pattern-match null in C#

Published by marco on

 The article The null check that didn’t check for nulls by Oren Eini (Ayende) points out an interesting and subtle difference in code-generation, depending on whether you use the var keyword. Using var in pattern-matching might lead to a pattern that looks like it checks for null but doesn’t. You can see and play with a live example (SharpLab.IO) but I’ve replicated the examples below.

This is the problematic example:

string Test1(List<string> strs)
{
    if(strs is [var s])
    {
        return s;
    }
    return string.Join(",", strs);
}

It’s basically saying that the pattern should match anything that’s a collection with one element. Since the type is obvious from the method signature’s parameter strs, we use var instead of string. That generates the following code.

internal static string <Main>$>g__Test1|0_0(List<string> strs)
{
    if (strs != null && strs.Count == 1)
    {
        return strs[0];
    }
    return string.Join(",", strs);
}

Note that it returns the first element without checking it for null.

If you change the var to string, which, as noted above, is redundant, then the generated code includes a null-check.

string Test2(List<string> strs)
{
    if(strs is [string s])
    {
        return s;
    }
    return string.Join(",", strs);
}

This is the generated code for the example above.

internal static string <Main>$>g__Test2|0_1(List<string> strs)
{
    if (strs != null && strs.Count == 1)
    {
        string text = strs[0];
        if (text != null)
        {
            return text;
        }
    }
    return string.Join(",", strs);
}

If you instead use { } to indicate that you want to match a non-null object, then you also get the null-check.

string Test3(List<string> strs)
{
    if(strs is [{} s])
    {
        return s;
    }
    return string.Join(",", strs);
}

This is the generated code for the example above. It is the same as the second example that uses string for the matched parameter.

internal static string <Main>$>g__Test3|0_2(List<string> strs)
{
    if (strs != null && strs.Count == 1)
    {
        string text = strs[0];
        if (text != null)
        {
            return text;
        }
    }
    return string.Join(",", strs);
}