Why C# pattern matching is not exhaustive for enums? - c#

Say, I have the following enum and the code testing enum:
enum Flag
{
On,
Off
}
string GetMessage(Flag flag) =>
flag switch
{
Flag.On => "State is ON",
Flag.Off => "State is OFF"
};
However, I get the warning:
Warning CS8509 The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(ConsoleApp.Flag)2' is not covered.
Why it's not exhaustive when I listed all enum's values? And what is (ConsoleApp.Flg)2 enum value?

Counterexample:
string Foo()
{
return GetMessage((Flag)42);
}
Unfortunately C# enums are not as robust as algebraic data types (or variant types, however you like to call them) in Haskell or other languages with better FP features. It's really just some metadata around an integral numeric value (int by default), so there's nothing in the type system stopping you from passing a value that does not correspond to a valid enum value. The compiler tells you just that, using (Flag)2 as a possible value. To fix the issue, add a standard catch-all:
string GetMessage(Flag flag) =>
flag switch
{
Flag.On => "State is ON",
Flag.Off => "State is OFF",
_ => throw new ArgumentOutOfRangeException(nameof(flag)),
};

Good news! In recent versions of the Roslyn compiler, this warning (where, for example, the pattern (ConsoleApp.Flag)2 is not covered) has been given a new code CS8524.
The original warning code CS8509 now applies only to missing named enum values.
So we can now tell the compiler to ignore CS8524 where we deem it unnecessary code bloat to write a catch-all handler for unnamed enum values but still want to catch cases where we forgot to handle a named value (or we add new named values to an existing enum).
Also, if previously we told the compiler to ignore CS8509 to avoid writing _ => throw ... handlers, we might want to change that to ignore CS8524 instead now so we get back our CS8509 warning for the cases we do want warnings about!
Background: The Roslyn change was made in dotnet/roslyn#47066 which I discovered when reading the comments for dotnet/csharplang#2671 (Exhaustability for switch expressions on enums should be less strict).

Related

Wondering why to use `string?` instead of `string` in a property declaration

I have always developed using ASP.NET Framework, where I used a string property this way: public string FirstName { get; set; }.
Now I started a new .NET Core 6 project and I declared the same property in a custom IdentityUser class.
In this case, Visual Studio told me that it is better to use nullable string. Why does it suggest that since a string type can be already null?
Suggestion message appears in Spanish but it is basically what I have described.
That suggestion is gone when I use string?. Note that if I use Nullable<string> shows a compiler error since string is a reference type.
Just wondering.
Thanks
Jaime
No need to use Nullable<> type. That's for value types. Just decide if you want to use the Nullable Context. You will find this setting under
Project Properties >> Build >> General >> Nullable.
You have two basic options here.
Turn off the setting to make your code work like before (I would not do this)
Make your code honor the setting.
When this Nullable Context enabled, you are telling the compiler the following:
Any reference type declared without the ? symbol may never be null. It must always refer to a valid object.
Likewise any reference type declared with the ? symbol right after the type means that it may be null, just like old-school C# code.
That goes for strings, or any other reference type. The code-analyzer to checks for this and complains. So when you declare your property like this...
public string FirstName { get; set; }
Then you need to ensure in your constructor that this property is initialized to some valid string object or the code-analyzer will complain. Or you could declare it with a default value like this:
public string FirstName { get; set; } = string.Empty
If you want to be able to set the FirstName to null -- if that actually makes sense for your project -- then declare it with the question mark
public string? FirstName { get; set; }
Now it acts like an old style reference type, before C# 8.0.
Once you turn this setting on you'll find yourself dealing with this a lot. If you have warnings set to the highest level you'll be forced to chase down all these things in your code and address them. This is a good thing. Don't avoid it. Yes it is a pain to address up front but it saves you countless headaches down the road.
I once did it for an established application and spent two full days fixing all the warnings it generated. But in the year or two since then, I have lost count of the number of times this feature made the compiler/code-analyzer catch me failing to initialize a non-nullable reference type and saved me from a potential NullReferenceException down the line. I think this feature is priceless.
Nullable Context forces you to think about every reference type you have when you write the API. Can that reference be null or not? It makes you set the rule and honor it and is a lifesaver in a large project with multiple developers
Note: If you do use Nullable Context, you will want to be familiar with a very useful code attribute to use on some out values for functions: The [MayBeNullWhen] attribute. It comes in handy when you write a Try-type function to retrieve a reference type.
For example, suppose you wrote a function like this. This would work fine before but generates errors with Nullable Context enabled
public bool TryGetWidget(out Widget value)
{
value = null; // *** ERROR: Not valid if value is not nullable
if (/* some code that tries to retrieve the widget */)
value = retrievedWidget;
return value != null
}
The implication here is that the function might not succeed and return false. But if Widget is a reference type, then it will have to set the out value to be null in this case. Nullable Context will not allow that. You declared the out value as out Widget not out Widget? So you cannot set it to null. So what do you do?
You could change the argument to out Widget?
public bool TryGetWidget(out Widget? value)
That would make the function build. But then the Nullable context would complain if you tried to use the return value after the function returned true.
if (TryGetWidget(out var widget))
widget.SomeFunction(); // ERROR: `widget` might be null
Seems you can't win either way, doesn't it? Unless you use [MayBeNullWhen(false)] on the function declaration
public bool TryGetWidget([MaybeNullWhen(false)] out Widget value)
Now, your function will compile and your code that calls it will compile. The compiler/code-analyzer is smart enough to realize that a true return means you can use the out reference and a false returns means you cannot.
In addition to the ? operator there is also the ! operator. This one tells the compiler, "Ignore nullability. Assume this reference is valid" So if you had a function like this
Widget? GetWidget();
The fillowing code would not compile
GetWidget().SomeFunction(); // ERROR: Return value of GetWidget() Might be null
but this would compile by forcing the compiler to pretend the return value is valid.
GetWidget()!.SomeFunction(); // Better hope that returned reference is valid.

