У меня есть много наборов чисел фиксированного размера, где каждая запись может быть доступна с константой. Естественно, это указывает на массивы и перечисления:
enum StatType {
Foo = 0,
Bar
// ...
}
float[] stats = new float[...];
stats[StatType.Foo] = 1.23f;
Проблема с этим, конечно, в том, что вы не можете использовать enum для индексации массива без преобразования (хотя скомпилированный 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; }
}
}
или даже вариант со вторым универсальным параметром, определяющим перечисление. Это очень близко к тому, что я хочу, но проблема в том, что вы не можете просто привести неопределенное перечисление (будь то из универсального параметра или упакованного типа Enum) к 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;
}
, что может быть самым быстрым способом, поскольку вы можете напрямую обращаться к массиву.