Utwórz metodę ogólną ograniczającą T do wyliczenia

1187

Buduję funkcję, aby rozszerzyć tę Enum.Parsekoncepcję

  • Umożliwia parsowanie wartości domyślnej w przypadku, gdy nie zostanie znaleziona wartość Enum
  • Rozróżnia małe i wielkie litery

Napisałem więc:

public static T GetEnumFromString<T>(string value, T defaultValue) where T : Enum
{
    if (string.IsNullOrEmpty(value)) return defaultValue;
    foreach (T item in Enum.GetValues(typeof(T)))
    {
        if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
    }
    return defaultValue;
}

Otrzymuję Ograniczenie błędu nie może być klasą specjalną System.Enum.

W porządku, ale czy istnieje obejście pozwalające na generyczne wyliczenie, czy też będę musiał naśladować Parsefunkcję i przekazać typ jako atrybut, co wymusza brzydkie wymagania boksowania w twoim kodzie.

EDYCJA Wszystkie poniższe sugestie zostały bardzo docenione, dzięki.

Ustawiłem się (opuściłem pętlę, aby zachować rozróżnianie wielkości liter - używam tego podczas analizowania XML)

public static class EnumUtils
{
    public static T ParseEnum<T>(string value, T defaultValue) where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum) throw new ArgumentException("T must be an enumerated type");
        if (string.IsNullOrEmpty(value)) return defaultValue;

        foreach (T item in Enum.GetValues(typeof(T)))
        {
            if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        }
        return defaultValue;
    }
}

EDYCJA: (16 lutego 2015 r.) Julien Lebosquain opublikował niedawno wymuszone przez kompilator ogólne rozwiązanie bezpieczeństwa typu w MSIL lub F # poniżej, które jest warte obejrzenia, i opinię. Usunę tę edycję, jeśli rozwiązanie pojawi się dalej na stronie.

johnc
źródło
10
Może powinieneś użyć ToUpperInvariant () zamiast ToLower () ...
Max Galkin
31
@Shimmy: jak tylko przekażesz typ wartości metodzie rozszerzenia, pracujesz nad jej kopią, więc nie możesz zmienić jej stanu.
Garo Yeriazarian,
4
Wiem, że to stary wątek, nie wiem, czy coś zmieniły, ale metody rozszerzeń działają dobrze dla typów wartości, na pewno nie zawsze mają one sens, ale użyłem „public static TimeSpan Seconds (this int x) { zwróć TimeSpan.FromSeconds (x);} ”, aby włączyć składnię„ Wait.For (5.Seconds ()) ... ”
Jens
6
Uświadomienie sobie, że nie było częścią pytania, ale można poprawić logikę pętli foreach za pomocą String.Equals z StringComparison.InvariantCultureIgnoreCase
Firestrand

Odpowiedzi:

1005

Ponieważ EnumType implementuje IConvertibleinterfejs, lepsza implementacja powinna wyglądać mniej więcej tak:

public T GetEnumFromString<T>(string value) where T : struct, IConvertible
{
   if (!typeof(T).IsEnum) 
   {
      throw new ArgumentException("T must be an enumerated type");
   }

   //...
}

Nadal pozwoli to na wdrożenie typów wartości IConvertible. Szanse są jednak rzadkie.

Vivek
źródło
2
Generyczne są dostępne od .NET 2.0. Dlatego są one również dostępne w Vb 2005.
Vivek
46
Cóż, uczyń to jeszcze bardziej ograniczonym, jeśli zdecydujesz się pójść tą ścieżką ... użyj "klasy TestClass <T> gdzie T: struct, IComparable, IFormattable, IConvertible"
Ricardo Nolde
106
Inną sugestią jest zdefiniowanie typu ogólnego za pomocą identyfikatora TEnum. Zatem: public TEnum GetEnumFromString <TEnum> (wartość ciągu) gdzie TEnum: struct, IConvertible, IComparible, IFormattable {}
Lisa
11
Niewiele zyskujesz, włączając inne interfejsy, ponieważ prawie wszystkie wbudowane typy wartości implementują wszystkie te interfejsy. Jest to szczególnie prawdziwe w przypadku ograniczeń ogólnej metody rozszerzenia, która jest niezwykle przydatna do obsługi wyliczeń, z wyjątkiem faktu, że te metody rozszerzenia są jak wirus, który infekuje wszystkie obiekty. IConvertable przynajmniej go trochę zawęża.
Russbishop
2
@SamIam: Kiedy pisałeś, ten wątek miał 6 i pół roku i miałeś rację, nie sprawdziłem czasu kompilacji żadnej z odpowiedzi. Dopiero 3 dni później, po 6 latach, dostałeś życzenie - patrz post Juliena Lebosquaina poniżej.
David I. McIntosh,
662

Ta funkcja jest wreszcie obsługiwana w C # 7.3!

