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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/443935/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 03:34:33  来源:igfitidea点击:

Indexing arrays with enums in C#

c#arraysenumscil

提问by gix

I have a lot of fixed-size collections of numbers where each entry can be accessed with a constant. Naturally this seems to point to arrays and enums:

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

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

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

The problem with this is of course that you cannot use an enum to index an array without a cast (though the compiled IL is using plain ints). So you have to write this all over the place:

这样做的问题当然是您不能使用枚举来索引没有强制转换的数组(尽管编译的 IL 使用的是普通整数)。所以你必须到处写这个:

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

I have tried to find ways to use the same easy syntax without casting but haven't found a perfect solution yet. Using a dictionary seems to be out of the question since I found it to be around 320 times slower than an array. I also tried to write a generic class for an array with enums as index:

我试图找到无需强制转换即可使用相同简单语法的方法,但还没有找到完美的解决方案。使用字典似乎是不可能的,因为我发现它比数组慢 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; }
    }
}

or even a variant with a second generic parameter specifying the enum. This comes quite close to what I want but the problem is that you cannot just cast an unspecific enum (be it from a generic parameter or the boxed type Enum) to int. Instead you have to first box it with a cast to object and then cast it back. This works, but is quite slow. I found that the generated IL for the indexer looks something like this:

甚至是带有指定枚举的第二个泛型参数的变体。这与我想要的非常接近,但问题是您不能将非特定枚举(无论是从泛型参数还是盒装类型枚举)强制转换为 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 
}

As you can see there are unnecessary box and unbox instructions there. If you strip them from the binary the code works just fine and is just a tad slower than pure array access.

如您所见,那里有不必要的装箱和拆箱说明。如果你从二进制文件中剥离它们,代码工作得很好,只是比纯数组访问慢一点。

Is there any way to easily overcome this problem? Or maybe even better ways? I think it would also be possible to tag such indexer methods with a custom attribute and strip those two instructions post-compile. What would be a suitable library for that? Maybe Mono.Cecil?

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

Of course there's always the possibility to drop enums and use constants like this:

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

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

which may be the fastest way since you can directly access the array.

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

回答by Steven Behnke

I don't believe there is any way to add an implicit conversion operator to an enum, unfortunately. So you'll have to either live with ugly typecasts or just use a static class with consts.

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

Here's a StackOverflow question that discusses more on the implicit conversion operator:

这是一个 StackOverflow 问题,它更多地讨论了隐式转换运算符:

Can we define implicit conversions of enums in c#?

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

回答by Jon Skeet

I suspect you maybe able to make it a bit faster by compiling a delegate to do the conversion for you, such that it doesn't require boxing and unboxing. An expression tree may well be the simplest way of doing that if you're using .NET 3.5. (You'd use that in your EnumArray example.)

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

Personally I'd be very tempted to use your const intsolution. It's not like .NET provides enum value validation anyway by default - i.e. your callers could always cast int.MaxValueto your enum type, and you'd get an ArrayIndexException (or whatever). So, given the relative lack of protection / type safety you're already getting, the constant value answer is appealing.

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

Hopefully Marc Gravell will be along in a minute to flesh out the compiled conversion delegate idea though...

希望 Marc Gravell 能在一分钟内充实编译后的转换委托想法......

回答by Michael Meadows

enums are supposed to be type safe. If you're using them as the index of an array, you're fixing both the type and the values of the enum, so you have no benefit over declaring a static class of int constants.

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

回答by Mark Brackett

If your EnumArray wasn't generic, but instead explicitly took a StatType indexer - then you'd be fine. If that's not desirable, then I'd probably use the const approach myself. However, a quick test with passing in a Func<T, E> shows no appreciable difference vs direct access.

如果您的 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; }
    }
 }

回答by Juliet

If you have a lot of fixed-size collections, then it would probably be easier to wrap up your properties in an object than a float[]:

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

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

Passing an object around will give you the type safety, concise code, and constant-time access that you want.

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

And if you reallyneed to use an array, its easy enough to add a ToArray() method which maps the properties of your object to a float[].

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

回答by Matthew Brubaker

I'm not 100% familiar with C#, but I've seen implicit operators used to map one type to another before. Can you create an implicit operator for the Enum type that allows you to use it as an int?

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

回答by Glenn Slayden

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"; 

(I also posted this answer at https://stackoverflow.com/a/12901745/147511)

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

[edit: oops I just noticed that Steven Behnke mentioned this approach elsewhere on this page. Sorry; but at least this shows an example of doing it...]

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