type - c# where




Create Generische Methode, die T auf ein Enum beschränkt (12)

Ich baue eine Funktion, um das Enum.Parse-Konzept zu erweitern

  • Ermöglicht das Analysieren eines Standardwerts, falls ein Enum-Wert nicht gefunden wird
  • Groß- / Kleinschreibung wird nicht berücksichtigt

Also habe ich folgendes geschrieben:

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 Fehlerbedingung kann keine spezielle Klasse 'System.Enum' sein.

Fair genug, aber gibt es einen Workaround, um eine generische Enum zu erlauben, oder werde ich die Parse-Funktion nachahmen und einen Typ als Attribut übergeben müssen, was die hässlichen Boxanforderungen zu Ihrem Code zwingt.

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

Habe mich entschieden (Ich habe die Schleife verlassen, um die Groß- / Kleinschreibung zu beachten - ich benutze 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;
    }
}

EDIT: (16.02.2015) Julien Lebosquain hat vor kurzem eine Compiler Typ-sichere generische Lösung in MSIL oder F # veröffentlicht , die einen Blick wert ist, und eine Upvote. Ich werde diese Änderung entfernen, wenn die Lösung weiter oben auf der Seite erscheint.


Da der Enum Typ die IConvertible Schnittstelle implementiert, sollte eine bessere Implementierung etwa 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 IConvertible die IConvertible implementieren. Die Chancen sind jedoch selten.


Das ist meine Meinung. 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


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

Ich habe Viveks Lösung in eine Utility-Klasse gekapselt, die Sie wiederverwenden können. Bitte beachten Sie, dass Sie Typeinschränkungen "wo T: struct, IConvertible" auf Ihrem Typ 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);
        }
    }
}

Ich habe spezifische Anforderung, wo ich enum mit dem mit enum-Wert verbundenen Text verwenden musste. Wenn ich z. B. enum verwende, um den Fehlertyp anzugeben, müssen die 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());
}

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

Ich liebte die Lösung von Christopher Currens mit IL, aber für diejenigen, die sich nicht damit beschäftigen wollen, MSIL in ihren Build-Prozess zu integrieren, schrieb ich eine ähnliche Funktion in C #.

Beachten Sie jedoch, dass Sie keine generische Einschränkung wie where T : Enum da Enum ein spezieller Typ ist. Daher muss ich prüfen, ob der 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;
}

Ich modifizierte die Probe durch Dimarzionist. Diese Version funktioniert nur mit Enums und lässt Strukturen nicht durchkommen.

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

Mir hat das immer gefallen (du könntest es ä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>();
}

Sie können eine echte Enumerationsbeschränkung für den Compiler erzwingen, indem Sie die Constraint-Vererbung missbrauchen. Der folgende Code gibt gleichzeitig eine class und eine struct 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>
{
}

Verwendung:

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

Hinweis: Dies wird speziell in der C # 5.0-Sprachspezifikation angegeben:

Wenn der Typparameter S von dem Typparameter T abhängt, dann gilt: [...] Es gilt, dass S die Einschränkung des Wertetyps hat und T die Einschränkung des Referenztyps hat. Effektiv beschränkt dies T auf die Typen System.Object, System.ValueType, System.Enum und jeden Schnittstellentyp.


Wenn es danach in System.Enum direktes Casting zu verwenden, können Sie die System.Enum Basisklasse bei Bedarf in Ihrer Methode verwenden. Sie müssen nur die Typparameter sorgfältig ersetzen. Also wäre die Methodenimplementierung 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 kannst du es benutzen wie:

var parsedOutput = (YourEnum)EnumUtils.GetEnumFromString(someString, YourEnum.DefaultValue);

Wie bereits in anderen Antworten erwähnt; Während dies im Quellcode nicht ausgedrückt werden kann, kann es tatsächlich auf IL Level durchgeführt werden. @ Christopher Currens answer zeigt, wie das IL dazu tut.

Mit Fody s Add-In ExtraConstraints.Fody gibt es einen sehr einfachen Weg, komplett mit Build-Tooling, um dies zu erreichen. Fügen Sie einfach ihre nugget-Pakete ( Fody , ExtraConstraints.Fody ) zu Ihrem Projekt hinzu und fügen Sie die Einschränkungen wie folgt hinzu (Auszug aus der Readme von ExtraConstraints):

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

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

und Fody fügt die notwendige 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 möchten Sie vielleicht auch das hochinteressante Enums.NET zur Kenntnis Enums.NET .







enums