Хтось знає хороший спосіб вирішення проблеми з відсутністю загального обмеження переліку?


90

Що я хочу зробити, це приблизно так: у мене є переліки зі спільними позначеними значеннями.

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 # не розглядає перелічення як структури (навіть якщо вони є типами значень), тому я не можу додавати такі типи розширень.

Хтось знає обхідний шлях?


2
Кіт: завантажте версію 0.0.0.2 UnconstrainedMelody - я реалізував HasAll та HasAny. Насолоджуйтесь.
Джон Скіт,

Що ви маєте на увазі під словом "C # не сприймає перелічення як структури"? Ви можете використовувати типи перерахувань як параметри типу, які обмежуються structпросто точно.
Timwi

перегляньте цю статтю тут: codeproject.com/KB/cs/ExtendEnum.aspx Методи 'IsValidEnumValue' або 'IsFlagsEnumDefined' - це, мабуть, відповідь на ваше запитання.
dmihailescu

1
Проголосуйте за цю ідею користувацького голосу , якщо ви хочете, щоб її одного разу вбудували в .net.
Матьє

11
C # 7.3 вводить обмеження перерахування.
Marc Sigrist

Відповіді:


49

РЕДАГУВАТИ: Це зараз працює у версії 0.0.0.2 UnconstrainedMelody.

(Як вимагається у моєму дописі в блозі про обмеження переліку. Для окремої відповіді я включив основні факти нижче).

Найкраще рішення - почекати, поки я включу його до UnconstrainedMelody 1 . Це бібліотека, яка приймає код C # з "фальшивими" обмеженнями, такими як

where T : struct, IEnumConstraint

і перетворює його на

where T : struct, System.Enum

через крок після побудови.

Це не повинно бути надто складно писати IsSet... хоча обслуговування обох прапорів як на Int64базі, так і на UInt64базі може бути складною частиною. (Я відчуваю запах деяких допоміжних методів, які в основному дозволяють мені обробляти будь-які прапорці enum так, ніби він має базовий тип UInt64.)

Якою б ви хотіли, щоб поведінка була, якщо ви телефонували

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

? Чи слід перевіряти, що встановлені всі вказані прапори? Це було б моє сподівання.

Я спробую зробити це по дорозі додому сьогодні ввечері ... Я сподіваюся швидко проаналізувати корисні методи перерахування, щоб швидко довести бібліотеку до придатного для використання стандарту, а потім трохи відпочити.

EDIT: IsSetДо речі, я не впевнений , що це ім’я. Варіанти:

  • Включає
  • Містить
  • HasFlag (або HasFlags)
  • IsSet (це, звичайно, варіант)

Думки вітаються. Я впевнений, що все одно щось в кам’яному положенні пройде деякий час ...


1 або надішліть його як патч, звичайно ...


1
Вам довелося піти і згадати PostSharp LOL: o postsharp.org/blog/generic-constraints-for-enums-and-delegates
Сем

1
Або насправді простіші HasAny () та HasAll ()
Кіт

1
Так, я згоден, що це навіть краще. colors.HasAny(Colors.Red | Colors.Blue)виглядає як дуже читабельний код. =)
Blixt

1
Так, мені теж подобаються HasAny та HasAll. Піде з цим.
Джон Скіт,

5
Починаючи з C # 7.3 (випущений у травні 2018 року), можна використовувати обмеження where T : System.Enum. Це вже було написано в іншому місці потоку; просто думав, що повторю це тут.
Джеппе Стіг Нільсен,


16

Даррене, це працювало б, якби типи були конкретними переліченнями - щоб загальні перерахування працювали, вам потрібно передати їх у ints (або, швидше за все, uint), щоб виконати булеву математику:

public static bool IsSet( this Enum input, Enum matchTo )
{
    return ( Convert.ToUInt32( input ) & Convert.ToUInt32( matchTo ) ) != 0;
}