Poniższy fragment kodu (z próbek dotnet ) pokazuje, jak:

public static Dictionary<int, string> EnumNamedValues<T>() where T : System.Enum
{
    var result = new Dictionary<int, string>();
    var values = Enum.GetValues(typeof(T));

    foreach (int item in values)
        result.Add(item, Enum.GetName(typeof(T), item));
    return result;
}

Pamiętaj, aby ustawić wersję językową w projekcie C # na wersję 7.3.


Oryginalna odpowiedź poniżej:

Spóźniłem się na grę, ale podjąłem wyzwanie, aby zobaczyć, jak można to zrobić. Nie jest to możliwe w C # (lub VB.NET, ale przewiń w dół do F #), ale jest to możliwe w MSIL. Napisałem tę małą .... rzecz

// license: http://www.apache.org/licenses/LICENSE-2.0.html
.assembly MyThing{}
.class public abstract sealed MyThing.Thing
       extends [mscorlib]System.Object
{
  .method public static !!T  GetEnumFromString<valuetype .ctor ([mscorlib]System.Enum) T>(string strValue,
                                                                                          !!T defaultValue) cil managed
  {
    .maxstack  2
    .locals init ([0] !!T temp,
                  [1] !!T return_value,
                  [2] class [mscorlib]System.Collections.IEnumerator enumerator,
                  [3] class [mscorlib]System.IDisposable disposer)
    // if(string.IsNullOrEmpty(strValue)) return defaultValue;
    ldarg strValue
    call bool [mscorlib]System.String::IsNullOrEmpty(string)
    brfalse.s HASVALUE
    br RETURNDEF         // return default it empty

    // foreach (T item in Enum.GetValues(typeof(T)))
  HASVALUE:
    // Enum.GetValues.GetEnumerator()
    ldtoken !!T
    call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
    call class [mscorlib]System.Array [mscorlib]System.Enum::GetValues(class [mscorlib]System.Type)
    callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Array::GetEnumerator() 
    stloc enumerator
    .try
    {
      CONDITION:
        ldloc enumerator
        callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
        brfalse.s LEAVE

      STATEMENTS:
        // T item = (T)Enumerator.Current
        ldloc enumerator
        callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
        unbox.any !!T
        stloc temp
        ldloca.s temp
        constrained. !!T

        // if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        callvirt instance string [mscorlib]System.Object::ToString()
        callvirt instance string [mscorlib]System.String::ToLower()
        ldarg strValue
        callvirt instance string [mscorlib]System.String::Trim()
        callvirt instance string [mscorlib]System.String::ToLower()
        callvirt instance bool [mscorlib]System.String::Equals(string)
        brfalse.s CONDITION
        ldloc temp
        stloc return_value
        leave.s RETURNVAL

      LEAVE:
        leave.s RETURNDEF
    }
    finally
    {
        // ArrayList's Enumerator may or may not inherit from IDisposable
        ldloc enumerator
        isinst [mscorlib]System.IDisposable
        stloc.s disposer
        ldloc.s disposer
        ldnull
        ceq
        brtrue.s LEAVEFINALLY
        ldloc.s disposer
        callvirt instance void [mscorlib]System.IDisposable::Dispose()
      LEAVEFINALLY:
        endfinally
    }

  RETURNDEF:
    ldarg defaultValue
    stloc return_value

  RETURNVAL:
    ldloc return_value
    ret
  }
} 

Która generuje funkcję, która wyglądałaby tak, gdyby był poprawny C #:

T GetEnumFromString<T>(string valueString, T defaultValue) where T : Enum

Następnie z następującym kodem C #:

using MyThing;
// stuff...
private enum MyEnum { Yes, No, Okay }
static void Main(string[] args)
{
    Thing.GetEnumFromString("No", MyEnum.Yes); // returns MyEnum.No
    Thing.GetEnumFromString("Invalid", MyEnum.Okay);  // returns MyEnum.Okay
    Thing.GetEnumFromString("AnotherInvalid", 0); // compiler error, not an Enum
}

Niestety oznacza to, że ta część kodu jest napisana w MSIL zamiast w C #, z jedyną dodatkową korzyścią, którą możesz ograniczyć tą metodą System.Enum. Jest to również coś w rodzaju bummera, ponieważ kompiluje się w osobny zestaw. Nie oznacza to jednak, że musisz go wdrożyć w ten sposób.

Usuwając linię .assembly MyThing{}i wywołując ilasmę w następujący sposób:

ilasm.exe /DLL /OUTPUT=MyThing.netmodule

dostajesz moduł sieci zamiast zestawu.

Niestety, VS2010 (i oczywiście wcześniej) nie obsługuje dodawania odniesień do modułu sieciowego, co oznacza, że ​​będziesz musiał zostawić go w 2 osobnych zestawach podczas debugowania. Jedynym sposobem na dodanie ich jako części zestawu byłoby samodzielne uruchomienie csc.exe za pomocą /addmodule:{files}argumentu wiersza poleceń. W skrypcie MSBuild nie byłoby to zbyt bolesne. Oczywiście, jeśli jesteś odważny lub głupi, możesz ręcznie uruchomić csc za każdym razem. Z pewnością komplikuje się, ponieważ wiele zestawów potrzebuje do niego dostępu.

Tak więc można to zrobić w .Net. Czy to warte dodatkowego wysiłku? Cóż, chyba pozwolę ci zdecydować o tym.


Rozwiązanie F # jako alternatywa

Dodatkowy kredyt: Okazuje się, że ogólne ograniczenie enumjest możliwe w co najmniej jednym innym języku .NET oprócz MSIL: F #.

type MyThing =
    static member GetEnumFromString<'T when 'T :> Enum> str defaultValue: 'T =
        /// protect for null (only required in interop with C#)
        let str = if isNull str then String.Empty else str

        Enum.GetValues(typedefof<'T>)
        |> Seq.cast<_>
        |> Seq.tryFind(fun v -> String.Compare(v.ToString(), str.Trim(), true) = 0)
        |> function Some x -> x | None -> defaultValue

Ten jest łatwiejszy w utrzymaniu, ponieważ jest dobrze znanym językiem z pełną obsługą Visual Studio IDE, ale nadal potrzebujesz osobnego projektu w swoim rozwiązaniu. Jednak naturalnie produkuje znacznie różne IL (kod jest bardzo różny) i opiera się na FSharp.Corebibliotece, która, podobnie jak każda inna biblioteka zewnętrzna, musi stać się częścią twojej dystrybucji.

Oto, jak możesz go użyć (w zasadzie to samo, co rozwiązanie MSIL) i pokazać, że nie działa poprawnie na strukturach synonimicznych inaczej:

// works, result is inferred to have type StringComparison
var result = MyThing.GetEnumFromString("OrdinalIgnoreCase", StringComparison.Ordinal);
// type restriction is recognized by C#, this fails at compile time
var result = MyThing.GetEnumFromString("OrdinalIgnoreCase", 42);
Christopher Currens
źródło
67
Tak, bardzo hardcore. Mam najwyższy szacunek dla kogoś, kto potrafi kodować w IL i wiem, w jaki sposób funkcje są obsługiwane na wyższym poziomie językowym - poziomie, który wielu z nas nadal uważa za niski poziom w aplikacjach, regułach biznesowych, interfejsach użytkownika, bibliotekach komponentów itp. .
TonyG
13
Naprawdę chciałbym wiedzieć, dlaczego zespół C # nie zaczął jeszcze na to pozwalać, ponieważ jest już obsługiwany przez MSIL.
MgSam
25
@MgSam - Od Erica Lipperta :There's no particularly unusual reason why not; we have lots of other things to do, limited budgets, and this one has never made it past the "wouldn't this be nice?" discussion in the language design team.
Christopher Currens,
5
@LordofScripts: Myślę, że powodem jest to, że ponieważ klasa, która przytrzymuje Taby System.Enumnie byłby w stanie zrobić wszystkie rzeczy, o Tktóre ludzie mogą oczekiwać, autorzy C # zorientowali mogą również zakazać go całkowicie. Uważam tę decyzję za niefortunną, ponieważ C # po prostu zignorowała jakiekolwiek specjalne postępowanie z System.Enumograniczeniami, byłoby możliwe napisanie HasAnyFlags<T>(this T it, T other)metody rozszerzenia, która była o rząd wielkości szybsza niż Enum.HasFlag(Enum)i która sprawdzała jej argumenty.
supercat
9
Nie sądzę, że kiedykolwiek miałem projekt, w którym nie skończyłem tutaj. C # 6 to 110% cukru syntaktycznego, a TO się nie dostało? Wytnij bzdury.
Michael Blackburn,
214

C # ≥ 7,3

Począwszy od wersji C # 7.3 (dostępnej w Visual Studio 2017 ≥ 15.7), ten kod jest teraz w pełni poprawny:

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

C # ≤ 7,2

Możesz mieć wymuszone ograniczenie wyliczania przez prawdziwy kompilator, nadużywając dziedziczenia ograniczeń. Poniższy kod określa jednocześnie ograniczenia a classoraz structograniczenia:

public abstract class EnumClassUtils<TClass>
where TClass : class
{

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

}

public class EnumUtils : EnumClassUtils<Enum>
{
}

Stosowanie:

EnumUtils.Parse<SomeEnum>("value");

Uwaga: jest to wyraźnie określone w specyfikacji języka C # 5.0:

Jeżeli parametr typu S zależy od parametru typu T, wówczas: [...] S ma ograniczenie typu wartości, a T ograniczenie typu odniesienia. Skutecznie ogranicza to T do typów System.Object, System.ValueType, System.Enum i dowolnego typu interfejsu.

Julien Lebosquain
źródło
7
@ DavidI.McIntosh EnumClassUtils<System.Enum>jest wystarczający, aby ograniczyć T do dowolnego System.Enumtypu pochodnego. structna Parseczym ogranicza go dalej do rzeczywistego typu wyliczeniowego. W Enumpewnym momencie musisz ograniczyć się do . Aby to zrobić, twoja klasa musi być zagnieżdżona. Zobacz gist.github.com/MrJul/7da12f5f2d6c69f03d79
Julien Lebosquain
7
Żeby było jasne, mój komentarz „nieprzyjemny” nie był komentarzem do twojego rozwiązania - to naprawdę piękny hack. Po prostu „nieprzyjemne”, że stwardnienie rozsiane zmusza nas do użycia tak zawiłego hacka.
David I. McIntosh,
2
Czy istnieje sposób, aby to naprawić, aby można je było również stosować w przypadku metod rozszerzenia?
Mord Zuber
3
Co where TClass : classzyskuje tu ograniczenie?
tsemer
2
@Trinkyoenum DefinitelyNotAnInt : byte { Realize, That, I, Am, Not, An, Int } enum AlsoNotAnInt : long { Well, Bummer }
M.Stramm
30

Edytować

Na pytanie doskonale odpowiedział już Julien Lebosquain . Chciałbym również, aby rozszerzyć swoją odpowiedź z ignoreCase, defaultValuei opcjonalne argumenty, podczas dodawania TryParsei ParseOrDefault.

public abstract class ConstrainedEnumParser<TClass> where TClass : class
// value type constraint S ("TEnum") depends on reference type T ("TClass") [and on struct]
{
    // internal constructor, to prevent this class from being inherited outside this code
    internal ConstrainedEnumParser() {}
    // Parse using pragmatic/adhoc hard cast:
    //  - struct + class = enum
    //  - 'guaranteed' call from derived <System.Enum>-constrained type EnumUtils
    public static TEnum Parse<TEnum>(string value, bool ignoreCase = false) where TEnum : struct, TClass
    {
        return (TEnum)Enum.Parse(typeof(TEnum), value, ignoreCase);
    }
    public static bool TryParse<TEnum>(string value, out TEnum result, bool ignoreCase = false, TEnum defaultValue = default(TEnum)) where TEnum : struct, TClass // value type constraint S depending on T
    {
        var didParse = Enum.TryParse(value, ignoreCase, out result);
        if (didParse == false)
        {
            result = defaultValue;
        }
        return didParse;
    }
    public static TEnum ParseOrDefault<TEnum>(string value, bool ignoreCase = false, TEnum defaultValue = default(TEnum)) where TEnum : struct, TClass // value type constraint S depending on T
    {
        if (string.IsNullOrEmpty(value)) { return defaultValue; }
        TEnum result;
        if (Enum.TryParse(value, ignoreCase, out result)) { return result; }
        return defaultValue;
    }
}

public class EnumUtils: ConstrainedEnumParser<System.Enum>
// reference type constraint to any <System.Enum>
{
    // call to parse will then contain constraint to specific <System.Enum>-class
}

Przykłady użycia:

WeekDay parsedDayOrArgumentException = EnumUtils.Parse<WeekDay>("monday", ignoreCase:true);
WeekDay parsedDayOrDefault;
bool didParse = EnumUtils.TryParse<WeekDay>("clubs", out parsedDayOrDefault, ignoreCase:true);
parsedDayOrDefault = EnumUtils.ParseOrDefault<WeekDay>("friday", ignoreCase:true, defaultValue:WeekDay.Sunday);

Stary

Moje stare ulepszenia odpowiedzi Vivek za pomocą komentarzy i „nowych” zmian:

  • używać TEnumdla jasności dla użytkowników
  • dodaj więcej ograniczeń interfejsu dla dodatkowego sprawdzania ograniczeń
  • niech TryParseobsłuży ignoreCaseistniejący parametr (wprowadzony w VS2010 / .Net 4)
  • opcjonalnie użyj defaultwartości ogólnej (wprowadzonej w VS2005 / .Net 2)
  • użyj opcjonalnych argumentów (wprowadzonych w VS2010 / .Net 4) z wartościami domyślnymi dla defaultValueiignoreCase

w wyniku czego:

public static class EnumUtils
{
    public static TEnum ParseEnum<TEnum>(this string value,
                                         bool ignoreCase = true,
                                         TEnum defaultValue = default(TEnum))
        where TEnum : struct,  IComparable, IFormattable, IConvertible
    {
        if ( ! typeof(TEnum).IsEnum) { throw new ArgumentException("TEnum must be an enumerated type"); }
        if (string.IsNullOrEmpty(value)) { return defaultValue; }
        TEnum lResult;
        if (Enum.TryParse(value, ignoreCase, out lResult)) { return lResult; }
        return defaultValue;
    }
}
Poważnie
źródło
18

Możesz zdefiniować konstruktora statycznego dla klasy, który sprawdzi, czy typ T jest wyliczeniem i wyrzuci wyjątek, jeśli nie jest. Jest to metoda wspomniana przez Jeffery Richtera w jego książce CLR poprzez C #.

internal sealed class GenericTypeThatRequiresAnEnum<T> {
    static GenericTypeThatRequiresAnEnum() {
        if (!typeof(T).IsEnum) {
        throw new ArgumentException("T must be an enumerated type");
        }
    }
}

Następnie w metodzie parsowania możesz po prostu użyć Enum.Parse (typeof (T), input, true), aby przekonwertować ciąg znaków na wyliczenie. Ostatni prawdziwy parametr służy do ignorowania wielkości liter na wejściu.

Karg
źródło
1
Jest to dobra opcja dla klas ogólnych - ale oczywiście nie pomaga w przypadku metod ogólnych.
McGarnagle,
Również nie jest to wymuszane w czasie kompilacji, wiedziałbyś tylko, że podałeś non, Enum Tgdy konstruktor został wykonany. Chociaż jest to o wiele przyjemniejsze niż czekanie na konstruktor instancji.
jrh
15

Należy również wziąć pod uwagę, że ponieważ wydanie C # 7.3 przy użyciu ograniczeń Enum jest obsługiwane od razu po wyjęciu z pudełka, bez konieczności dodatkowego sprawdzania i innych rzeczy.

Idąc dalej i biorąc pod uwagę, że zmieniłeś wersję językową swojego projektu na C # 7.3, następujący kod będzie działał idealnie:

    private static T GetEnumFromString<T>(string value, T defaultValue) where T : Enum
    {
        // Your code goes here...
    }

Jeśli nie wiesz, jak zmienić wersję językową na C # 7.3, zobacz następujący zrzut ekranu: wprowadź opis zdjęcia tutaj

EDYCJA 1 - Wymagana wersja Visual Studio i rozważenie ReSharper

Aby program Visual Studio rozpoznał nową składnię, potrzebujesz co najmniej wersji 15.7. Można to znaleźć również w uwagach do wydania Microsoftu, zobacz Visual Studio 2017 15.7 Informacje o wersji . Dzięki @MohamedElshawaf za wskazanie tego ważnego pytania.

Proszę również zauważyć, że w moim przypadku ReSharper 2018.1 w chwili pisania tego EDYCJI nie obsługuje jeszcze C # 7.3. Po aktywacji ReSharper podświetla ograniczenie Enum jako błąd informujący, że nie mogę użyć „System.Array”, „System.Delegate”, „System.Enum”, „System.ValueType”, „obiekt” jako ograniczenia parametru typu . ReSharper sugeruje jako szybką poprawkę na usunięcie ograniczenia „Enum” parametru typu T metody

Jeśli jednak tymczasowo wyłączysz ReSharper w menu Narzędzia -> Opcje -> ReSharper Ultimate -> Ogólne , zobaczysz, że składnia jest w porządku, biorąc pod uwagę, że używasz VS 15.7 lub wyższej i C # 7.3 lub wyższej.

baumgarb
źródło
1
Jakiej wersji VS używasz?
mshwf
1
@MohamedElshawaf Wierzę, że jest to wersja 15.7, która obsługuje C # 7.3
Patrick Roberts
1
Myślę, że lepiej pisać where T : struct, Enum, aby uniknąć podania System.Enumsiebie jako parametru typu.
Mariusz Pawelski,
Jak @MariuszPawelski piszę struct, Enum. Moje uzasadnienie wyjaśniono w odpowiedzi i komentarzach tutaj .
Stephen Kennedy
Informacje ReSharper naprawdę mi pomogły. Uwaga: najnowsza wersja podglądu obsługuje tę funkcję.
DalSoft,
11

Zmodyfikowałem próbkę przez dimarzionist. Ta wersja działa tylko z Enums i nie pozwala strukturom na przejście.

public static T ParseEnum<T>(string enumString)
    where T : struct // enum 
    {
    if (String.IsNullOrEmpty(enumString) || !typeof(T).IsEnum)
       throw new Exception("Type given must be an Enum");
    try
    {

       return (T)Enum.Parse(typeof(T), enumString, true);
    }
    catch (Exception ex)
    {
       return default(T);
    }
}
Bivoauc
źródło
13
Nie zwróciłbym wartości domyślnej w przypadku niepowodzenia; Pozwoliłbym propagować wyjątek (podobnie jak w przypadku Enum.Parse). Zamiast tego użyj TryParse zwracając wartość bool i zwracaj wynik za pomocą parametru out.
Mark Simpson
1
OP chce, aby wielkość liter nie uwzględniała wielkości liter, tak nie jest.
Konrad Morawski
9

Próbowałem trochę poprawić kod:

public T LoadEnum<T>(string value, T defaultValue = default(T)) where T : struct, IComparable, IFormattable, IConvertible
{
    if (Enum.IsDefined(typeof(T), value))
    {
        return (T)Enum.Parse(typeof(T), value, true);
    }
    return defaultValue;
}
Jaskółka oknówka
źródło
1
Jest to lepsze niż zaakceptowana odpowiedź, ponieważ pozwala na wywołanie, defaultValue.ToString("D", System.Globalization.NumberFormatInfo.CurrentInfo)nawet jeśli nie wiesz, jaki to jest rodzaj wyliczenia, tylko że obiekt jest wyliczeniem.
styfle,
1
Jednak sprawdzenie z wyprzedzeniem IsDefinedzrujnuje wielkość liter. W przeciwieństwie do Parse, IsDefinednie ma ignoreCaseargumentu, a MSDN mówi, że pasuje tylko do dokładnej wielkości liter .
Nyerguds,
5

Mam określony wymóg, w którym wymagałem użycia wyliczenia z tekstem powiązanym z wartością wyliczenia. Na przykład, gdy używam wyliczenia, aby określić typ błędu, wymagane jest opisanie szczegółów błędu.

public static class XmlEnumExtension
{
    public static string ReadXmlEnumAttribute(this Enum value)
    {
        if (value == null) throw new ArgumentNullException("value");
        var attribs = (XmlEnumAttribute[]) value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof (XmlEnumAttribute), true);
        return attribs.Length > 0 ? attribs[0].Name : value.ToString();
    }

    public static T ParseXmlEnumAttribute<T>(this string str)
    {
        foreach (T item in Enum.GetValues(typeof(T)))
        {
            var attribs = (XmlEnumAttribute[])item.GetType().GetField(item.ToString()).GetCustomAttributes(typeof(XmlEnumAttribute), true);
            if(attribs.Length > 0 && attribs[0].Name.Equals(str)) return item;
        }
        return (T)Enum.Parse(typeof(T), str, true);
    }
}

