简体   繁体   English

为什么 C# 模式匹配对于枚举不是详尽无遗的?

[英]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).警告 CS8509 switch 表达式未处理其输入类型的所有可能值(并非详尽无遗)。 For example, the pattern '(ConsoleApp.Flag)2' is not covered.例如,不包括模式“(ConsoleApp.Flag)2”。

Why it's not exhaustive when I listed all enum's values?为什么当我列出所有枚举的值时它并不详尽? And what is (ConsoleApp.Flg)2 enum value? (ConsoleApp.Flg)2枚举值是什么?

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.不幸的是,C# 枚举不如 Haskell 或其他具有更好 FP 功能的语言中的代数数据类型(或变体类型,但你喜欢称它们)健壮。 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.它实际上只是围绕整数值(默认为int )的一些元数据,因此类型系统中没有任何内容阻止您传递与有效枚举值不对应的值。 The compiler tells you just that, using (Flag)2 as a possible value.编译器会告诉您,使用(Flag)2作为可能的值。 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.好消息,在 Roslyn 编译器的最新版本中,此警告(例如,未涵盖模式(ConsoleApp.Flag)2 )已被赋予新代码 CS8524。

The original warning code CS8509 now applies only to missing named enum values.原始警告代码 CS8509 现在仅适用于缺少的命名枚举值。

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).所以我们现在可以告诉编译器忽略 CS8524,我们认为为未命名的枚举值编写一个包罗万象的处理程序是不必要的代码膨胀,但仍然希望捕获我们忘记处理命名值的情况(或者我们将新的命名值添加到一个现有的枚举)。

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!此外,如果之前我们告诉编译器忽略 CS8509 以避免编写_ => throw...处理程序,我们现在可能希望将其更改为忽略 CS8524,以便我们为我们确实需要警告的情况返回 CS8509 警告!

Background: The Roslyn change was made in do.net/roslyn#47066 which I discovered when reading the comments for do.net/csharplang#2671 (Exhaustability for switch expressions on enums should be less strict) .背景: Roslyn 更改是在do.net/roslyn#47066中进行的,这是我在阅读do.net/csharplang#2671的评论时发现的(枚举上开关表达式的穷尽性应该不那么严格)

This can be made exhaustive with a #pragma directive.这可以通过#pragma指令来做到详尽无遗。 Example:例子:

public enum SoundEffect
{ 
    NOTE_0,
    NOTE_1,
    NOTE_2,
    NOTE_3,
    NOTE_4,
    CHIME,
}

AudioClip GetAudioClip(SoundEffect soundEffect)
{
    #pragma warning disable CS8524
    return soundEffect switch
    {
        SoundEffect.NOTE_0 => Notes[0],
        SoundEffect.NOTE_1 => Notes[1],
        SoundEffect.NOTE_2 => Notes[2],
        SoundEffect.NOTE_3 => Notes[3],
        SoundEffect.NOTE_4 => Notes[4],
        SoundEffect.CHIME => Chime,
    };
    #pragma warning restore CS8524
}

Then, any new value that you add to SoundEffect (in the example above) will trigger a compiler warning.然后,您添加到SoundEffect (在上面的示例中)的任何新值都将触发编译器警告。

You can turn that compiler warning into an error through C# compiler options.您可以通过 C# 编译器选项将该编译器警告转换为错误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM