使用C#中的枚举索引数组

gix*_*gix 18 c# arrays enums cil

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

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

float[] stats = new float[...];
stats[StatType.Foo] = 1.23f;
Run Code Online (Sandbox Code Playgroud)

这个问题当然是你不能使用枚举来索引没有强制转换的数组(虽然编译的IL使用普通的int).所以你必须在整个地方写这个:

stats[(int)StatType.foo] = 1.23f;
Run Code Online (Sandbox Code Playgroud)

我试图找到方法使用相同的简单语法而不进行强制转换但尚未找到完美的解决方案.使用字典似乎是不可能的,因为我发现它比数组慢大约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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

甚至是带有指定枚举的第二个通用参数的变体.这非常接近我想要的但问题是你不能只是将一个非特定的枚举(从泛型参数或盒装类型枚举)转换为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 
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,那里有不必要的box和unbox指令.如果从二进制文件中删除它们,代码就可以正常工作,并且比纯数组访问慢一点.

有没有办法轻松克服这个问题?或者甚至更好的方式?我认为也可以使用自定义属性标记这样的索引器方法,并在编译后删除这两个指令.什么是合适的图书馆?也许是Mono.Cecil?

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

static class StatType {
    public const int Foo = 0;
    public const int Bar = 1;
    public const int End = 2;
}
Run Code Online (Sandbox Code Playgroud)

这可能是您可以直接访问阵列的最快方法.

Jon*_*eet 8

我怀疑你可以通过编译委托为你做转换来使它快一点,这样它就不需要装箱和取消装箱.如果您使用的是.NET 3.5,表达式树可能是最简单的方法.(您将在EnumArray示例中使用它.)

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

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


Mar*_*ett 5

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

 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; }
    }
 }
Run Code Online (Sandbox Code Playgroud)


Ste*_*nke 0

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

这是一个 StackOverflow 问题,详细讨论了隐式转换运算符:

我们可以在 C# 中定义枚举的隐式转换吗?