Czy ktoś zna dobre obejście braku ograniczenia ogólnego enum?

Chcę zrobić coś takiego: mam enums z połączonymi wartościami.

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;
    }
}

Więc mogę zrobić:

MyEnum tester = MyEnum.FlagA | MyEnum.FlagB

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

Niestety C#'S generic gdzie ograniczenia nie mają ograniczeń enum, tylko class i struct. C# nie widzi enum jako struktury (mimo że są to typy wartości), więc nie mogę dodawać takich typów rozszerzeń.

Ktoś zna obejście?
Author: Keith, 2008-08-10

12 answers

EDIT: to jest teraz na żywo w wersji 0.0.0.2 Unstrainedmelody.

(zgodnie z życzeniem na moim blogu o ograniczeniach enum. Poniżej zamieściłem podstawowe fakty, aby uzyskać samodzielną odpowiedź.)

Najlepszym rozwiązaniem jest poczekać aż dołączę go do 1. Jest to biblioteka, która pobiera kod C# z" fałszywymi " ograniczeniami, takimi jak

where T : struct, IEnumConstraint

I zamienia go w

where T : struct, System.Enum

Przez postbuild krok.

Nie powinno być trudno napisać IsSet... chociaż catering dla FLAG Int64 i UInt64 może być trudną częścią. (Czuję, że pojawiają się jakieś metody pomocnicze, które pozwalają mi traktować dowolne flagi enum tak, jakby miały bazowy typ UInt64.)

Jakie by było zachowanie, gdybyś zadzwonił

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

? Czy należy sprawdzić, czy wszystkie podane flagi są ustawione? To byłoby moje oczekiwanie.

Postaram się to zrobić po drodze dziś w domu... Mam nadzieję na szybki blitz NA przydatne metody enum, aby uzyskać bibliotekę do użytecznego standardu szybko, a następnie zrelaksować się trochę.

EDIT: nie jestem pewien IsSet jako nazwy, tak przy okazji. Opcje:

  • zawiera
  • zawiera
  • HasFlag (lub HasFlags)
  • Jest to jedna z najbardziej rozpoznawalnych postaci na świecie.]}

Myśli mile widziane. Jestem pewien, że minie trochę czasu, zanim cokolwiek się ułoży...


1 lub złożyć jako łatka, oczywiście...

 47
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-29 05:40:34

Darren, to by działało, gdyby typy były konkretnymi wyliczeniami - aby wyliczenia ogólne działały, musisz je oddać do ints (lub bardziej prawdopodobnego uint), aby wykonać matematykę logiczną:

public static bool IsSet( this Enum input, Enum matchTo )
{
    return ( Convert.ToUInt32( input ) & Convert.ToUInt32( matchTo ) ) != 0;
}
 16
Author: Ronnie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-10 22:53:05

Właściwie, to jest możliwe, z brzydką sztuczką. Jednak nie może być stosowany do metod rozszerzenia.

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")

Jeśli chcesz, możesz dać Enums<Temp> prywatny konstruktor i publiczną zagnieżdżoną abstrakcyjną klasę dziedziczoną z Temp jako Enum, aby zapobiec dziedziczonym wersjom dla nie-enum.

 9
Author: SLaks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-13 02:41:25

Można to osiągnąć za pomocą IL I ExtraConstraints

Pozwala na napisanie tego kodu

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

Co jest kompilowane

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

    public void MethodWithEnumConstraint<T>() where T: struct, Enum
    {
    }
}
 8
Author: Simon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-19 10:31:58

W C# 7.3 istnieje wbudowany sposób dodawania ograniczeń enum:

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

Źródło: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/where-generic-type-constraint

 7
Author: Ivan Ferić,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-11 09:33:08

To nie odpowiada na pierwotne pytanie, ale obecnie istnieje metoda w. Net 4 o nazwie Enum.HasFlag który robi to, co próbujesz zrobić w swoim przykładzie

 4
Author: Phil Devaney,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-20 11:08:24

Sposób, w jaki to robię, to wprowadzenie ograniczenia struct, a następnie sprawdzenie, czy T jest enum w czasie wykonywania. To nie eliminuje problemu całkowicie, ale zmniejsza go nieco

 3
Author: thecoop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-27 14:02:01

W C# 7.3 można używać ograniczenia Enum na typach generycznych:

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

Jeśli chcesz użyć Nullable enum, musisz opuścić ograniczenie struktury:

public static TEnum? TryParse<TEnum>(string value) where TEnum : struct, Enum
{
    if( Enum.TryParse(value, out TEnum res) )
        return res;
    else
        return null;
}
 2
Author: Mik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-18 13:04:18

Używając oryginalnego kodu, wewnątrz metody Możesz również użyć reflection, aby sprawdzić, że T jest enum:

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
Author: Scott Dorman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-28 15:19:36

Oto kod, który właśnie zrobiłem, który wydaje się działać tak, jak chcesz, bez konieczności robienia czegokolwiek zbyt szalonego. Nie ogranicza się tylko do enum ustawionych jako flagi, ale w razie potrzeby zawsze można wprowadzić czek.

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
Author: Brian Surowiec,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-13 03:57:16

Jeśli ktoś potrzebuje generic IsSet (stworzony z pudełka w locie może być ulepszony), i lub string do Enum onfly conversion (który używa enumconstraint przedstawionego poniżej):

  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);

    }

  }

Jeśli ktoś jeszcze potrzebuje przykład hot do stworzenia enum kodowania ograniczenia:

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

}
Mam nadzieję, że to komuś pomoże.
 0
Author: SoLaR,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-22 08:42:06

Chciałem tylko dodać Enum jako ogólne Ograniczenie.

Podczas gdy jest to tylko dla małej metody pomocniczej wykorzystującej ExtraConstraints to dla mnie za dużo.

Postanowiłem po prostu utworzyć ograniczenie struct i dodać sprawdzenie dla IsEnum. Do konwersji zmiennej z T na Enum wrzucam ją najpierw do obiektu.

    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
Author: Jürgen Steinblock,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-24 09:46:31