【问题标题】:Indexing arrays with enums in C#在 C# 中使用枚举索引数组
【发布时间】:2010-10-01 10:21:16
【问题描述】:

我有很多固定大小的数字集合,其中每个条目都可以使用常量访问。自然这似乎指向数组和枚举:

enum StatType {
    Foo = 0,
    Bar
    // ...
}

float[] stats = new float[...];
stats[StatType.Foo] = 1.23f;

这个问题当然是你不能在没有强制转换的情况下使用枚举来索引数组(尽管编译后的 IL 使用纯整数)。所以你必须到处写这个:

stats[(int)StatType.foo] = 1.23f;

我已经尝试找到在不强制转换的情况下使用相同简单语法的方法,但还没有找到完美的解决方案。使用字典似乎是不可能的,因为我发现它比数组慢大约 320 倍。我还尝试为以枚举为索引的数组编写一个泛型类:

public sealed class EnumArray<T>
{
    private T[] array;
    public EnumArray(int size)
    {
        array = new T[size];
    }
    // slow!
    public T this[Enum idx]
    {
        get { return array[(int)(object)idx]; }
        set { array[(int)(object)idx] = value; }
    }
}

甚至是带有指定枚举的第二个通用参数的变体。这与我想要的非常接近,但问题是您不能只将非特定枚举(无论是来自泛型参数还是盒装类型枚举)强制转换为 int。相反,您必须先将其与对象的转换一起装箱,然后再将其转换回。这有效,但速度很慢。我发现为索引器生成的 IL 看起来像这样:

.method public hidebysig specialname instance !T get_Item(!E idx) cil managed
{
    .maxstack 8
    L_0000: ldarg.0 
    L_0001: ldfld !0[] EnumArray`2<!T, !E>::array
    L_0006: ldarg.1 
    L_0007: box !E
    L_000c: unbox.any int32
    L_0011: ldelem.any !T
    L_0016: ret 
}

如您所见,那里有不必要的装箱和拆箱说明。如果你从二进制文件中去掉它们,代码就可以正常工作,并且比纯数组访问慢一点。

有什么方法可以轻松解决这个问题吗?或者甚至更好的方法? 我认为也可以使用自定义属性标记此类索引器方法并在编译后剥离这两个指令。什么是合适的图书馆?也许是 Mono.Cecil?

当然,总是有可能删除枚举并使用这样的常量:

static class StatType {
    public const int Foo = 0;
    public const int Bar = 1;
    public const int End = 2;
}

这可能是最快的方式,因为您可以直接访问数组。

【问题讨论】:

    标签: c# arrays enums cil


    【解决方案1】:
    struct PseudoEnum 
    { 
        public const int INPT = 0; 
        public const int CTXT = 1; 
        public const int OUTP = 2; 
    }; 
    
    // ... 
    
    String[] arr = new String[3]; 
    
    arr[PseudoEnum.CTXT] = "can"; 
    arr[PseudoEnum.INPT] = "use"; 
    arr[PseudoEnum.CTXT] = "as"; 
    arr[PseudoEnum.CTXT] = "array"; 
    arr[PseudoEnum.OUTP] = "index"; 
    

    (我也在https://stackoverflow.com/a/12901745/147511发布了这个答案)

    [编辑:哎呀,我刚刚注意到 Steven Behnke 在本页的其他地方提到了这种方法。对不起;但至少这显示了一个这样做的例子......]

    【讨论】:

      【解决方案2】:

      我对 C# 不是 100% 熟悉,但我之前曾见过用于将一种类型映射到另一种类型的隐式运算符。您能否为 Enum 类型创建一个隐式运算符,允许您将其用作 int?

      【讨论】:

      • 您只能在源类或目标类中创建这些转换器。因此,要做到这一点,您必须更改 Int32 类或枚举类,这是您无法做到的。
      【解决方案3】:

      如果你有很多固定大小的集合,那么将你的属性包装在一个对象中可能比使用 float[] 更容易:

      public class Stats
      {
          public float Foo = 1.23F;
          public float Bar = 3.14159F;
      }
      

      传递一个对象将为您提供所需的类型安全、简洁的代码和常量时间访问。

      如果您真的需要使用数组,添加一个 ToArray() 方法将对象的属性映射到 float[] 很容易。

      【讨论】:

      • 虽然这可能对简单的事情有好处,但它使得循环部分值变得困难。由于数组和枚举之间存在 1:N 关系,因此更新它们也很麻烦,例如一些枚举用于索引多个数组。
      【解决方案4】:

      如果您的 EnumArray 不是通用的,而是明确采用了 StatType 索引器 - 那么您就可以了。如果这不是可取的,那么我自己可能会使用 const 方法。但是,传入 Func 的快速测试显示与直接访问相比没有明显差异。

       public class EnumArray<T, E> where E:struct {
          private T[] _array;
          private Func<E, int> _convert;
      
          public EnumArray(int size, Func<E, int> convert) {
              this._array = new T[size];
              this._convert = convert;
          }
      
          public T this[E index] {
              get { return this._array[this._convert(index)]; }
              set { this._array[this._convert(index)] = value; }
          }
       }
      

      【讨论】:

      • 这似乎是一个很好的解决方法来转换一个通用参数。虽然生成的 IL 包含对委托的 callvirt,并且在编译或执行期间并未优化。
      【解决方案5】:

      枚举应该是类型安全的。如果您将它们用作数组的索引,那么您将同时修复枚举的类型和值,因此与声明 int 常量的静态类相比,您没有任何好处。

      【讨论】:

      • 我想如果你真的可以在编译时对(某些)枚举强制类型安全会很好。也适用于 (enumValue + int) 这样的表达式,其中 int 具有在编译时已知的有效范围。
      【解决方案6】:

      我怀疑您可能可以通过编译委托为您进行转换来加快速度,这样就不需要装箱和拆箱。如果您使用的是 .NET 3.5,那么表达式树可能是最简单的方法。 (您将在 EnumArray 示例中使用它。)

      我个人很想使用您的const int 解决方案。它不像 .NET 默认提供枚举值验证 - 即您的调用者总是可以将 int.MaxValue 转换为您的枚举类型,并且您会得到一个 ArrayIndexException (或其他)。因此,鉴于您已经获得了相对缺乏保护/类型安全性,恒定值的答案很有吸引力。

      希望 Marc Gravell 会在一分钟内完成编译转换委托的想法...

      【讨论】:

        【解决方案7】:

        不幸的是,我不相信有任何方法可以将隐式转换运算符添加到枚举中。因此,您必须要么忍受丑陋的类型转换,要么只使用带有 const 的静态类。

        这是一个 StackOverflow 问题,讨论了有关隐式转换运算符的更多信息:

        Can we define implicit conversions of enums in c#?

        【讨论】:

          猜你喜欢
          • 2010-11-02
          • 1970-01-01
          • 2010-09-29
          • 2014-02-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-10-17
          相关资源
          最近更新 更多