public enum MyEnum
{
    [XmlEnum("First Value")]
    One,
    [XmlEnum("Second Value")]
    Two,
    Three
}

 static void Main()
 {
    // Parsing from XmlEnum attribute
    var str = "Second Value";
    var me = str.ParseXmlEnumAttribute<MyEnum>();
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
    // Parsing without XmlEnum
    str = "Three";
    me = str.ParseXmlEnumAttribute<MyEnum>();
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
    me = MyEnum.One;
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
}
Sunny Rajwadi
źródło
4

Mam nadzieję, że to jest pomocne:

public static TValue ParseEnum<TValue>(string value, TValue defaultValue)
                  where TValue : struct // enum 
{
      try
      {
            if (String.IsNullOrEmpty(value))
                  return defaultValue;
            return (TValue)Enum.Parse(typeof (TValue), value);
      }
      catch(Exception ex)
      {
            return defaultValue;
      }
}
dimarzionist
źródło
1
Jeśli potrzebujesz case niewrażliwość, wystarczy zastąpić return (TValue)Enum.Parse(typeof (TValue), value);przezreturn (TValue)Enum.Parse(typeof (TValue), value, true);
Paulo Santos
3

Co ciekawe, najwyraźniej jest to możliwe w innych językach (bezpośrednio Managed C ++, IL).

