【问题标题】:Non-unique enum values非唯一枚举值
【发布时间】:2011-12-23 23:44:06
【问题描述】:

我试图掩盖 edi 文件上的索引位置...我遇到过一种情况,根据情况,索引中可能有 2 或 3 个东西。使用枚举来隐藏“幻数”会很酷,并且惊讶地发现您可以将多个枚举分配给相同的值,如下所示:

public enum Color
{
    Red = 1,
    Blue = 1,
    Green = 1
}

编译器对此很满意。我没想到这会奏效。我不需要返回枚举,所以我不担心尝试返回,但这闻起来很时髦。为什么 CLR 允许枚举有多个值,我应该为此使用结构吗? (结构似乎比枚举更重,这似乎有效)

【问题讨论】:

  • 最好用相反的问题来回答:为什么它不允许这样做?例如,当您包含 First 和 Last 枚举成员时,它会很方便。
  • 你想如何使用“struct for this”?
  • 我可以使用结构来获取枚举“外观”,而不必强制转换。类似“public static int Red { get{ return 1; }}”

标签: c# enums struct


【解决方案1】:

实际上你已经定义了一个结构......在幕后一个枚举只是一个结构(但它派生自 System.Enum)并且枚举的值被定义为常量(你可以用 ILDASM 验证这一点) .

您的枚举定义转换为以下伪 C# 代码:

public struct Color : System.Enum
{
    public const int Red = 1;
    public const int Blue = 1;
    public const int Green = 1;
}

上面的代码不会在 C# 中编译,因为编译器不允许定义具有显式基类的结构,但这就是它为枚举定义发出的内容。

由于包含多个具有相同值的常量的类型没有问题,因此枚举定义没有问题。

但由于枚举没有唯一值,因此在转换为该枚举时可能会遇到问题。 例如下面两行代码会返回枚举值Red,因为第一个值是任意选择的。

Color color1 = (Color)1;
Color color2 = (Color)Enum.Parse(typeof(Color), "1");

严格来说枚举值不是Red,它是1,但是当你打印出这个值你会看到Red。

另外,下面的布尔值是真的,看起来有点奇怪......

// true (Red is Green??)
bool b = Color.Red == Color.Green;

归根结底,这是完全合法的,但在合理的情况下使用它取决于您...

这是我的 .NET 教程中讨论底层枚举的部分的直接链接:http://motti.me/c1E

【讨论】:

  • 我们中的一些人是红绿色盲,所以最后一行代码很有意义;-)
  • 请注意,Object.Equals 在这种情况下也无法区分RedGreen。此外,自然地,Object.ReferenceEquals 对于任何一对Enums 总是为假,因为在比较值类型时它没有帮助。我真的无法知道RedGreen 之间的区别; VS 甚至无法显示正确的字段名称,所以我认为即使通过 FieldName 也不行。
【解决方案2】:

相同的数值但不同的名称不是别名。它可以是例如

public enum Color
{
   DefaultColor = 1,
   Red = 1,
   Blue = 2
}

在某些情况下它是有意义的,但不是很多。当您解析值并调用 colorValue.ToString() 时,您将返回最后一个值作为字符串化值(在本例中为红色),但您将失去默认颜色的概念,因为它是相同的。至少在您对数据建模的方式上。如果您想将其分开,请为不同的事物使用不同的值。

【讨论】:

  • 这是一个很好的“为什么”示例:因为您要枚举值,但还要命名默认值(或最小值/最大值)。
【解决方案3】:

这是完全合法的 C#。来自C# Language specification 4.0 版,第 14.3 节:

多个枚举成员可以共享相同的关联值。例子

enum Color 
{
   Red,
   Green,
   Blue,
   Max = Blue
}

显示一个枚举,其中两个枚举成员——Blue 和 Max——具有相同的 关联值。