1
І якщо у вас смішна кількість прапорів, ви можете викликати GetTypeCode () для аргументів і Convert.ToUint64 ()
комплект

Дивовижно, комбінація "Enum" і Convert.ToUInt32я більше ніде не знайшла. AFAIK, це єдине гідне рішення Pre-Net-4, яке також працює в VB. До речі, якщо matchToможе мати кілька бітів прапора, то замініть != 0на == Convert.ToUInt32(matchTo).
ToolmakerSteve

1
Зверніть увагу, що при Convert.ToUInt32використанні з переліченням буде використовуватися Convert.ToUInt32(object)перевантаження, це означає, що CLR спочатку встановить ці значення перед передачею, а потім до ToUInt32методу. У більшості випадків це не матиме значення, але добре знати, що ви будете робити GC досить зайнятим, якщо використовуєте щось подібне для синтаксичного аналізу мільйонів переліків в секунду.
Groo

10

Насправді це можливо, з потворною хитрістю. Однак його не можна використовувати для методів розширення.

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>приватний конструктор та загальнодоступний вкладений абстрактний успадкований клас з Tempas Enum, щоб запобігти успадкованим версіям для неперелічених.


8

Ви можете досягти цього за допомогою 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
    {
    }
}

6

Починаючи з C # 7.3, ви можете використовувати обмеження Enum для загальних типів:

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

Якщо ви хочете використовувати Nullable перерахування, ви повинні залишити обмеження 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;
}

4

Це не відповідає на вихідне запитання, але зараз у .NET 4 існує метод Enum.HasFlag, який робить те, що ви намагаєтесь зробити у своєму прикладі


Проголосував за те, що на даний момент більшість людей повинні використовувати .NET 4 (або новішу версію), і тому вони повинні використовувати цей метод, а не намагатися його зламати разом.
CptRobby

Проголосував. Однак їх рішення використовує бокс аргументу flag. .NET 4.0 наразі п’ять років.
Jeppe Stig Nielsen,

3

Як я це роблю, я встановлюю обмеження struct, а потім перевіряю, що T є переліком під час виконання. Це не усуває проблему повністю, але дещо зменшує


7
де T: struct, IComparable, IFormattable, IConvertible - це найближче до перерахування :)
Kit

1

Використовуючи свій оригінальний код, всередині методу ви також можете використовувати відображення, щоб перевірити, що 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;
    }
}

2
Дякую, але це перетворює проблему з часом компіляції (обмеження where) на час виконання (ваш виняток). Також вам все одно потрібно буде перетворити вхідні дані на ints, перш ніж ви зможете з ними щось робити.
Кіт

1

Ось деякий код, який я щойно зробив, здається, працює так, як ти хочеш, без необхідності робити щось надто божевільне. Це не обмежується лише переліченнями, встановленими як прапори, але завжди може бути встановлений чек, якщо це необхідно.

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.");
    }
}

0

якщо комусь потрібен загальний IsSet (створений з коробки на ньому можна вдосконалити), або або рядок до перетворення Enfly onfly (який використовує EnumConstraint, представлений нижче):

  public class TestClass
  { }

  public struct TestStruct
  { }

  public enum TestEnum
  {
    e1,    
    e2,
    e3
  }

  public static class TestEnumConstraintExtenssion
  {

    public static bool IsSet<TEnum>(this TEnum _this, TEnum flag)
      where TEnum : struct
    {
      return (((uint)Convert.ChangeType(_this, typeof(uint))) & ((uint)Convert.ChangeType(flag, typeof(uint)))) == ((uint)Convert.ChangeType(flag, typeof(uint)));
    }

    //public static TestClass ToTestClass(this string _this)
    //{
    //  // #generates compile error  (so no missuse)
    //  return EnumConstraint.TryParse<TestClass>(_this);
    //}

    //public static TestStruct ToTestStruct(this string _this)
    //{
    //  // #generates compile error  (so no missuse)
    //  return EnumConstraint.TryParse<TestStruct>(_this);
    //}

    public static TestEnum ToTestEnum(this string _this)
    {
      // #enum type works just fine (coding constraint to Enum type)
      return EnumConstraint.TryParse<TestEnum>(_this);
    }

    public static void TestAll()
    {
      TestEnum t1 = "e3".ToTestEnum();
      TestEnum t2 = "e2".ToTestEnum();
      TestEnum t3 = "non existing".ToTestEnum(); // default(TestEnum) for non existing 

      bool b1 = t3.IsSet(TestEnum.e1); // you can ommit type
      bool b2 = t3.IsSet<TestEnum>(TestEnum.e2); // you can specify explicite type

      TestStruct t;
      // #generates compile error (so no missuse)
      //bool b3 = t.IsSet<TestEnum>(TestEnum.e1);

    }

  }