Is there a convenient way to filter a sequence of C# 8.0 nullable references, retaining only non-nulls?

I have code like this:
IEnumerable<string?> items = new [] { "test", null, "this" };
var nonNullItems = items.Where(item => item != null); //inferred as IEnumerable<string?>
var lengths = nonNullItems.Select(item => item.Length); //nullability warning here
Console.WriteLine(lengths.Max());
How can I write this code in a convenient way such that:
There is no nullability warning, because the type nonNullItems is inferred as IEnumerable<string>.
I don't need to add unchecked non-nullability assertions like item! (because I want to benefit from the compilers sanity checking, and not rely on me being an error-free coder)
I don't add runtime checked non-nullability assertions (because that's pointless overhead both in code-size and at runtime, and in case of human error that fails later than ideal).
The solution or coding pattern can apply more generally to other sequences of items of nullable-reference type.
I'm aware of this solution, which leverages the flow-sensitive typing in the C# 8.0 compiler, but it's.... not so pretty, mostly because it's so long and noisy:
var notNullItems = items.SelectMany(item =>
item != null ? new[] { item } : Array.Empty<string>())
);
Is there a better alternative?
I think you'll have to help the compiler in either one way or another. Calling .Where() is never safe of returning not-null. Maybe Microsoft could add some logic to determine basic scenarios like yours, but AFAIK that's not the situation right now.
However, you could write a simple extension method like that:
public static class Extension
{
public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> o) where T:class
{
return o.Where(x => x != null)!;
}
}
Unfortunately you will have to tell the compiler that you know more about the situation than it does.
One reason would be that the Where method has not been annotated in a way that lets the compiler understand the guarantee for non-nullability, nor is it actually possible to annotate it. There might be a case for having additional heuristics added to the compiler to understand some basic cases, like this one, but currently we do not have it.
As such, one option would be to use the null forgiving operator, colloquially known as the "dammit operator". You touch upon this yourself, however, instead of sprinkling exclamation marks all over the code where you use the collection, you can instead tuck on an additional step on producing the collection which, at least to me, makes it more palatable:
var nonNullItems = items.Where(item => item != null).Select(s => s!);
This will flag nonNullItems as IEnumerable<string> instead of IEnumerable<string?>, and thus be handled correctly in the rest of your code.
I don't know if this answer meets the criteria for your 3rd bullet point, but then your .Where() filter does not either, so...
Replace
var nonNullItems = items.Where(item => item != null)
with
var nonNullItems = items.OfType<string>()
This will yield an inferred type of IEnumerable<string> for nonNullItems, and this technique can be applied to any nullable reference type.
FWIW special support is being considered for C# 10: https://github.com/dotnet/csharplang/issues/3951

Using a dynamic variable as a method argument disables (some) compiler checks

