Подтвердить что ты не робот

Как сравнить System.Enum с перечислением (реализацией) без бокса?

Как сравнить System.Enum с enum без бокса? Например, как я могу сделать следующий код без бокса enum?

enum Color
{
    Red,
    Green,
    Blue
}

...

System.Enum myEnum = GetEnum(); // Returns a System.Enum. 
                                // May be a Color, may be some other enum type.

...

if (myEnum == Color.Red) // ERROR!
{
    DoSomething();
}

Чтобы быть конкретным, целью здесь является не сравнение базовых значений. В этом случае базовые значения не должны иметь значения. Вместо этого, если две Enums имеют одно и то же базовое значение, они не должны считаться равными, если они представляют собой два разных вида перечислений:

enum Fruit
{
    Apple = 0,
    Banana = 1,
    Orange = 2
}

enum Vegetable
{
    Tomato = 0,
    Carrot = 1,
    Celery = 2
}

myEnum = Vegetable.Tomato;
if (myEnum != Fruit.Apple) // ERROR!
{
    // Code should reach this point 
    // even though they're the same underlying int values

    Log("Works!");
}

Это в основном те же функции, что и Enum.Equals(Object). К сожалению, Equals() требует бокса перечисления, что в нашем случае было бы озорной задачей.

Есть ли хороший способ сравнить два произвольных перечисления без бокса или иначе создать кучу накладных расходов?

Спасибо за любую помощь!

4b9b3361

Ответ 1

Это можно сделать с помощью общей реализации:

private static bool Equals<TEnum>(Enum first, TEnum second)
    where TEnum : struct
{
    var asEnumType = first as TEnum?;
    return asEnumType != null && EqualityComparer<TEnum>.Default.Equals(asEnumType.Value, second);
}

Единственная память, выделенная кучей, будет ленивым экземпляром для каждого значения EqualityComparer<TEnum>.Default, однако это произойдет только один раз для каждого типа перечисления.

Ответ 2

Я был бы осторожен в том, чтобы беспокоиться о GC на этом уровне, если у вас нет действительно конкретной необходимости обеспечить низкую задержку. Это не означает, что "мой веб-сервер получает много хитов", это означает такие вещи, как воспроизведение звука/видео, игры или приложения с низкой задержкой. Если это просто "мое приложение не должно быть медленным", то избегать GC полностью перебор. Если вы избегаете бокса, чтобы избежать одного распределения, вам также следует избегать любого другого распределения, которое будет включать в себя ведение журнала, создание закрытий, любую конкатенацию строк и, конечно же, любое использование нового ключевого слова для ссылочного типа.

Однако, если вы действительно в сценарии, где вы должны избегать GC, и это последняя или худшая из ваших проблем с распределением, тогда это может быть интересно изучить. Обязательно прочитайте документацию по GC Latency Modes - если вам требуется только небольшая задержка для определенных периодов, вы можете изменить поведение GC для вас, не изменяя весь ваш код, чтобы не выделять память.

Итак, посмотрим на проблему с бонусом enum. Как уже говорили другие, значение уже помещается внутри объекта System.Enum. Вы сказали в комментарии, что это прекрасно, потому что это значение определяется один раз для срока службы приложения. В этом случае я бы решил определить его не как перечисление, а как статические значения класса. См. этот вопрос для получения более подробной информации об общем подходе.

Если вы примете этот подход, то у вас есть один объект, созданный для каждого возможного значения перечисления для всего жизненного цикла приложения. Затем вы можете использовать сравнение ссылок или любое другое сравнение, которое вам нужно, и вам не придется иметь дело с боксом.

Итак, для вашего примера вы можете иметь что-то вроде

class Color
{
    public static Color Red { get; } = new Color();
    public static Color Green { get; } = new Color();

    private Color()
    {
    }
}

Затем любые сравнения между значениями перечисления определяются по определению между двумя объектами, поэтому во время сравнения не происходит никакого бокса/распаковки. И теперь у вас определенно есть четкое различие между различными типами перечислений - Fruit.Apple и Vegetable.Tomato никогда не сравнится с равными.

Ответ 3

Вам нужно бросить, что практически не влияет на производительность. (даже если вы боксировали... чего бы вы не были... это оказало бы очень небольшое влияние.)

Если вы беспокоитесь, используйте общий метод.

TEnum GetEnum<TEnum>() where TEnum : struct

Затем вы вернетесь назад, который вы ожидаете, и не будет кастинг или бокс.

Ответ 4

Итак, у вас есть:

System.Enum myEnum = ...;

и вы знаете, что myEnum - это уже некоторое значение перечисления в боксе. Вы хотите сравнить это с Color.Red, не создавая в нем еще одну ячейку с Color.Red. Вы можете сделать это с помощью:

if (myEnum is Color && (Color)myEnum == Color.Red)
{
  ...
}

Ключевое слово is будет повторно использовать существующее поле. Преобразование из System.Enum в Color - это преобразование unboxing (не уверен, что проверка типа не будет выполняться дважды, но мы действительно действительно микро-оптимизируем здесь). == здесь проведет простое целочисленное сравнение двух числовых значений (например, два 32-битных целых числа, в зависимости от базового интегрального типа Color), находящихся в стеке вызовов.

Ответ 5

Не могли бы вы просто проверить

if (myEnum.GetType () != Fruit.Apple.GetType ()) ...

Ответ 6

Вы можете проверить тип Enum в условии if:

if (myEnum is Fruit && myEnum.Equals(Fruit.Apple))
{
    // Code should reach this point 
    // even though they're the same underlying int values

    Log("Works!");
}

Ответ 7

Вы могли бы GetEnum вернуть тип object вместо типа Enum и отбросить до int.

enum Fruit
{
    Apple = 0,
    Banana = 1,
    Orange = 2
}

enum Vegetable
{
    Tomato = 0,
    Carrot = 1,
    Celery = 2
}

object GetEnum()
{
    // for example
    return Fruit.Banana;
}

...

var myEnum = GetEnum();
Vegetable veg = Vegetable.Carrot;

if ((int)myEnum == (int)veg)
{
    Console.Write("SAME");
}

Не уверен, сколько издержек это влечет за собой.