Erstellen Sie eine generische Methode, die T auf eine Aufzählung beschränkt

1188

Ich baue eine Funktion auf, um das Enum.ParseKonzept zu erweitern

  • Ermöglicht das Analysieren eines Standardwerts, falls kein Enum-Wert gefunden wird
  • Ist unabhängig von Groß- und Kleinschreibung

Also schrieb ich folgendes:

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

Ich erhalte eine Fehlerbeschränkung kann keine Sonderklasse sein System.Enum.

Fair genug, aber gibt es eine Problemumgehung, um eine generische Aufzählung zuzulassen, oder muss ich die ParseFunktion nachahmen und einen Typ als Attribut übergeben, wodurch die hässliche Boxanforderung an Ihren Code erzwungen wird.

BEARBEITEN Alle Vorschläge unten wurden sehr geschätzt, danke.

Ich habe mich entschieden (ich habe die Schleife verlassen, um die Groß- und Kleinschreibung nicht zu berücksichtigen - ich verwende dies beim Parsen von 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;
    }
}

BEARBEITEN: (16. Februar 2015) Julien Lebosquain hat kürzlich eine vom Compiler erzwungene typsichere generische Lösung in MSIL oder F # veröffentlicht , die einen Blick wert ist, und eine positive Bewertung. Ich werde diese Bearbeitung entfernen, wenn die Lösung weiter oben auf der Seite sprudelt.

Johnc
quelle
10
Vielleicht sollten Sie ToUpperInvariant () anstelle von ToLower () verwenden ...
Max Galkin
31
@Shimmy: Sobald Sie einen Werttyp an die Erweiterungsmethode übergeben, arbeiten Sie an einer Kopie davon, sodass Sie den Status nicht ändern können.
Garo Yeriazarian
4
Wissen Sie, dass es sich um einen alten Thread handelt, wissen Sie nicht, ob sie Änderungen vorgenommen haben, aber Erweiterungsmethoden funktionieren gut für Werttypen. Sicher, dass sie nicht immer so sinnvoll sind, aber ich habe "public static TimeSpan Seconds (this int x) {" verwendet. return TimeSpan.FromSeconds (x);} ", um die Syntax von" Wait.For (5.Seconds ()) ... "
Jens
6
Stellen Sie fest, dass dies nicht Teil der Frage war, aber Sie können Ihre foreach-Schleifenlogik verbessern, indem Sie String.Equals mit StringComparison.InvariantCultureIgnoreCase
Firestrand

Antworten:

1006

Da EnumType die IConvertibleSchnittstelle implementiert, sollte eine bessere Implementierung ungefähr so ​​aussehen:

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

   //...
}

Dies ermöglicht weiterhin die Übergabe von Wertetypen, die implementiert werden IConvertible. Die Chancen sind jedoch selten.

Vivek
quelle
2
Generika sind seit .NET 2.0 verfügbar. Daher sind sie auch in vb 2005 verfügbar.
Vivek
46
Machen Sie es dann noch enger, wenn Sie diesen Weg beschreiten ... verwenden Sie "Klasse TestClass <T> wobei T: struct, IComparable, IFormattable, IConvertible"
Ricardo Nolde
106
Ein weiterer Vorschlag besteht darin, den generischen Typ mit dem Bezeichner TEnum zu definieren. Also: public TEnum GetEnumFromString <TEnum> (Zeichenfolgenwert) wobei TEnum: struct, IConvertible, IComparible, IFormattable {}
Lisa
11
Sie gewinnen nicht viel, wenn Sie die anderen Schnittstellen einbeziehen, da fast alle integrierten Werttypen alle diese Schnittstellen implementieren. Dies gilt insbesondere für Einschränkungen einer generischen Erweiterungsmethode, die für die Bearbeitung von Aufzählungen äußerst praktisch ist, mit Ausnahme der Tatsache, dass diese Erweiterungsmethoden wie ein Virus sind, der alle Ihre Objekte infiziert. IConvertable schränkt es zumindest ein wenig ein.
Russbischof
2
@SamIam: Als du gepostet hast, war dieser Thread was, 6 ½ Jahre alt, und du warst korrekt, keine Kompilierungszeit beim Einchecken einer der Antworten. Dann, nur 3 Tage später, nach 6 Jahren, haben Sie Ihren Wunsch bekommen - siehe Julien Lebosquains Beitrag weiter unten.
David I. McIntosh
663