Якщо хтось все ще потребує прикладу гарячого для створення обмеження кодування Enum:

using System;

/// <summary>
/// would be same as EnumConstraint_T&lt;Enum>Parse&lt;EnumType>("Normal"),
/// but writen like this it abuses constrain inheritence on System.Enum.
/// </summary>
public class EnumConstraint : EnumConstraint_T<Enum>
{

}

/// <summary>
/// provides ability to constrain TEnum to System.Enum abusing constrain inheritence
/// </summary>
/// <typeparam name="TClass">should be System.Enum</typeparam>
public abstract class EnumConstraint_T<TClass>
  where TClass : class
{

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

  public static bool TryParse<TEnum>(string value, out TEnum evalue)
    where TEnum : struct, TClass // struct is required to ignore non nullable type error
  {
    evalue = default(TEnum);
    return Enum.TryParse<TEnum>(value, out evalue);
  }

  public static TEnum TryParse<TEnum>(string value, TEnum defaultValue = default(TEnum))
    where TEnum : struct, TClass // struct is required to ignore non nullable type error
  {    
    Enum.TryParse<TEnum>(value, out defaultValue);
    return defaultValue;
  }

  public static TEnum Parse<TEnum>(string value, TEnum defaultValue = default(TEnum))
    where TEnum : struct, TClass // struct is required to ignore non nullable type error
  {
    TEnum result;
    if (Enum.TryParse<TEnum>(value, out result))
      return result;
    return defaultValue;
  }

  public static TEnum Parse<TEnum>(ushort value)
  {
    return (TEnum)(object)value;
  }

  public static sbyte to_i1<TEnum>(TEnum value)
  {
    return (sbyte)(object)Convert.ChangeType(value, typeof(sbyte));
  }

  public static byte to_u1<TEnum>(TEnum value)
  {
    return (byte)(object)Convert.ChangeType(value, typeof(byte));
  }

  public static short to_i2<TEnum>(TEnum value)
  {
    return (short)(object)Convert.ChangeType(value, typeof(short));
  }

  public static ushort to_u2<TEnum>(TEnum value)
  {
    return (ushort)(object)Convert.ChangeType(value, typeof(ushort));
  }

  public static int to_i4<TEnum>(TEnum value)
  {
    return (int)(object)Convert.ChangeType(value, typeof(int));
  }

  public static uint to_u4<TEnum>(TEnum value)
  {
    return (uint)(object)Convert.ChangeType(value, typeof(uint));
  }

}

сподіваюся, це комусь допомагає.


0

Я просто хотів додати Enum як загальне обмеження.

Хоча це лише для крихітного допоміжного методу ExtraConstraints для мене це занадто великі накладні витрати.

Я вирішив просто створити structобмеження та додати перевірку виконання для IsEnum. Для перетворення змінної з T на Enum я спочатку призначаю її об'єкту.

    public static Converter<T, string> CreateConverter<T>() where T : struct
    {
        if (!typeof(T).IsEnum) throw new ArgumentException("Given Type is not an Enum");
        return new Converter<T, string>(x => ((Enum)(object)x).GetEnumDescription());
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.