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);
}