Can someone explain to me why the compiler does not check the return type of a function if a dynamic variable is used as an argument to a method call?
class Program
{
static void Main(string[] args)
{
// int a = GetAString(1); // Compiler error CS0029 Cannot impilicitly convert type 'string' to 'int'
dynamic x = 1;
int b = GetAString(x); // No compiler error -> Runtime Binder Exception
// int c = (string)GetAString(x); // Compiler error CS0029 Cannot impilicitly convert type 'string' to 'int'
}
static string GetAString(int uselessInt)
{
return "abc";
}
}
By using dynamic the compiler will generate a call site anywhere you use a dynamic parameter. This call site will attempt to resolve the method at runtime, and if it cannot find a matching method will raise an exception.
In your example the call site examines x and sees that it is an int. It then looks for any methods called GetAString that take an int and finds your method and generates code to make the call.
Next, it will generate code to attempt to assign the return value to b. All of this is still done at runtime as the use of the dynamic variable has made the entire expression require runtime evaluation. The call site will see if it can generate code to assign a string to an int, and as it cannot it will raise an exception.
As an aside, your example doesn't make a lot of sense as you seem to want to assign a string to an int Your GetAsString method is even returning a non-numeric value so it's never going to assign to an int. If you write:
dynamic x = 1;
string b = GetAsString(x);
Then everything should work.
In the general case, the candidates aren't necessarily as straightforward as yours. For example, consider these two methods:
string M(string a) => a;
char[] M(char[] a) => a;
What should this code suggest as the type of the last variable?
dynamic d = SomeExpression();
var s = M(d);
At this point, the designers of C# would have to make a choice:
Assert that the return value of a method called with dynamic arguments is also dynamic itself.
Select a type that can be assigned from all methods of the group (e.g. IEnumerable<char>).
The latter option is essentially what you're describing in your question. The C# designers went with the former option. Possible reasons for that design decision could be:
Maybe they thought that if you opt in to dynamic in an expression, then it's more likely than not that you'll want to keep using dynamic on any dependent expressions, until you explicitly opt out of it again.
Maybe they didn't introduce dynamic to enable multiple dispatch, so they didn't want to encourage it further by including provisions for static typing.
Maybe they thought that including those provisions would bloat the specification or make the language harder to understand.
Maybe the former option is simpler to implement (assuming you already have the rest of dynamic implemented) and they decided the other option wasn't worth more time or effort.
Maybe it's just not that straightforward to implement in C#. Value types could require boxing to match the common supertype, which complicates things. Raw pointer types are out of the unified hierarchy altogether.

Local constant initialised to null reference

I have read that C# allows local constants to be initialised to the null reference, for example:
const string MyString = null;
Is there any point in doing so however? What possible uses would this have?
My guess is because null is a valid value that can be assigned to reference types and nullable values types.
I can't see any reason to forbid this.
There might be some far off edge cases where this can be useful, for example with multi targeting and conditional compilation. IE you want to define a constant for one platform but define it as null for another due to missing functionality.
Ex, of possible usefull usage:
#IF (SILVELIGHT)
public const string DefaultName = null;
#ELSE
public const string DefaultName = "Win7";
#ENDIF
Indeed, you can initialize local const strings and readonly reference types to null, even though it seems to be redundant since their default value is null anyway.
However, the fact remains that null is a compile-time constant suitable enough to initialize strings and reference types. Therefore, the compiler would have to go out of its way in order to consider, identify and reject this special case, even though it's still perfectly valid from a language standpoint.
The merits of doing that would be disputable, and it probably wouldn't be worth the effort in the first place.
It could be used if you want to write code without keywords, if that strikes your fancy:
const string UninitializedSetting = null;
if (mySetting == UninitializedSetting)
{
Error("mySetting string not initialized");
}
Choosing to name a value (rather than using an in-place magic constant), using const, and setting to null are more or less orthogonal issues, although I agree that the venn diagram might have a very small area of overlap for the three :)
A case that I can think of is when you have as much or more throw-away data than you do code, but you want to ensure the values don't accidentally get changed while writing or maintaining your code. This situation is fairly common in unit tests:
[Test]
public void CtorNullUserName()
{
const string expectedUserName = null;
var user = new User(expectedUserName);
Assert.AreEqual(expectedUserName, user.Name, "Expected user name to be unchanged from ctor");
}
You could arguably structure such code in a plethora of ways that didn't involve assigning null to a const, but this is still a valid option.
This might also be useful to help resolve method overloading issues:
public class Something
{
public void DoSomething(int? value) { Console.WriteLine("int?"); }
public void DoSomething(string value) { Console.WriteLine("string"); }
}
// ...
new Something().DoSomething(null); // This is ambiguous, and won't compile
const string value = null;
new Something().DoSomething(value); // Not ambiguous
If you use constants, for example, for configuration of your application then why not? The null value can represent a valid state - e.g. that a logger is not installed. Also note that when you declare a local constant, you can initialize it to a value given by global constant (which may be a more interesting scenario).
EDIT: Another question is, what are good situations for using const anyway? For configuration, you'd probably want a configuration file and other variables usually change (unless they are immutable, but then readonly is a better fit...)
Besides the situations already pointed out, it may have to do with a quirk of the C# language. The C# Specification 3.0, section 8.5.2 states:
The type and constant-expression of a local constant declaration must follow the same rules as those of a constant member declaration (§10.4).
And within 10.4 reads as follows:
As described in §7.18, a constant-expression is an expression that can be fully evaluated at compile-time. Since the only way to create a non-null value of a reference-type other than string is to apply the new operator, and since the new operator is not permitted in a constant-expression, the only possible value for constants of reference-types other than string is null.

