Chcę zrobić coś takiego: mam wyliczenia z połączonymi oflagowanymi 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łem zrobić:
MyEnum tester = MyEnum.FlagA | MyEnum.FlagB
if( tester.IsSet( MyEnum.FlagA ) )
//act on flag a
Niestety, C # jest generyczny, w którym ograniczenia nie mają ograniczenia wyliczeniowego, tylko klasa i struktura. C # nie widzi wyliczeń jako struktur (mimo że są to typy wartości), więc nie mogę dodawać takich typów rozszerzeń.
Czy ktoś zna obejście?
struct
dobrego.Odpowiedzi:
EDYCJA: To jest teraz dostępne w wersji 0.0.0.2 UnconstrainedMelody.
(Zgodnie z prośbą zawartą w moim poście na blogu dotyczącym ograniczeń wyliczeniowych . Poniżej zamieściłem podstawowe fakty, aby udzielić niezależnej odpowiedzi.
Najlepszym rozwiązaniem jest zaczekanie, aż dołączę go do UnconstrainedMelody 1 . Jest to biblioteka, która przyjmuje kod C # z „fałszywymi” ograniczeniami, takimi jak
where T : struct, IEnumConstraint
i zamienia go w
where T : struct, System.Enum
poprzez krok po kompilacji.
Nie powinno być zbyt trudno to napisać
IsSet
... chociaż obsługa flag zarównoInt64
opartych, jak iUInt64
opartych może być trudną częścią. (Wyczuwam nadchodzące metody pomocnicze, w zasadzie pozwalające mi traktować wyliczenie wszystkich flag tak, jakby miały podstawowy typUInt64
.)Jakie zachowanie byś chciał, gdybyś zadzwonił
? Czy powinien sprawdzić, czy wszystkie określone flagi są ustawione? Takie byłoby moje oczekiwanie.
Spróbuję to zrobić dziś wieczorem w drodze do domu ... Mam nadzieję, że będę miał szybki rzut oka na użyteczne metody wyliczania, aby szybko doprowadzić bibliotekę do użytecznego standardu, a potem trochę się zrelaksować.
EDYCJA: Nawiasem mówiąc, nie jestem pewien
IsSet
co do nazwy. Opcje:Myśli mile widziane. Jestem pewien, że minie trochę czasu, zanim cokolwiek zostanie osadzone w kamieniu ...
1 lub oczywiście prześlij jako łatkę ...
źródło
colors.HasAny(Colors.Red | Colors.Blue)
wygląda na bardzo czytelny kod.=)
where T : System.Enum
. To było już napisane w innym miejscu wątku; pomyślałem, że powtórzę to tutaj.Od wersji C # 7.3 jest teraz wbudowany sposób dodawania ograniczeń wyliczeniowych:
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
źródło
Darren, to zadziałałoby, gdyby typy były specyficznymi wyliczeniami - aby ogólne wyliczenia działały, musisz rzutować je na ints (lub bardziej prawdopodobne uint), aby wykonać obliczenia boolowskie:
public static bool IsSet( this Enum input, Enum matchTo ) { return ( Convert.ToUInt32( input ) & Convert.ToUInt32( matchTo ) ) != 0; }
źródło
Convert.ToUInt32
nie znalazłem nigdzie indziej. AFAIK, to jedyne przyzwoite rozwiązanie Pre-Net-4, które działa również w VB. BTW, jeślimatchTo
może mieć wiele bitów flaga, a następnie zastąpić!= 0
z== Convert.ToUInt32(matchTo)
.Convert.ToUInt32
użyte z wyliczeniem użyjeConvert.ToUInt32(object)
przeciążenia, co oznacza, że środowisko CLR najpierw zapakuje te wartości przed przekazaniem ich doToUInt32
metody. W większości przypadków nie ma to znaczenia, ale dobrze jest wiedzieć, że GC będzie raczej zajęty, jeśli używasz czegoś takiego do analizowania milionów wyliczeń na sekundę.Właściwie jest to możliwe za pomocą brzydkiej sztuczki. Nie można go jednak używać w przypadku metod rozszerzających.
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 podać
Enums<Temp>
prywatny konstruktor i publiczną dziedziczoną klasę abstrakcyjną zagnieżdżoną zTemp
asEnum
, aby zapobiec dziedziczeniu wersji dla nie-wyliczeń.źródło
Możesz to osiągnąć za pomocą IL Weaving i ExtraConstraints
Pozwala napisać ten kod
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 { } }
źródło
Począwszy od C # 7,3, można użyć ograniczenia wyliczenia w typach ogólnych:
public static TEnum Parse<TEnum>(string value) where TEnum : Enum { return (TEnum) Enum.Parse(typeof(TEnum), value); }
Jeśli chcesz użyć wyliczenia o wartości Nullable, musisz zostawić ograniczenie struktury oryginalnej:
public static TEnum? TryParse<TEnum>(string value) where TEnum : struct, Enum { if( Enum.TryParse(value, out TEnum res) ) return res; else return null; }
źródło
To nie odpowiada na pierwotne pytanie, ale w .NET 4 jest teraz metoda o nazwie Enum.HasFlag, która robi to, co próbujesz zrobić w twoim przykładzie
źródło
flag
. NET 4.0 ma już pięć lat.Sposób, w jaki to robię, polega na umieszczeniu ograniczenia struct, a następnie sprawdzeniu, czy T jest wyliczeniem w czasie wykonywania. Nie eliminuje to całkowicie problemu, ale w pewnym stopniu go zmniejsza
źródło
Używając oryginalnego kodu, wewnątrz metody możesz również użyć odbicia, aby sprawdzić, czy T jest wyliczeniem:
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; } }
źródło
Oto kod, który właśnie stworzyłem, który wydaje się działać tak, jak chcesz, bez konieczności robienia niczego zbyt szalonego. Nie ogranicza się tylko do wyliczeń 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."); } }
źródło
jeśli ktoś potrzebuje generycznego IsSet (utworzonego po wyjęciu z pudełka, można go ulepszyć), lub string do konwersji Enum onfly (który wykorzystuje EnumConstraint przedstawiony 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ś nadal potrzebuje przykładu gorącego, aby utworzyć ograniczenie kodowania Enum:
using System; /// <summary> /// would be same as EnumConstraint_T<Enum>Parse<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.
źródło
Chciałem tylko dodać Enum jako ogólne ograniczenie.
Chociaż jest to tylko dla małej metody pomocniczej, użycie
ExtraConstraints
jest dla mnie trochę za dużo.Postanowiłem po prostu utworzyć
struct
ograniczenie i dodać sprawdzanie w czasie wykonywaniaIsEnum
. Aby przekonwertować zmienną z T na Enum, najpierw rzutuję ją na obiekt.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()); }
źródło