Diese Funktion wird endlich in C # 7.3 unterstützt!

Das folgende Snippet (aus den Dotnet-Beispielen ) zeigt, wie:

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

Stellen Sie sicher, dass Sie Ihre Sprachversion in Ihrem C # -Projekt auf Version 7.3 einstellen.


Originalantwort unten:

Ich bin zu spät zum Spiel, aber ich habe es als Herausforderung angesehen, zu sehen, wie es gemacht werden kann. Dies ist in C # (oder VB.NET nicht möglich, aber für F # nach unten scrollen), in MSIL jedoch möglich . Ich habe dieses kleine ... Ding geschrieben

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

Was eine Funktion erzeugt, die so aussehen würde , wenn sie gültig wäre C #:

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

Dann mit folgendem C # -Code:

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
}

Leider bedeutet dies, dass dieser Teil Ihres Codes in MSIL anstelle von C # geschrieben wird. Der einzige zusätzliche Vorteil besteht darin, dass Sie diese Methode einschränken können System.Enum. Es ist auch eine Art Mist, weil es in einer separaten Assembly zusammengestellt wird. Dies bedeutet jedoch nicht, dass Sie es auf diese Weise bereitstellen müssen.

Durch Entfernen der Zeile .assembly MyThing{}und Aufrufen von ilasm wie folgt:

ilasm.exe /DLL /OUTPUT=MyThing.netmodule

Sie erhalten ein Netzmodul anstelle einer Baugruppe.

Leider unterstützt VS2010 (und natürlich früher) das Hinzufügen von Netzmodulreferenzen nicht, was bedeutet, dass Sie es beim Debuggen in zwei separaten Assemblys belassen müssen. Die einzige Möglichkeit, sie als Teil Ihrer Assembly hinzuzufügen, besteht darin, csc.exe selbst mit dem /addmodule:{files}Befehlszeilenargument auszuführen . Es wäre nicht auch soIn einem MSBuild-Skript schmerzhaft. Wenn Sie mutig oder dumm sind, können Sie csc natürlich jedes Mal manuell ausführen. Und es wird sicherlich komplizierter, da mehrere Baugruppen Zugriff darauf benötigen.

So kann es in .Net gemacht werden. Lohnt sich der zusätzliche Aufwand? Ähm, ich denke, ich werde dich darüber entscheiden lassen.


F # Lösung als Alternative

Zusätzliches Guthaben: Es stellt sich heraus, dass eine generische Einschränkung enumin mindestens einer anderen .NET-Sprache neben MSIL: F # möglich ist.

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

Diese Sprache ist einfacher zu warten, da es sich um eine bekannte Sprache mit vollständiger Unterstützung für Visual Studio IDE handelt. Sie benötigen jedoch noch ein separates Projekt in Ihrer Lösung. Es erzeugt jedoch natürlich erheblich unterschiedliche IL (der Code ist sehr unterschiedlich) und es stützt sich auf dieFSharp.Core Bibliothek, die wie jede andere externe Bibliothek Teil Ihrer Distribution werden muss.

So können Sie es verwenden (im Grunde das gleiche wie die MSIL-Lösung) und zeigen, dass es bei ansonsten synonymen Strukturen korrekt fehlschlägt:

