Compiler complains about unassigned variable after switch on enum - c#

I have the following enumerator:
enum Foo { Bar, Baz };
In the following code, the compiler aborts with the error:
use of unassigned local variable 'str'
The code:
string str;
Foo foo = Foo.Bar;
switch (foo)
{
case Foo.Bar: str = "a"; break;
case Foo.Baz: str = "b"; break;
}
str.ToLower();
The switch covers all the possible values of the enumerator. But the compiler still thinks that str might be unassigned. Why is that? Of course I could put a default case in there, but that would be wrong, since errors are not caught at compile-time. For example, if the Foo enum is modified later on and a new value added, then it would be nice to get a compiler error. If I use a default case, then the error is not caught when recompiling.
I suppose there is no way to get the compiler to accept a switch without a default case and raise an error if Foo is extended later on?

I suppose there is no way to get the compiler to accept a switch without a default case and raise an error if Foo is extended later on?
This is correct. Long story short, the reason why the compiler does this is that it is possible to assign foo a value that is not one of a valid enum Foos values by typecasting an int, making it possible to bypass all cases of the switch.
The solution that I use in situations like this is to add an assertion:
switch (foo)
{
case Foo.Bar: str = "a"; break;
case Foo.Baz: str = "b"; break;
default: Debug.Assert(false, "An enum value is not covered by switch: "+foo);
}

Enums are statically typed and type checked. But the checking does not extend to ensure that enum values only assume defined values. In fact, for Flags enums variables often do not assume any single defined value.
Like that:
Foo f = (Foo)1234; //valid
And that's why the switch can pick the default case at runtime and str could end up being used in an uninitialized state.
Some languages have stronger constructs than .NET enums such as Haskell and F#.

The enum is essentially an int and any int value could be assigned to it. This usually doesn't happen but is why you need to handle the default case or simply declare the string with a default value (like null)

Of course I could put a default case in there, but that would be wrong
That would be according to good practices. Your enum can still contain other numeric values, because enums in C# are only compile time layer above the underlying numeric representation - think const fields. Foo f = (Foo)int.MaxValue; will still compile and run, but now you don't have a switch case for it.
Depending on your interface, you may either put a default case with an exception there, define str with null, or an empty string.

your best bet is to just initialize str with an empty string in your first line. the compiler can't (or won't) try to analyse the switch logic that deeply.

Related

Why C# pattern matching is not exhaustive for enums?

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).

Correct exception to throw for an unhandled switch case for an argument?

NOTE: This is different than the proposed duplicates as this deals with an argument rather than a value. The behavior and applicable scenarios are essentially different.
Say we have SomeEnum and have a switch statement handling it like:
enum SomeEnum
{
One,
Two,
}
void someFunc(SomeEnum value)
{
switch(value)
{
case SomeEnum.One:
...
break;
case SomeEnum.Two:
...
break;
default:
throw new ??????Exception("Unhandled value: " + value.ToString());
}
}
As you see we handle all possible enum values but still keep a default throwing an exception in case a new member gets added and we want to make sure we are aware of the missing handling.
My question is: what's the right exception in such circumstances where you want to notify that the given code path is not handled/implemented or should have never been visited? We used to use NotImplementedException but it doesn't seem to be the right fit. Our next candidate is InvalidOperationException but the term doesn't sound right. What's the right one and why?
EDIT: C# 8.0 introduced switch expressions which produce compiler warnings for non-exahustive switch statements. That's another reason why you should use switch expressions over switch statements whenever applicable. The same function can be written in a safer way like:
void someFunc(SomeEnum value)
{
_ = value switch
{
SomeEnum.One => ....,
SomeEnum.Two => ....,
}
}
When a new member gets added to SomeEnum, the compiler will show the warning "CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'EnumHandling.SomeEnum.Three' is not covered." for the switch expression which makes it way easier to catch potential bugs.
ArgumentException looks the most correct to me in this instance (though is not defined in the BCL).
There is a specialized exception for enum arguments - InvalidEnumArgumentException:
The exception thrown when using invalid arguments that are enumerators.
An alternative is ArgumentOutOfRangeException:
The exception that is thrown when the value of an argument is outside the allowable range of values as defined by the invoked method.
The logic for using these is that the passed in argument (value) is not valid as far as someFunc is concerned.
I'd throw the InvalidEnumArgumentException as it will give more detailed information in this case, you are checking on an enum
Since you have the login in a function you can throw InvalidArgumentException.
The exception that is raised when a parameter that is not valid is
passed to a method on the referenced connection to the server.
EDIT:
A better alternative would be: ArgumentException, since InvalidArgumentException in Microsoft.SqlServer.Management.Common namespace. Something like:
throw new ArgumentException("Unhandled value: " + value.ToString());
InvalidArgumentException.
when user pass some invalid value or null value when value value is required, it is recommended to handle InvalidArgumentException .
If you were using Code Contracts (something I HIGHLY recommend), you would put this at the start of the method:
Contract.Requires(value == SomeEnum.One || value == SomeEnum.Two);
If you want to check a range of an enum which has too many individual values to write them all explicitly, you can do it like this:
Contract.Requires(SomeEnum.One <= value && value <= SomeEnum.Two);

