19

各エントリに定数でアクセスできる固定サイズの数値コレクションがたくさんあります。当然、これは配列と列挙型を指しているようです:

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

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

もちろん、これに関する問題は、列挙型を使用してキャストなしで配列にインデックスを付けることができないことです (ただし、コンパイルされた IL はプレーンな int を使用しています)。したがって、これをあちこちに書く必要があります:

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

または、列挙型を指定する 2 番目のジェネリック パラメーターを持つバリアントですらあります。これは私が望むものに非常に近いですが、問題は、特定されていない列挙型 (ジェネリック パラメーターまたはボックス化された型の列挙型) を 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 
}

ご覧のとおり、不要なボックスとアンボックスの説明があります。バイナリからそれらを取り除くと、コードは正常に機能し、純粋な配列アクセスよりも少し遅くなります。

この問題を簡単に克服する方法はありますか? それとももっと良い方法ですか?このようなインデクサー メソッドにカスタム属性のタグを付けて、コンパイル後にこれら 2 つの命令を削除することも可能だと思います。それに適したライブラリは何でしょうか?もしかしてモノ・セシル?

もちろん、列挙型を削除して、次のように定数を使用する可能性は常にあります。

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 がデフォルトで enum 値の検証を提供しているわけではありません。つまり、呼び出し元は常にint.MaxValueenum 型にキャストでき、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
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 に答える
3

固定サイズのコレクションが多数ある場合は、float[] よりもプロパティをオブジェクトにラップする方がおそらく簡単です。

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

オブジェクトを渡すことで、必要な型の安全性、簡潔なコード、一定時間のアクセスが得られます。

また、本当に配列を使用する必要がある場合は、オブジェクトのプロパティを float[] にマップする ToArray() メソッドを簡単に追加できます。

于 2009-01-14T18:55:14.413 に答える
1

列挙型はタイプセーフであると想定されています。それらを配列のインデックスとして使用している場合、列挙型の型と値の両方を修正しているため、int 定数の静的クラスを宣言するよりも利点はありません。

于 2009-01-14T17:56:26.793 に答える
0

私はC#に100%精通していませんが、以前にあるタイプを別のタイプにマップするために使用される暗黙の演算子を見たことがあります。列挙型の暗黙的な演算子を作成して、それをintとして使用できるようにすることはできますか?

于 2009-01-14T19:19:09.013 に答える
0

残念ながら、暗黙的な変換演算子を列挙型に追加する方法はないと思います。したがって、醜い型キャストを使用するか、const を持つ静的クラスを使用する必要があります。

これは、暗黙的な変換演算子について詳しく説明している StackOverflow の質問です。

c# で列挙型の暗黙的な変換を定義できますか?

于 2009-01-14T17:45:55.440 に答える