// 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
quelle
67
Ja, sehr hardcore. Ich habe größten Respekt vor jemandem, der in IL codieren kann, und weiß, wie die Funktionen auf der höheren Sprachebene unterstützt werden - eine Ebene, die viele von uns unter Anwendungen, Geschäftsregeln, Benutzeroberflächen, Komponentenbibliotheken usw. Immer noch als niedrig ansehen .
TonyG
13
Was ich wirklich gerne wissen würde, ist, warum das C # -Team dies noch nicht zugelassen hat, da es bereits von MSIL unterstützt wird.
MgSam
25
@MgSam - Von Eric Lippert :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: Ich denke, der Grund dafür ist, dass eine Klasse, die a einschränkt T, System.Enumnicht in der Lage ist, alle Dinge damit zu tunT die Autoren von C # dachten, sie könnten es genauso gut insgesamt verbieten die Leute erwarten könnten. Ich halte die Entscheidung für unglücklich, da C # jede spezielle Behandlung von System.EnumEinschränkungen einfach ignoriert hatte , wäre es möglich gewesen, eine HasAnyFlags<T>(this T it, T other)Erweiterungsmethode zu schreiben , die um Größenordnungen schneller war als Enum.HasFlag(Enum)und die ihre Argumente typgeprüft hat.
Supercat
9
Ich glaube nicht, dass ich jemals ein Projekt hatte, bei dem ich nicht hier gelandet bin. C # 6 besteht zu 110% aus syntaktischem Zucker und DIESES ist nicht reingekommen? Schneiden Sie den Mist.
Michael Blackburn
214

C # ≥ 7.3

Ab C # 7.3 (verfügbar mit Visual Studio 2017 ≥ v15.7) ist dieser Code jetzt vollständig gültig:

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

C # ≤ 7,2

Sie können eine echte Compiler-Enum-Einschränkung erzwingen lassen, indem Sie die Vererbung von Einschränkungen missbrauchen. Der folgende Code gibt gleichzeitig eine classund eine structEinschränkung an:

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

Verwendungszweck:

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

Hinweis: Dies ist in der C # 5.0-Sprachspezifikation ausdrücklich angegeben:

Wenn der Typparameter S vom Typparameter T abhängt, gilt Folgendes: [...] Es gilt, dass S die Werttypbeschränkung und T die Referenztypbeschränkung hat. Tatsächlich beschränkt dies T auf die Typen System.Object, System.ValueType, System.Enum und jeden Schnittstellentyp.

Julien Lebosquain
quelle
7
@ DavidI.McIntosh EnumClassUtils<System.Enum>reicht aus, um T auf System.Enumalle abgeleiteten Typen zu beschränken. structon Parsebeschränkt es dann weiter auf einen echten Aufzählungstyp. Sie müssen sich Enumirgendwann darauf beschränken. Dazu muss Ihre Klasse verschachtelt sein. Siehe gist.github.com/MrJul/7da12f5f2d6c69f03d79
Julien Lebosquain
7
Um ganz klar zu sein, mein Kommentar "nicht angenehm" war kein Kommentar zu Ihrer Lösung - es ist wirklich ein schöner Hack. Nur "nicht angenehm", dass MS uns zwingt, solch einen verschlungenen Hack zu verwenden.
David I. McIntosh
2
Gibt es eine Möglichkeit, dies so zu bearbeiten, dass es auch für Erweiterungsmethoden verwendet werden kann?
Mord Zuber
3
Was gewinnt die where TClass : classEinschränkung hier?
Tsemer
2
@Trinkyoenum DefinitelyNotAnInt : byte { Realize, That, I, Am, Not, An, Int } enum AlsoNotAnInt : long { Well, Bummer }
M.Stramm
30

Bearbeiten

Die Frage wurde jetzt von Julien Lebosquain hervorragend beantwortet . Ich mag auch seine Antwort mit erweitern ignoreCase, defaultValueund optionalen Argumenten, während des Hinzufügen TryParseund 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
}

Anwendungsbeispiele:

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

Alt

Meine alten Verbesserungen an Viveks Antwort unter Verwendung der Kommentare und "neuen" Entwicklungen:

  • verwenden TEnum für Klarheit für Benutzer
  • Fügen Sie weitere Schnittstellenbeschränkungen hinzu, um zusätzliche Einschränkungen zu überprüfen
  • TryParsehandhaben lassenignoreCase mit dem vorhandenen Parameter (eingeführt in VS2010 / .Net 4)
  • Verwenden Sie optional den generischen defaultWert (eingeführt in VS2005 / .Net 2).
  • Verwenden Sie optionale Argumente (eingeführt in VS2010 / .Net 4) mit Standardwerten für defaultValueundignoreCase