Strange (possibly wrong?) C# compiler behavior with method overloading and enums

today I discovered a very strange behavior with C# function overloading. The problem occurs when I have a method with 2 overloads, one accepting Object and the other accepting Enum of any type. When I pass 0 as parameter, the Enum version of the method is called. When I use any other integer value, the Object version is called. I know this can be easilly fixed by using explicit casting, but I want to know why the compiler behaves that way. Is this a bug or just some strange language rule I don't know about?
The code below explains the problem (checked with runtime 2.0.50727)
Thanks for any help on this,
Grzegorz Kyc
class Program
{
enum Bar
{
Value1,
Value2,
Value3
}
static void Main(string[] args)
{
Foo(0);
Foo(1);
Console.ReadLine();
}
static void Foo(object a)
{
Console.WriteLine("object");
}
static void Foo(Bar a)
{
Console.WriteLine("enum");
}
}
It may be that you're not aware that there's an implicit conversion from a constant1 of 0 to any enum:
Bar x = 0; // Implicit conversion
Now, the conversion from 0 to Bar is more specific than the conversion from 0 to object, which is why the Foo(Bar) overload is used.
Does that clear everything up?
1 There's actually a bug in the Microsoft C# compiler which lets it be any zero constant, not just an integer:
const decimal DecimalZero = 0.0m;
...
Bar x = DecimalZero;
It's unlikely that this will ever be fixed, as it could break existing working code. I believe Eric Lippert has a two blog posts which go into much more detail.
The C# specification section 6.1.3 (C# 4 spec) has this to say about it:
An implicit enumeration conversion
permits the decimal-integer-literal 0
to be converted to any enum-type and
to any nullable-type whose underlying
type is an enum-type. In the latter
case the conversion is evaluated by
converting to the underlying enum-type
and wrapping the result (§4.1.10).
That actually suggests that the bug isn't just in allowing the wrong type, but allowing any constant 0 value to be converted rather than only the literal value 0.
EDIT: It looks like the "constant" part was partially introduced in the C# 3 compiler. Previously it was some constant values, now it looks like it's all of them.
I know I have read somewhere else that the .NET system always treats zero as a valid enumeration value, even if it actually isn't. I will try to find some reference for this...
OK, well I found this, which quotes the following and attributes it to Eric Gunnerson:
Enums in C# do dual purpose. They are used for the usual enum use, and they're also used for bit fields. When I'm dealing with bit fields, you often want to AND a value with the bit field and check if it's true.
Our initial rules meant that you had to write:
if ((myVar & MyEnumName.ColorRed) != (MyEnumName) 0)
which we thought was difficult to read. One alernative was to define a zero entry:
if ((myVar & MyEnumName.ColorRed) != MyEnumName.NoBitsSet)
which was also ugly.
We therefore decided to relax our rules a bit, and permit an implicit conversion from the literal zero to any enum type, which allows you to write:
if ((myVar & MyEnumName.ColorRed) != 0)
which is why PlayingCard(0, 0) works.
So it appears that the whole reason behind this was to simply allow equating to zero when checking flags without having to cast the zero.

Categories