【讨论】:

    【解决方案4】:

    如果您将每个枚举值视为一个常数,那么这是有道理的。您没有理由不能拥有两个具有相同值的常量:

    public enum MyColor 
    { 
        Blue = 2,         
        Yellow = 3,
        Green = 4
        BlueAndYellow = 4,        
    } 
    

    等同于:

    public enum MyColor 
    { 
        Blue = 2,         
        Yellow = 3,
        Green = 4,
        BlueAndYellow = Green,        
    } 
    

    基本上你只是有两个不同名字的同一个常数。 BlueAndYellowGreen 的别名。

    【讨论】:

      【解决方案5】:

      这里需要注意的一点是,非唯一值会导致 Visual Studio 设计器中的值丢失和重复。

      public enum MyColor
      {
      Red= 1,
      Green= 1,
      Blue= 2
      }
      

      如果您在可浏览的属性中使用此枚举,您将在设计器中看到 Green,Green,Blue 而不是 Red,Green,Blue

      【讨论】:

        【解决方案6】:

        这是一个完全可以接受的定义:

        public enum AllTheThings
        {
            TheMoney = 1,
            TheFreeRides = 1,
            TheLieThatYouDenied = 2,
            TheCallsYouveBeenMaking = 3,
            TheTimesYouveBeenFaking = 4
        }
        

        【讨论】:

        • this.Collection.Select(c => c.Rise());
        • 我相信这会使其进入“2 个独立学科合并”列表中的前 5 名
        【解决方案7】:

        枚举的多个成员指向同一个值可能会导致混淆。我通过 Visual Studio Marketplace 上的简单扩展为此添加了代码修复。

        UniqueEnumValueFixer

        源代码可在此处获得: https://github.com/toreaurstadboss/UniqueEnumValuesAnalyzer

        我们检测枚举是否有多个具有相同值的成员的部分如下所示。在安装 .NET 编译器 SDK (Roslyn) 后,代码基于带有代码修复 (.NET 标准) 项目类型的分析器构建。

           public override void Initialize(AnalysisContext context)
            {
                // TODO: Consider registering other actions that act on syntax instead of or in addition to symbols
                // See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Analyzer%20Actions%20Semantics.md for more information
                context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);
        
            }
        
            private static void AnalyzeSymbol(SymbolAnalysisContext context)
            {
                try
                {
                    var namedTypeSymbol = (INamedTypeSymbol)context.Symbol;
                    if (namedTypeSymbol.EnumUnderlyingType != null)
                    {
                        var valueListForEnum = new List<Tuple<string, int>>();
                        //Debugger.Launch();
                        //Debugger.Break();
                        var typeResolved = context.Compilation.GetTypeByMetadataName(namedTypeSymbol.MetadataName) ?? context.Compilation.GetTypeByMetadataName(namedTypeSymbol.ToString());
                        if (typeResolved != null)
                        {
                            foreach (var member in typeResolved.GetMembers())
                            {
                                var c = member.GetType().GetRuntimeProperty("ConstantValue");
                                if (c == null)
                                {
                                    c = member.GetType().GetRuntimeProperties().FirstOrDefault(prop =>
                                        prop != null && prop.Name != null &&
                                        prop.Name.Contains("IFieldSymbol.ConstantValue"));
                                    if (c == null)
                                    {
                                        continue;
                                    }
                                }
        
                                var v = c.GetValue(member) as int?;
                                if (v.HasValue)
                                {
                                    valueListForEnum.Add(new Tuple<string, int>(member.Name, v.Value));
                                }
                            }
                            if (valueListForEnum.GroupBy(v => v.Item2).Any(g => g.Count() > 1))
                            {
                                var diagnostic = Diagnostic.Create(Rule, namedTypeSymbol.Locations[0],
                                    namedTypeSymbol.Name);
                                context.ReportDiagnostic(diagnostic);
                            }
                        }
                    }
                }
                catch (Exception err)
                {
                    Console.WriteLine(err);
                }
        
            }
        

        枚举 IceCream 看起来像这样:

        枚举冰淇淋 { 香草= 0, 巧克力 = 2, 草莓=香草, 桃子 = 2 }

        【讨论】:

          【解决方案8】:

          需要注意的一点是,如果您依赖 C# 自动分配枚举值,那么任何别名成员的顺序都变得很重要。考虑以下几点:

          public enum Foo
          {
              Alpha,  // 0
              Bravo,  // 1
              Charlie,  // 2
              Delta,  // 3
          }
          

          如果您在其中添加别名,它将重置该位置的自动编号

          public enum Foo
          {
              Alpha,  // 0
              Bravo,  // 1
              Charlie,  // 2
              AlsoBravo = Bravo,  // AlsoBravo assigned 1, same as Bravo
              Delta,  // Delta is now 2, not 3 as you might expect
          }
          

          通常这不是问题,因为别名成员直接出现在他们正在别名的成员之后。这很好,可以按预期工作:

          public enum Foo
          {
              Alpha,  // 0
              Bravo,  // 1
              AlsoBravo = Bravo,  // AlsoBravo assigned 1, same as Bravo
              Charlie,  // Continues with 2, as expected
              Delta,  // 3
          }
          

          今天这个问题困扰着我,因为我有一个枚举,其成员具有我不想复制的属性,类似于以下内容:

          public enum AppIcon
          {
              [IconMapping(Icon.Cogs)] MenuItem_AppSettingsTab,  // 0
              [IconMapping(Icon.TabRemove)] MenuItem_CloseTab,  // 1
          
              RootTab_AppSettings = MenuItem_AppSettingsTab,  // 0
              [IconMapping(Icon.Cube)] RootTab_Package,  // 1 not 3, oops!
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2020-11-11
            • 2020-11-10
            • 1970-01-01
            • 1970-01-01
            • 2019-01-29
            • 2021-10-21
            • 1970-01-01
            • 2019-04-22
            相关资源
            最近更新 更多