ergebend:

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;
    }
}
Yahoo Serious
quelle
18

Sie können einen statischen Konstruktor für die Klasse definieren, der überprüft, ob der Typ T eine Aufzählung ist, und eine Ausnahme auslösen, wenn dies nicht der Fall ist. Dies ist die Methode, die Jeffery Richter in seinem Buch CLR via C # erwähnt hat.

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

Dann können Sie in der Analysemethode einfach Enum.Parse (typeof (T), input, true) verwenden, um von string in enum zu konvertieren. Der letzte wahre Parameter dient zum Ignorieren der Eingabe.

Karg
quelle
1
Dies ist eine gute Option für generische Klassen - aber natürlich hilft es nicht für generische Methoden.
McGarnagle
Dies wird auch beim Kompilieren nicht erzwungen. Sie würden nur wissen, dass Sie Enum Tbei der Ausführung des Konstruktors ein Non angegeben haben . Dies ist jedoch viel schöner, als auf einen Instanzkonstruktor zu warten.
jrh
15

Es sollte auch berücksichtigt werden, dass die Veröffentlichung von C # 7.3 unter Verwendung von Enum-Einschränkungen sofort unterstützt wird, ohne dass zusätzliche Überprüfungen und andere Vorgänge erforderlich sind.

Wenn Sie also die Sprachversion Ihres Projekts in C # 7.3 geändert haben, funktioniert der folgende Code einwandfrei:

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

Falls Sie nicht wissen, wie Sie die Sprachversion in C # 7.3 ändern können, sehen Sie den folgenden Screenshot: Geben Sie hier die Bildbeschreibung ein

BEARBEITEN 1 - Erforderliche Visual Studio-Version und unter Berücksichtigung von ReSharper

Damit Visual Studio die neue Syntax erkennt, benötigen Sie mindestens Version 15.7. Sie finden dies auch in den Versionshinweisen von Microsoft, siehe Versionshinweise zu Visual Studio 2017 15.7 . Vielen Dank an @MohamedElshawaf für den Hinweis auf diese gültige Frage.

Bitte beachten Sie auch, dass in meinem Fall ReSharper 2018.1 zum Zeitpunkt des Schreibens dieser EDIT C # 7.3 noch nicht unterstützt. Wenn ReSharper aktiviert ist, wird die Enum-Einschränkung als Fehler hervorgehoben, der besagt, dass 'System.Array', 'System.Delegate', 'System.Enum', 'System.ValueType', 'object' nicht als Typparameter-Einschränkung verwendet werden können . ReSharper schlägt als schnelle Lösung vor, die 'Enum'-Einschränkung des Typparameters T der Methode zu entfernen

Wenn Sie ReSharper jedoch vorübergehend unter Extras -> Optionen -> ReSharper Ultimate -> Allgemein deaktivieren Sie feststellen, dass die Syntax vollkommen in Ordnung ist, wenn Sie VS 15.7 oder höher und C # 7.3 oder höher verwenden.

baumgarb
quelle
1
Welche VS-Version verwenden Sie?
mshwf
1
@ MohamedElshawaf Ich glaube, es ist Version 15.7, die Unterstützung für C # 7.3 enthält
Patrick Roberts
1
Ich denke, es ist besser zu schreiben where T : struct, Enum, um zu vermeiden, sich System.Enumselbst als Typparameter zu übergeben.
Mariusz Pawelski
Wie @MariuszPawelski schreibe ich struct, Enum. Meine Begründung wird in der Antwort und den Kommentaren hier erklärt .
Stephen Kennedy
Die ReSharper-Informationen haben mir wirklich geholfen. Beachten Sie, dass die neueste Vorschau-Version diese Funktion unterstützt.
DalSoft
11

Ich habe die Probe von Dimarzionist modifiziert. Diese Version funktioniert nur mit Enums und lässt keine Strukturen durch.

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
quelle
13
Ich würde den Standardwert bei einem Fehler nicht zurückgeben. Ich würde die Ausnahme verbreiten lassen (genau wie bei Enum.Parse). Verwenden Sie stattdessen TryParse, um einen Bool zurückzugeben, und geben Sie das Ergebnis mit einem out-Parameter zurück.
Mark Simpson
1
OP möchte, dass die Groß- und Kleinschreibung nicht berücksichtigt wird.
Konrad Morawski
9