Cytować:

... Oba ograniczenia faktycznie generują poprawną IL i mogą być również zużyte przez C #, jeśli są napisane w innym języku (możesz zadeklarować te ograniczenia w zarządzanym C ++ lub w IL).

Kto wie

Andrew Backer
źródło
2
Rozszerzenia zarządzane dla C ++ nie mają ŻADNEGO wsparcia dla ogólnych, myślę, że masz na myśli C ++ / CLI.
Ben Voigt
3

To jest moje zdanie na ten temat. W połączeniu z odpowiedziami i MSDN

public static TEnum ParseToEnum<TEnum>(this string text) where TEnum : struct, IConvertible, IComparable, IFormattable
{
    if (string.IsNullOrEmpty(text) || !typeof(TEnum).IsEnum)
        throw new ArgumentException("TEnum must be an Enum type");

    try
    {
        var enumValue = (TEnum)Enum.Parse(typeof(TEnum), text.Trim(), true);
        return enumValue;
    }
    catch (Exception)
    {
        throw new ArgumentException(string.Format("{0} is not a member of the {1} enumeration.", text, typeof(TEnum).Name));
    }
}

Źródło MSDN

KarmaEDV
źródło
2
To naprawdę nie ma sensu. Jeśli TEnumfaktycznie jest typem Enum, ale textjest pustym ciągiem, wówczas pojawia się ArgumentExceptionpowiedzenie „TEnum musi być typem Enum”, nawet jeśli tak jest.
Nick
3

