在 C# 中使用枚举索引数组
我有很多固定大小的数字集合,其中每个条目都可以使用常量访问。 当然,这似乎指向数组和枚举:
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
}
正如您所看到的,那里有不必要的装箱和拆箱指令。 如果您从二进制文件中删除它们,代码就可以正常工作,并且比纯数组访问慢一点。
有没有什么方法可以轻松解决这个问题呢? 或者也许更好的方法? 我认为也可以使用自定义属性来标记此类索引器方法,并在编译后删除这两个指令。 什么是合适的图书馆? 也许莫诺·塞西尔?
当然,总是有可能删除枚举并使用这样的常量:
static class StatType {
public const int Foo = 0;
public const int Bar = 1;
public const int End = 2;
}
这可能是最快的方法,因为您可以直接访问数组。
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:
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:
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:
.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?
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
我怀疑您可能可以通过编译委托来为您进行转换来使其更快一点,这样就不需要装箱和拆箱。 如果您使用 .NET 3.5,表达式树很可能是最简单的方法。 (您可以在 EnumArray 示例中使用它。)
就我个人而言,我很想使用您的
const int
解决方案。 无论如何,.NET 默认情况下都不会提供枚举值验证 - 即,您的调用者始终可以将int.MaxValue
转换为您的枚举类型,并且您会得到 ArrayIndexException (或其他)。 因此,考虑到您已经获得的保护/类型安全相对缺乏,恒定值答案很有吸引力。希望 Marc Gravell 很快就能充实到编译后的转换委托的想法......
I suspect you may be 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.)
Personally I'd be very tempted to use your
const int
solution. It's not like .NET provides enum value validation anyway by default - i.e. your callers could always castint.MaxValue
to 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.Hopefully Marc Gravell will be along in a minute to flesh out the compiled conversion delegate idea though...
如果您的 EnumArray 不是通用的,而是明确采用 StatType 索引器 - 那么您就可以了。 如果这不理想,那么我可能会自己使用 const 方法。 然而,通过传入 Func进行快速测试是很困难的。 与直接访问相比没有明显差异。
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.
如果您有很多固定大小的集合,那么将属性包装在对象中可能比 float[] 更容易:
传递对象将为您提供类型安全、简洁的代码和恒定时间访问你要的那个。
如果您确实需要使用数组,那么添加一个 ToArray() 方法就足够了,该方法将对象的属性映射到 float[]。
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[]:
Passing an object around will give you the type safety, concise code, and constant-time access that you want.
And if you really need to use an array, its easy enough to add a ToArray() method which maps the properties of your object to a float[].
(我还在https://stackoverflow.com/a/12901745/147511发布了这个答案)
[编辑:哎呀我刚刚注意到 Steven Behnke 在本页其他地方提到了这种方法。 对不起; 但至少这展示了一个这样做的例子......]
(I also posted this answer at 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...]
枚举应该是类型安全的。 如果将它们用作数组的索引,则将修复枚举的类型和值,因此与声明 int 常量的静态类相比没有任何好处。
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.
不幸的是,我不相信有任何方法可以将隐式转换运算符添加到枚举中。 因此,您必须要么忍受丑陋的类型转换,要么只使用带有常量的静态类。
这是一个 StackOverflow 问题,详细讨论了隐式转换运算符:
可以我们在 C# 中定义了枚举的隐式转换?
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:
Can we define implicit conversions of enums in c#?
我对 C# 并不是 100% 熟悉,但我以前见过用于将一种类型映射到另一种类型的隐式运算符。 您可以为 Enum 类型创建一个隐式运算符,允许您将其用作 int 吗?
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?