How do you maintain code with InvalidEnumArgumentException?

I am curious how would you maintain your code once you throw a System.ComponentModel.InvalidEnumArgumentException.
Basically I have a switch statement like this:
switch (enumValue)
{
case MyEnum.Value1:
break;
case MyEnum.Value2:
break;
default:
throw new InvalidEnumArgumentException();
}
What if I decide to add more values to MyEnum in the future, for example, Value3 and Value4? That would mean I would end up throwing a misleading exception. How would I prevent this?
Should I use reflection before throwing? What exception should I throw in this case? I'm looking for suggestions.
I just discovered this exception a couple minutes ago so maybe I am looking at this in the wrong context. Is this exception thrown when a certain enum argument is not supported (in which case Value3 and Value4 would not be supported)?
The problem you state depends on the context, if a method receives an enumeration as an argument it must specify what values does it support and what it does with an unknown enumeration value.
If you add more enumeration options you need to decide what to do even if you were not throwing an exception in the default case.
Be reminded that the exception is special helpful since you can pass any integer as an enumeration value.
For example:
enum Foo { A, B }
static int Bar(Foo f)
{
switch (f)
{
case Foo.A:
return 1;
case Foo.B:
return 2;
default:
throw new InvalidEnumArgumentException("f", (int)f, typeof(Foo));
}
}
static void Main()
{
Bar(Foo.A);
Bar((Foo)99);
}
What if I decide to add more values to MyEnum in the future, for example, Value3 and Value4?
That would mean I would end up throwing a misleading exception. How would I prevent this?
When you're using InvalidEnumArgumentException the key thing to understand is argument. By throwing the exception you are saying that the argument for the method was invalid. (InvalidEnumArgumentException derives from ArgumentException.) It doesn't necessarily mean that the value was not a member of the enum. So I would not consider it misleading.
I wouldn't be using that exception you're using in that context. By virtue of enumValue being of type MyEnum (I presume?) it can never contain an invalid enum value. If you have switches based on the value of the enum which need to fail if they don't recognise the value then you'll need to throw an appropriate exception (maybe just a normal ArgumentException?) but in most cases I guess you'd let the code run do nothing for an unknown enum value.
You are using the exception in a wrong way:
http://msdn.microsoft.com/en-us/library/system.componentmodel.invalidenumargumentexception.aspx
This exception is thrown if you pass an invalid enumeration value to a method or when setting a property.
I believe you are looking it in the wrong context, if these weren't enum but some specific values which were based on some business rule, so in such cases as the rules increase you would touch up relevant codes like these to incorporate the new ones. So if you are modifying the Enum then you should be look around for changes like these.
To validate the incoming enumeration value use the following static method...
public void MyMethod(MyEnum e)
{
if (!Enum.IsDefined(typeof(MyEnum), e))
throw new InvalidEnumArgumentException("e", (int)e, typeof(MyEnum));
...and you do not need to alter the check if new enum values are added in the future.

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.

switch statement in C# and "a constant value is expected"

Why does the compiler say "a constant value is required" for the first case...the second case works fine...
switch (definingGroup)
{
case Properties.Settings.Default.OU_HomeOffice:
//do something
break;
case "OU=Home Office":
//do something
break;
default:
break;
}
also tried...
switch (definingGroup)
{
case Properties.Settings.Default.OU_HomeOffice.ToString():
//do something
break;
case "OU=Home Office":
//do something
break;
default:
break;
}
...same error
Here's the Properties.Setting code
[global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("OU=Home Office")]
public string OU_HomeOffice {
get {
return ((string)(this["OU_HomeOffice"]));
}
}
Properties.Settings.Default.OU_HomeOffice isn't a constant string - something known at compile time. The C# switch statement requires that every case is a compile-time constant.
(Apart from anything else, that's the only way it can know that there won't be any duplicates.)
See section 8.7.2 of the C# 3.0 spec for more details.
This is because the value cannot be determined at compile time (as it is coming out of a configuration setting). You need to supply values that are known at the time the code is compiled (constants).
What it's basically saying is that it needs to ensure that the value for each case will not change at runtime. Hard-coding your string in-line like you did on the second case will ensure that the value won't change at run time (as would declaring a 'const' variable and assigning it the hard-coded string as a value).
The first case is making a call into a property of a class, the value of which isn't known to the compiler at compile time.
If you have some 'configuration' values that are pretty much doing to be constant within your application, you might consider creating a class where you can hard-code these values are const variables and use those in your switch statements. Otherwise, you're likely going to be stuck with having to use if/else if statements.

Categories