建立將T約束爲枚舉的泛型方法

我正在構建一個函數來擴展Enum.Parse概念, 安全

  • 若是找不到Enum值,則容許解析默認值
  • 不區分大小寫

因此我寫了如下內容: ide

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

我收到了一個錯誤約束,它不能是System.Enum特殊類。 函數

足夠公平,可是有一種容許通用枚舉的解決方法,仍是我必須模仿Parse函數並將類型做爲屬性傳遞,這迫使對代碼使用難看的裝箱要求。 this

編輯謝謝全部下面的建議。 spa

已經解決(我離開了循環以保持不區分大小寫-解析XML時正在使用它) code

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

編輯: (2015年2月16日)Julien Lebosquain最近在下面的MSIL或F#中發佈了由編譯器強制執行的類型安全的通用解決方案 ,這很值得一看,並值得一提。 若是解決方案在頁面上冒泡,我將刪除此編輯。 orm


#1樓

我喜歡Christopher Currens使用IL的解決方案,但對於那些不想將MSIL歸入其構建過程的棘手事務的人,我在C#中編寫了相似的功能。 接口

請注意,儘管不能使用泛型限制,例如where T : Enum由於Enum是特殊類型。 所以,我必須檢查給定的泛型類型是否真的是枚舉。 事務

個人職能是: ci

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

#2樓

足夠有趣的是,顯然這在其餘語言 (直接由Managed C ++,IL)中也是可能的

報價:

...這兩個約束實際上都產生有效的IL,而且若是用另外一種語言編寫,則也能夠由C#使用(您能夠在託管C ++或IL中聲明這些約束)。

誰知道


#3樓

編輯

Julien Lebosquain如今已經很好地回答了這個問題。 我還想在添加TryParseParseOrDefault同時,使用ignoreCasedefaultValue和可選參數擴展他的答案。

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
}

用法示例:

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

經過使用註釋和「新」開發,我對Vivek答案的舊改進:

  • 使用TEnum爲用戶清晰
  • 添加更多接口約束以進行其餘約束檢查
  • TryParse使用現有參數處理ignoreCase (在VS2010 / .Net 4中引入)
  • (可選)使用通用default (在VS2005 / .Net 2中引入)
  • defaultValueignoreCase使用具備默認值的可選參數 (在VS2010 / .Net 4中引入)

致使:

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

#4樓

我已經將Vivek的解決方案封裝到能夠重用的實用程序類中。 請注意,您仍然應該在類型上定義類型約束「 T:struct,IConvertible」。

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

#5樓

這是個人見解。 從答案和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來源

相關文章
相關標籤/搜索