У меня много коллекций фиксированного размера чисел, к которым можно получить доступ к каждой записи с константой. Естественно, это указывает на массивы и перечисления:
enum StatType {
Foo = 0,
Bar
// ...
}
float[] stats = new float[...];
stats[StatType.Foo] = 1.23f;
Проблема с этим, конечно, в том, что вы не можете использовать перечисление для индексации массива без приведения (хотя скомпилированный IL использует простые ints). Поэтому вы должны написать это повсюду:
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; }
}
}
или даже вариант со вторым общим параметром, определяющим перечисление. Это очень близко к тому, что я хочу, но проблема заключается в том, что вы не можете просто указать неспецифическое перечисление (будь то из общего параметра или Enable в штучной упаковке) в 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
}
Как вы видите, там нет лишних инструкций с полем и unbox. Если вы удалите их из двоичного кода, код работает просто отлично и чуть медленнее, чем чистый доступ к массиву.
Есть ли способ легко преодолеть эту проблему? Или, может быть, еще лучше? Я думаю, что также можно пометить такие методы индексатора специальным атрибутом и разбить эти две команды после компиляции. Что было бы подходящей библиотекой для этого? Может быть, Mono.Cecil?
Конечно, всегда есть возможность удалить перечисления и использовать такие константы:
static class StatType {
public const int Foo = 0;
public const int Bar = 1;
public const int End = 2;
}
который может быть самым быстрым способом, поскольку вы можете напрямую обращаться к массиву.