Ich habe versucht, den Code ein wenig zu verbessern:

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;
}
Martin
quelle
1
Dies ist besser als die akzeptierte Antwort, da Sie damit anrufen defaultValue.ToString("D", System.Globalization.NumberFormatInfo.CurrentInfo)können, obwohl Sie nicht wissen, um welche Art von Aufzählung es sich handelt, sondern nur, dass das Objekt eine Aufzählung ist.
Styfle
1
Die Vorabprüfung mit IsDefinedwird jedoch die Unempfindlichkeit gegenüber Groß- und Kleinschreibung zerstören. Im Gegensatz Parse, IsDefinedhat kein ignoreCaseArgument, und MSDN sagt , dass es nur exakten Fall paßt .
Nyerguds
5

Ich habe spezielle Anforderungen, bei denen ich Enum mit Text verwenden muss, der mit dem Enum-Wert verknüpft ist. Wenn ich beispielsweise enum verwende, um den Fehlertyp anzugeben, müssen Fehlerdetails beschrieben werden.

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
quelle
4

Hoffe das ist hilfreich:

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
quelle
1
Wenn Sie Groß- und Kleinschreibung nicht benötigen, ersetzen Sie sie einfach return (TValue)Enum.Parse(typeof (TValue), value);durchreturn (TValue)Enum.Parse(typeof (TValue), value, true);
Paulo Santos
3

Interessanterweise ist dies anscheinend in anderen Sprachen möglich (Managed C ++, IL direkt).

Zitieren:

... Beide Einschränkungen erzeugen tatsächlich eine gültige IL und können auch von C # verwendet werden, wenn sie in einer anderen Sprache geschrieben sind (Sie können diese Einschränkungen in verwaltetem C ++ oder in IL deklarieren).

Wer weiß

Andrew Backer
quelle
2
Managed Extensions für C ++ unterstützen KEINE Generika. Ich denke, Sie meinen C ++ / CLI.
Ben Voigt
3

Das ist meine Einstellung dazu. Kombiniert aus den Antworten und 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));
    }
}

MSDN-Quelle

KarmaEDV
quelle
2
Das macht eigentlich keinen Sinn. Wenn es sich TEnumtatsächlich um einen Aufzählungstyp handelt, es sich jedoch textum eine leere Zeichenfolge handelt, wird das ArgumentExceptionSprichwort "TEnum muss ein Aufzählungstyp sein" angezeigt, obwohl dies der Fall ist.
Nick
3

Die vorhandenen Antworten gelten ab C # <= 7.2. Es gibt jedoch eine C # -Sprachen- Funktionsanforderung (die an eine CoreFX- Funktionsanforderung gebunden ist ), um Folgendes zu ermöglichen:

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

Zum Zeitpunkt des Schreibens ist das Feature "In Diskussion" bei den Sprachentwicklungstreffen.

BEARBEITEN

Wie pro Nawfal ‚s Info, wird dies wird in C # eingeführt 7.3 .

DiskJunky
quelle
1
Interessante Diskussion dort, danke. Noch nichts in Stein gemeißelt (noch)
Johnc
1
@ Johnc, sehr wahr, aber eine Notiz wert und es ist eine häufig gestellte Funktion. Faire Chancen, dass es hereinkommt.
DiskJunky
1
Dies kommt in C # 7.3: docs.microsoft.com/en-us/visualstudio/releasenotes/… . :)
Nawfal
1

Das hat mir immer gefallen (Sie können es nach Bedarf ändern):

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
quelle
1

Ich mochte Christopher Currens 'Lösung mit IL, aber für diejenigen, die sich nicht mit kniffligen Geschäften befassen wollen, MSIL in ihren Erstellungsprozess einzubeziehen, habe ich eine ähnliche Funktion in C # geschrieben.

