Кто-нибудь знает хороший обходной путь для отсутствия общего ограничения enum? - PullRequest
84 голосов
/ 10 августа 2008

Я хочу сделать что-то вроде этого: у меня есть перечисления с комбинированными помеченными значениями.

public static class EnumExtension
{
    public static bool IsSet<T>( this T input, T matchTo ) 
        where T:enum //the constraint I want that doesn't exist in C#3
    {    
        return (input & matchTo) != 0;
    }
}

Тогда я мог бы сделать:

MyEnum tester = MyEnum.FlagA | MyEnum.FlagB

if( tester.IsSet( MyEnum.FlagA ) )
    //act on flag a

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

Кто-нибудь знает обходной путь?

Ответы [ 12 ]

47 голосов
/ 11 сентября 2009

РЕДАКТИРОВАТЬ: Это теперь живое в версии 0.0.0.2 UnconstrainedMelody.

(В соответствии с просьбой в моем блоге об ограничениях enum . Ниже приведены основные факты для самостоятельного ответа.)

Лучшее решение - подождать, пока я включу его в UnconstrainedMelody 1 . Это библиотека, которая принимает код C # с «поддельными» ограничениями, такими как

where T : struct, IEnumConstraint

и превращает его в

where T : struct, System.Enum

через шаг пост-сборки.

Не должно быть слишком сложно написать IsSet ... хотя удовлетворение флагов, основанных как на Int64, так и на UInt64, может оказаться сложной задачей. (Я чувствую, как появляются некоторые вспомогательные методы, в основном позволяющие мне обрабатывать любые перечисления флагов, как если бы они имели базовый тип UInt64.)

Как бы вы хотели, чтобы было поведение, если бы вы позвонили

tester.IsSet(MyFlags.A | MyFlags.C)

? Следует ли проверить, что все заданные флаги установлены? Это было бы моим ожиданием.

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

РЕДАКТИРОВАТЬ: Я не уверен насчет IsSet как имя, кстати. Опции:

  • Включает
  • Содержит
  • HasFlag (или HasFlags)
  • IsSet (это, конечно, вариант)

Мысли приветствуются. Я уверен, что пройдет какое-то время, прежде чем что-то будет в камне ...


1 или отправьте его как патч, конечно ...

16 голосов
/ 11 августа 2008

Даррен, это сработало бы, если бы типы были конкретными перечислениями - для того, чтобы общие перечисления работали, вы должны привести их к целым числам (или, более вероятно, к uint) для выполнения логической математики:

public static bool IsSet( this Enum input, Enum matchTo )
{
    return ( Convert.ToUInt32( input ) & Convert.ToUInt32( matchTo ) ) != 0;
}
11 голосов
/ 11 мая 2018

Начиная с C # 7.3, теперь есть встроенный способ добавления ограничений перечисления:

public class UsingEnum<T> where T : System.Enum { }

источник: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/where-generic-type-constraint

9 голосов
/ 13 сентября 2009

На самом деле, это возможно, с уродливым трюком. Однако его нельзя использовать для методов расширения.

public abstract class Enums<Temp> where Temp : class {
    public static TEnum Parse<TEnum>(string name) where TEnum : struct, Temp {
        return (TEnum)Enum.Parse(typeof(TEnum), name); 
    }
}
public abstract class Enums : Enums<Enum> { }

Enums.IsSet<DateTimeKind>("Local")

Если вы хотите, вы можете дать Enums<Temp> закрытый конструктор и открытый вложенный абстрактный унаследованный класс с Temp как Enum, чтобы предотвратить унаследованные версии для не перечислений.

8 голосов
/ 20 июля 2012

Этого можно добиться с помощью IL Weaving и ExtraConstraints

Позволяет написать этот код

public class Sample
{
    public void MethodWithDelegateConstraint<[DelegateConstraint] T> ()
    {        
    }
    public void MethodWithEnumConstraint<[EnumConstraint] T>()
    {
    }
}

Что компилируется

public class Sample
{
    public void MethodWithDelegateConstraint<T>() where T: Delegate
    {
    }

    public void MethodWithEnumConstraint<T>() where T: struct, Enum
    {
    }
}
4 голосов
/ 20 ноября 2009

Это не отвечает на первоначальный вопрос, но теперь в .NET 4 есть метод с именем Enum.HasFlag , который делает то, что вы пытаетесь сделать в вашем примере

3 голосов
/ 27 июля 2009

Как я это делаю, это устанавливаю структурное ограничение, затем проверяю, что T - это enum во время выполнения. Это не устраняет проблему полностью, но несколько уменьшает ее

2 голосов
/ 18 мая 2018

Начиная с C # 7.3, вы можете использовать ограничение Enum для универсальных типов:

public static TEnum Parse<TEnum>(string value) where TEnum : Enum
{
    return (TEnum) Enum.Parse(typeof(TEnum), value);
}

Если вы хотите использовать Nullable enum, вы должны оставить ограничение orginial struct:

public static TEnum? TryParse<TEnum>(string value) where TEnum : struct, Enum
{
    if( Enum.TryParse(value, out TEnum res) )
        return res;
    else
        return null;
}
1 голос
/ 13 сентября 2009

Вот код, который я только что написал, который, кажется, работает так, как вы хотите, без необходимости делать что-то слишком сумасшедшее. Он не ограничен только перечислениями, установленными как Flags, но при необходимости всегда можно поставить чек.

public static class EnumExtensions
{
    public static bool ContainsFlag(this Enum source, Enum flag)
    {
        var sourceValue = ToUInt64(source);
        var flagValue = ToUInt64(flag);

        return (sourceValue & flagValue) == flagValue;
    }

    public static bool ContainsAnyFlag(this Enum source, params Enum[] flags)
    {
        var sourceValue = ToUInt64(source);

        foreach (var flag in flags)
        {
            var flagValue = ToUInt64(flag);

            if ((sourceValue & flagValue) == flagValue)
            {
                return true;
            }
        }

        return false;
    }

    // found in the Enum class as an internal method
    private static ulong ToUInt64(object value)
    {
        switch (Convert.GetTypeCode(value))
        {
            case TypeCode.SByte:
            case TypeCode.Int16:
            case TypeCode.Int32:
            case TypeCode.Int64:
                return (ulong)Convert.ToInt64(value, CultureInfo.InvariantCulture);

            case TypeCode.Byte:
            case TypeCode.UInt16:
            case TypeCode.UInt32:
            case TypeCode.UInt64:
                return Convert.ToUInt64(value, CultureInfo.InvariantCulture);
        }

        throw new InvalidOperationException("Unknown enum type.");
    }
}
1 голос
/ 17 августа 2008

Используя исходный код, внутри метода вы также можете использовать отражение, чтобы проверить, что T является перечислением:

public static class EnumExtension
{
    public static bool IsSet<T>( this T input, T matchTo )
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException("Must be an enum", "input");
        }
        return (input & matchTo) != 0;
    }
}
...