Istniejące odpowiedzi są prawdziwe od C # <= 7.2. Istnieje jednak żądanie funkcji języka C # (powiązane z żądaniem funkcji Corefx ), aby umożliwić:

public class MyGeneric<TEnum> where TEnum : System.Enum
{ }

W momencie pisania tego artykułu funkcja ta jest „w dyskusji” na spotkaniach poświęconych rozwojowi języka.

EDYTOWAĆ

Jak wynika z informacji nawfal , zostało to wprowadzone w C # 7.3 .

DiskJunky
źródło
1
Dzięki, ciekawa dyskusja. Jeszcze nic nie
osadzono
1
@ johnc, bardzo prawdziwe, ale warte odnotowania i jest to często zadawana funkcja. Nadchodzą
uczciwe
1
To będzie w C # 7.3: docs.microsoft.com/en-us/visualstudio/releasenotes/… . :)
nawfal
1

Zawsze mi się podobało (można odpowiednio zmodyfikować):

public static IEnumerable<TEnum> GetEnumValues()
{
  Type enumType = typeof(TEnum);

  if(!enumType.IsEnum)
    throw new ArgumentException("Type argument must be Enum type");

  Array enumValues = Enum.GetValues(enumType);
  return enumValues.Cast<TEnum>();
}
Jeff
źródło
1

