19

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

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

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

4

7 回答 7

8

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

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

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

于 2009-01-14T17:47:19.667 回答
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; }
    }
 }
于 2009-01-14T18:24:40.167 回答
3

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

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

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

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

于 2009-01-14T18:55:14.413 回答
3
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 在本页的其他地方提到了这种方法。对不起; 但至少这显示了一个这样做的例子......]

于 2012-10-15T18:30:29.493 回答
1

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

于 2009-01-14T17:56:26.793 回答
0

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

于 2009-01-14T19:19:09.013 回答
0

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

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

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

于 2009-01-14T17:45:55.440 回答