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

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

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

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

10.08.2008 17:14:10
Кит: скачать версию 0.0.0.2 UnconstrainedMelody - я реализовал HasAll и HasAny. Наслаждаться.
Jon Skeet 11.09.2009 21:22:49
Что вы подразумеваете под «C # не видит перечисления как структуры»? Вы можете использовать перечислимые типы в качестве параметров типа, которые structпросто ограничены .
Timwi 25.09.2010 19:38:28
проверьте эту статью здесь: codeproject.com/KB/cs/ExtendEnum.aspx «IsValidEnumValue» или «IsFlagsEnumDefined» методы, вероятно, ответ на ваш вопрос.
dmihailescu 1.07.2011 14:44:08
Проголосуйте за эту идею с пользовательскими счетами , если хотите, чтобы однажды она была встроена в .net.
Matthieu 24.04.2014 14:02:50
C # 7.3 вводит ограничения перечисления.
Marc Sigrist 10.04.2018 12:02:57
12 ОТВЕТОВ
РЕШЕНИЕ

РЕДАКТИРОВАТЬ: Это сейчас в версии 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, чтобы быстро привести библиотеку к пригодному для использования стандарту, а затем немного расслабиться.

EDIT: я не уверен, IsSetкак имя, кстати. Опции:

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

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


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

48
29.03.2016 05:40:34
Вы должны были пойти и упомянуть PostSharp LOL: o postsharp.org/blog/generic-constraints-for-enums-and-delegates
Sam Harwell 11.09.2009 09:34:56
Или на самом деле проще HasAny () и HasAll ()
Keith 11.09.2009 10:39:53
Да, я согласен, что это даже лучше. colors.HasAny(Colors.Red | Colors.Blue)выглядит как очень читаемый код. =)
Blixt 11.09.2009 10:49:09
Да, я люблю HasAny и HasAll тоже. Пойдем с этим.
Jon Skeet 11.09.2009 10:56:42
Начиная с C # 7.3 (выпущен в мае 2018 года), можно использовать ограничение where T : System.Enum. Это уже было написано в другом месте в потоке; Просто подумал, что я повторю это здесь.
Jeppe Stig Nielsen 29.05.2018 10:00:07

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

public static bool IsSet( this Enum input, Enum matchTo )
{
    return ( Convert.ToUInt32( input ) & Convert.ToUInt32( matchTo ) ) != 0;
}
16
10.08.2008 22:53:05
А если у вас смешное количество флагов, вы можете вызвать GetTypeCode () для аргументов и Convert.ToUint64 ()
Kit 10.09.2009 02:42:31
Круто, комбинация «Enum» и Convert.ToUInt32я больше нигде не нашли. AFAIK, Это единственное достойное решение Pre-Net-4, которое также работает в VB. Кстати, если matchToможет иметь несколько битов флага, то заменить != 0на == Convert.ToUInt32(matchTo).
ToolmakerSteve 19.03.2014 06:56:17
Обратите внимание, что Convert.ToUInt32при использовании перечисления будет использоваться Convert.ToUInt32(object)перегрузка, что означает, что CLR сначала поместит эти значения в ячейки, а затем передаст их ToUInt32методу. В большинстве случаев это не имеет значения, но хорошо знать, что вы будете держать GC довольно занятым, если будете использовать что-то подобное для анализа миллионов перечислений в секунду.
Groo 18.12.2014 15:08:06

Используя исходный код, внутри метода вы также можете использовать отражение, чтобы проверить, что 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;
    }
}
1
28.08.2008 15:19:36
Спасибо, но это превращает проблему времени компиляции (ограничение where) в проблему времени выполнения (ваше исключение). Кроме того, вам все равно нужно преобразовать входные данные в целые, прежде чем вы сможете что-либо с ними сделать.
Keith 11.09.2009 11:13:11

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

3
27.07.2009 14:02:01
где T: struct, IComparable, IFormattable, IConvertible - это самое близкое к перечислению значение :)
Kit 10.09.2009 02:39:02

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

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, чтобы предотвратить унаследованные версии для не перечислений.

10
13.09.2009 02:41:25

Вот код, который я только что написал, который, кажется, работает так, как вы хотите, без необходимости делать что-то слишком сумасшедшее. Он не ограничивается только перечислениями, установленными как 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
13.09.2009 03:57:16

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

4
20.11.2009 11:08:24
Проголосовал, потому что на данный момент, большинство всех должны использовать .NET 4 (или выше), и поэтому они должны использовать этот метод, а не пытаться взломать его вместе.
CptRobby 20.11.2014 20:01:27
Upvoted. Однако их решение использует бокс аргумента flag. .NET 4.0 сейчас пять лет.
Jeppe Stig Nielsen 19.04.2015 08:57:50

Вы можете добиться этого, используя 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
    {
    }
}
8
19.03.2014 10:31:58

Я просто хотел добавить 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());
    }
0
24.07.2017 09:46:31

если кому-то нужен универсальный IsSet (созданный из коробки на лету, можно улучшить) и / или преобразование строки в Enum 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
22.12.2016 08:42:06

Начиная с 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

23
11.05.2018 09:33:08

Начиная с 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;
}
5
18.05.2018 13:04:18