Podobało mi się rozwiązanie Christophera Currensa z wykorzystaniem IL, ale dla tych, którzy nie chcą zajmować się trudną sprawą włączenia MSIL do procesu kompilacji, napisałem podobną funkcję w języku C #.

Pamiętaj jednak, że nie możesz używać ogólnych ograniczeń, takich jak where T : Enumponieważ Enum jest typem specjalnym. Dlatego muszę sprawdzić, czy dany typ ogólny jest naprawdę wyliczony.

Moja funkcja to:

public static T GetEnumFromString<T>(string strValue, T defaultValue)
{
    // Check if it realy enum at runtime 
    if (!typeof(T).IsEnum)
        throw new ArgumentException("Method GetEnumFromString can be used with enums only");

    if (!string.IsNullOrEmpty(strValue))
    {
        IEnumerator enumerator = Enum.GetValues(typeof(T)).GetEnumerator();
        while (enumerator.MoveNext())
        {
            T temp = (T)enumerator.Current;
            if (temp.ToString().ToLower().Equals(strValue.Trim().ToLower()))
                return temp;
        }
    }

    return defaultValue;
}
ekspert
źródło
1

Zamknąłem rozwiązanie Vivek w klasę użyteczności, z której można ponownie skorzystać. Pamiętaj, że nadal powinieneś zdefiniować ograniczenia typu „gdzie T: struct, IConvertible” na swoim typie.

using System;

internal static class EnumEnforcer
{
    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="typeParameterName">Name of the type parameter.</param>
    /// <param name="methodName">Name of the method which accepted the parameter.</param>
    public static void EnforceIsEnum<T>(string typeParameterName, string methodName)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            string message = string.Format(
                "Generic parameter {0} in {1} method forces an enumerated type. Make sure your type parameter {0} is an enum.",
                typeParameterName,
                methodName);

            throw new ArgumentException(message);
        }
    }

    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="typeParameterName">Name of the type parameter.</param>
    /// <param name="methodName">Name of the method which accepted the parameter.</param>
    /// <param name="inputParameterName">Name of the input parameter of this page.</param>
    public static void EnforceIsEnum<T>(string typeParameterName, string methodName, string inputParameterName)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            string message = string.Format(
                "Generic parameter {0} in {1} method forces an enumerated type. Make sure your input parameter {2} is of correct type.",
                typeParameterName,
                methodName,
                inputParameterName);

            throw new ArgumentException(message);
        }
    }

    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="exceptionMessage">Message to show in case T is not an enum.</param>
    public static void EnforceIsEnum<T>(string exceptionMessage)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException(exceptionMessage);
        }
    }
}
niaher
źródło
1

Stworzyłem rozszerzenie Metoda to get integer value from enum spójrz na implementację metody

public static int ToInt<T>(this T soure) where T : IConvertible//enum
{
    if (typeof(T).IsEnum)
    {
        return (int) (IConvertible)soure;// the tricky part
    }
    //else
    //    throw new ArgumentException("T must be an enumerated type");
    return soure.ToInt32(CultureInfo.CurrentCulture);
}

to jest użycie

MemberStatusEnum.Activated.ToInt()// using extension Method
(int) MemberStatusEnum.Activated //the ordinary way
Basheer AL-MOMANI
źródło
Chociaż prawdopodobnie działa, nie ma prawie żadnego znaczenia dla pytania.
quetzalcoatl
1

Jak stwierdzono w innych odpowiedziach wcześniej; Chociaż nie można tego wyrazić w kodzie źródłowym, można to zrobić na poziomie IL. Odpowiedź Christopher Currens pokazuje, jak IL to robi.