Bitte beachten Sie jedoch, dass Sie keine generischen Einschränkungen wie verwenden können where T : Enum da Enum ein spezieller Typ ist. Daher muss ich überprüfen, ob der angegebene generische Typ wirklich enum ist.

Meine Funktion ist:

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;
}
Experte
quelle
1

Ich habe die Lösung von Vivek in eine Dienstprogrammklasse eingekapselt, die Sie wiederverwenden können. Bitte beachten Sie, dass Sie für Ihren Typ weiterhin Typeinschränkungen "wobei T: struct, IConvertible" definieren sollten.

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
quelle
1

Ich habe eine Erweiterungsmethode erstellt. to get integer value from enum Schauen Sie sich die Methodenimplementierung an

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

Dies ist die Verwendung

MemberStatusEnum.Activated.ToInt()// using extension Method
(int) MemberStatusEnum.Activated //the ordinary way
Basheer AL-MOMANI
quelle
Während es wahrscheinlich funktioniert, hat es fast keine Relevanz für die Frage.
Quetzalcoatl
1

Wie in anderen Antworten zuvor angegeben; Dies kann zwar nicht im Quellcode ausgedrückt werden, kann jedoch tatsächlich auf IL-Ebene erfolgen. Die Antwort von @Christopher Currens zeigt, wie die IL das macht.

Mit Fody 's Add-In ExtraConstraints.Fody gibt es eine sehr einfache Möglichkeit, dies mit Build-Tools zu erreichen. Fügen Sie einfach ihre Nuget-Pakete ( Fody, ExtraConstraints.Fody) zu Ihrem Projekt hinzu und fügen Sie die Einschränkungen wie folgt hinzu (Auszug aus der Readme-Datei von ExtraConstraints):

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

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

und Fody fügt die erforderliche IL hinzu, damit die Einschränkung vorhanden ist. Beachten Sie auch die zusätzliche Funktion zum Einschränken von Delegaten:

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

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

In Bezug auf Enums sollten Sie auch das hochinteressante Enums.NET beachten .

BatteryBackupUnit
quelle
1

Dies ist meine Implementierung. Grundsätzlich können Sie jedes Attribut einrichten und es funktioniert.

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
quelle
0

Wenn es in Ordnung ist, später direktes Casting zu verwenden, können Sie die System.EnumBasisklasse in Ihrer Methode verwenden, wo immer dies erforderlich ist. Sie müssen nur die Typparameter sorgfältig ersetzen. Die Implementierung der Methode wäre also wie folgt:

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

Dann können Sie es wie folgt verwenden:

var parsedOutput = (YourEnum)EnumUtils.GetEnumFromString(someString, YourEnum.DefaultValue);
uluorta
quelle
Die Verwendung von Enum.ToObject()würde zu einem flexibleren Ergebnis führen. Hinzu kommt, dass Sie die Zeichenfolgenvergleiche ohne Groß- und Kleinschreibung durchführen können, wodurch die Notwendigkeit eines Aufrufs ToLower()
zunichte gemacht wird
-6

Der Vollständigkeit halber ist das Folgende eine Java-Lösung. Ich bin sicher, dass dies auch in C # möglich ist. Es wird vermieden, dass der Typ irgendwo im Code angegeben werden muss. Stattdessen geben Sie ihn in den Zeichenfolgen an, die Sie analysieren möchten.

Das Problem ist, dass es keine Möglichkeit gibt zu wissen, mit welcher Aufzählung der String übereinstimmen könnte. Die Antwort besteht also darin, dieses Problem zu lösen.

Akzeptieren Sie einen String, der sowohl die Aufzählung als auch den Wert in der Form "enumeration.value" enthält, anstatt nur den Zeichenfolgenwert zu akzeptieren. Arbeitscode ist unten - erfordert Java 1.8 oder höher. Dies würde auch das XML präziser machen, da Sie so etwas wie color = "Color.red" anstelle von color = "red" sehen würden.

Sie würden die Methode acceptEnumeratedValue () mit einer Zeichenfolge aufrufen, die den Namen des Aufzählungsnamens und des Wertes enthält.

Die Methode gibt den formalen Aufzählungswert zurück.

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
quelle