Z Fody s dodatek ExtraConstraints.Fody istnieje bardzo prosty sposób, wraz z gromadzeniem narzędzi, aby to osiągnąć. Po prostu dodaj ich pakiety nuget ( Fody, ExtraConstraints.Fody) do swojego projektu i dodaj ograniczenia w następujący sposób (Fragment z Readme ExtraConstraints):

public void MethodWithEnumConstraint<[EnumConstraint] T>() {...}

public void MethodWithTypeEnumConstraint<[EnumConstraint(typeof(ConsoleColor))] T>() {...}

a Fody doda niezbędną IL, aby było obecne ograniczenie. Zwróć także uwagę na dodatkową funkcję ograniczania delegatów:

public void MethodWithDelegateConstraint<[DelegateConstraint] T> ()
{...}

public void MethodWithTypeDelegateConstraint<[DelegateConstraint(typeof(Func<int>))] T> ()
{...}

Jeśli chodzi o Enums, możesz również zwrócić uwagę na bardzo interesujący Enums.NET .

BatteryBackupUnit
źródło
1

To jest moja realizacja. Zasadniczo możesz ustawić dowolny atrybut i działa.

public static class EnumExtensions
    {
        public static string GetDescription(this Enum @enum)
        {
            Type type = @enum.GetType();
            FieldInfo fi = type.GetField(@enum.ToString());
            DescriptionAttribute[] attrs =
                fi.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];
            if (attrs.Length > 0)
            {
                return attrs[0].Description;
            }
            return null;
        }
    }
Cubelaster
źródło
0

Jeśli później możesz użyć rzutowania bezpośredniego, myślę, że możesz użyć System.Enumklasy bazowej w swojej metodzie, tam gdzie to konieczne. Trzeba tylko ostrożnie wymienić parametry typu. Zatem implementacja metody wyglądałaby następująco:

public static class EnumUtils
{
    public static Enum GetEnumFromString(string value, Enum defaultValue)
    {
        if (string.IsNullOrEmpty(value)) return defaultValue;
        foreach (Enum item in Enum.GetValues(defaultValue.GetType()))
        {
            if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        }
        return defaultValue;
    }
}

Następnie możesz użyć go w następujący sposób:

var parsedOutput = (YourEnum)EnumUtils.GetEnumFromString(someString, YourEnum.DefaultValue);
uluorta
źródło
użycie Enum.ToObject()dałoby bardziej elastyczny wynik. Do tego można było porównywać ciągi znaków bez rozróżniania wielkości liter, co ToLower()
negowałoby
-6

Dla kompletności poniżej przedstawiono rozwiązanie Java. Jestem pewien, że to samo można zrobić w języku C #. Pozwala to uniknąć konieczności określania typu w dowolnym miejscu w kodzie - zamiast tego określasz go w ciągach, które próbujesz przeanalizować.

Problem polega na tym, że nie ma sposobu, aby dowiedzieć się, które wyliczenie może pasować do ciągu - więc odpowiedzią jest rozwiązanie tego problemu.

Zamiast akceptować tylko wartość ciągu, zaakceptuj ciąg, który ma zarówno wyliczenie, jak i wartość w postaci „enumeration.value”. Działający kod znajduje się poniżej - wymaga Java 1.8 lub nowszej. Dzięki temu XML byłby bardziej precyzyjny, ponieważ zobaczyłbyś coś takiego jak color = "Color.red" zamiast po prostu color = "red".

Wywołalibyśmy metodę acceptEnumeratedValue () za pomocą łańcucha zawierającego nazwę wyliczenia nazwa wartości kropki.

Metoda zwraca formalną wyliczoną wartość.

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;


public class EnumFromString {

    enum NumberEnum {One, Two, Three};
    enum LetterEnum {A, B, C};


    Map<String, Function<String, ? extends Enum>> enumsByName = new HashMap<>();

    public static void main(String[] args) {
        EnumFromString efs = new EnumFromString();

        System.out.print("\nFirst string is NumberEnum.Two - enum is " + efs.acceptEnumeratedValue("NumberEnum.Two").name());
        System.out.print("\nSecond string is LetterEnum.B - enum is " + efs.acceptEnumeratedValue("LetterEnum.B").name());

    }

    public EnumFromString() {
        enumsByName.put("NumberEnum", s -> {return NumberEnum.valueOf(s);});
        enumsByName.put("LetterEnum", s -> {return LetterEnum.valueOf(s);});
    }

    public Enum acceptEnumeratedValue(String enumDotValue) {

        int pos = enumDotValue.indexOf(".");

        String enumName = enumDotValue.substring(0, pos);
        String value = enumDotValue.substring(pos + 1);

        Enum enumeratedValue = enumsByName.get(enumName).apply(value);

        return enumeratedValue;
    }


}
Rodney P. Barbati
źródło