2014-01-29 218 views
5

這是一個棘手的問題。也許某人的C#-fu比我的優越,因爲我找不到解決方案。有什麼辦法可以將這兩種方法合併爲一種方法或重載方法?

我有一個方法,它需要一個參數,該參數包含一個枚舉或一個指示枚舉值的字符串,並返回該枚舉的一個實例。它基本上是Enum.Parse的實現,但作爲一種通用方法實現。爲什麼.NET Framework沒有內置的功能超出了我的想象。現在

public static T Parse<T>(object value) where T : struct 
{ 
    if (!typeof (T).IsEnum) 
     throw new ArgumentException("T must be an Enum type."); 

    if (value == null || value == DBNull.Value) 
    { 
     throw new ArgumentException("Cannot parse enum, value is null."); 
    } 

    if (value is String) 
    { 
     return (T)Enum.Parse(typeof(T), value.ToString()); 
    } 

    return (T)Enum.ToObject(typeof(T), value); 
} 

,我可以這樣做:

MyEnum foo = Parse<MyEnum>(obj); 

,並得到MyEnum一個實例。如果obj爲空,則會拋出異常。

但是,有時objnull,我想這樣做。在這種情況下,我希望能夠做到:

MyEnum? foo = Parse<MyEnum?>(obj); 

但是,對於我的生活,我不能想出一個辦法來獲取工作。首先,儘管Nullable<MyEnum>struct,但它不能用作Parse<T>的類型參數。我認爲這跟編譯器用Nullable<>所做的所有魔法有關,所以我不會質疑它。

它沒有出現,你可以超載的方法,只區分它的基礎上約束T。例如,如果我這樣做:

public static T Parse<T>(object value) where T : new() 
{ 
    // This should be called if I pass in a Nullable, in theory 
} 

我會得到錯誤:成員具有相同簽名已經宣佈

所以,這給我留下了只剩下一個方法:實現一個完全獨立的方法設計爲可空類型:

MyEnum? foo = ParseNullable<T>(obj); 

public static T? ParseNullable<T>(object value) where T : struct 
{ 
    if (!typeof (T).IsEnum) 
     throw new ArgumentException("T must be an Enum type."); 

    if (value == null || value == DBNull.Value) 
     return null; 

    if (value is String) 
     return Enum.Parse(typeof (T), value.ToString()) as T?; 

    return Enum.ToObject(typeof (T), value) as T?; 
} 

現在我可以把這個

我的問題:有沒有辦法將這兩種方法結合成一種方法,根據類型參數做對,或者創建一個重載,其中一個重載將用於類型參數是可空的<>而另一個超載在不是時調用?

+0

你將無法ov使用'Nullable '版本來加載該方法,因爲您沒有更改參數(因此不會重載)。你只是改變返回類型。 – pickypg

+0

@pickypg - 是的,這是我的問題;我希望編譯器可以重載基於泛型類型約束的方法..也許C#6 heh .. –

回答

3

它需要在方法中的情侶額外的類型檢查,你必須跳過通用的限制,但它絕對有可能:

public static T Parse<T>(object value) 
{ 
    var isNullable = typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>); 
    var itemType = isNullable ? typeof(T).GetGenericArguments()[0] : typeof(T); 

    if (!itemType.IsEnum) 
     throw new ArgumentException("T must be an Enum type or Nullable<> of Enum type."); 

    if (value == null || value == DBNull.Value) 
    { 
     if (isNullable) 
      return default(T); // default(Nullable<>) is null 

     throw new ArgumentException("Cannot parse enum, value is null."); 
    } 

    if (value is String) 
    { 
     return (T)Enum.Parse(itemType, value.ToString()); 
    } 

    return (T)Enum.ToObject(itemType, value); 
} 

使用範例:

var items = new object[] { "A", "B", 0, 10, null, DBNull.Value }; 

var results = items.Select(x => new { x, e = Parse<Test?>(x) }).ToArray(); 

foreach (var r in results) 
    Console.WriteLine("{0} - {1}", r.x, r.e.ToString()); 

打印

A - A 
B - B 
0 - A 
10 - B 
- 
- 
+0

是的,有一些相同的答案,但我喜歡這一個最好的。代碼很容易閱讀,底部有測試結果。 –

+0

我結束了剛剛使用的東西;兩種名稱不同的方法。我真的希望編譯器能夠區分使用哪種方法,儘管我希望通過重載方法來實現。上述方法的問題是它將在運行時使用反射挖掘類型數據,這違背了通用方法的目的。我在數據綁定代碼中使用它,可能正在處理成千上萬行,所以在我的情況下,性能優化使用語法。謝謝!! –

+1

@MikeChristensen不客氣!順便說一句。我真的很喜歡你決定做正確的事情並使用單獨的方法的事實:) – MarcinJuraszek

1

創建一個像TryParse這樣的方法,並處理返回值==假大小寫,以便根據空值執行所需操作。然後,您可以實現另一個方法來包裝該調用,並在返回值爲false時返回null。 (另外,一定要使用Enum.IsDefined爲枚舉的類型的任何值分配,即使它不是由枚舉定義枚舉)

public static bool TryParseEnum<T>(object value, out T result) where T : struct 
{ 
    if(!typeof(T).IsEnum) 
     throw new ArgumentException("T must be an Enum type."); 

    if(value == null || value == DBNull.Value) 
    { 
     result = default(T); 

     return false; 
    } 

    if(value is String) 
    { 
     return Enum.TryParse<T>((string)value, out result); 
    } 

    result = (T)Enum.ToObject(typeof(T), value); 

    return Enum.IsDefined(typeof(T), result); 
} 

public static Nullable<T> ParseEnum<T>(this object value) where T: struct 
{ 
    T retVal; 

    if(!TryParseEnum(value, out retVal)) 
    { 
     return null; 
    } 

    return new Nullable<T>(retVal); 
} 

用法:

EnumXyz? nullableEnumValue = ParseEnum<EnumXyz>(someObject); 
1

我要去提供另一種方法...返回默認值。它是一個好主意,讓代表反正沒事(如果你忘了初始化等)enum SA默認值...即:

enum MyEnum { 
    Nothing = 0, 
    MeaningfulValue1, 
    MeaningfulValue2 
    // etc.. 
} 

然後你的方法只是變成:

if (value == null || value == DBNull.Value) 
    return default(T); 

。 。和致電地點:

var val = Parse<MyEnum>(obj); 

if (val == MyEnum.Nothing) 
    // it was null. 
+2

然後你永遠不知道該字符串是否包含默認值。另外,你不能*在.NET中給'enum'設置一個默認值,你只能選擇不給它任何默認值的標籤。 –

+0

枚舉的默認值是其基礎類型的默認值,這將是0 – Moho

+0

@JonHanna相當容易將其添加進去。另外,不給出標籤使得在賦值後很難很好地檢查值。這正是我明確給0標籤的要點,以便它可以用作後備。 –

1

由於您實際上沒有通過更改返回類型來重載,所以答案是您無法按自己的意願進行操作。

我會添加一個重載,它需要一個單獨的參數來確定參數的可用性。

public static T Parse<T>(object value) where T : struct 
{ 
    return (T)Parse<T>(value, false); 
} 

public static T? Parse<T>(object value, bool nullable) where T : struct 
{ 
    T? enumValue = null; 

    if (! typeof(T).IsEnum) 
    { 
     throw new ArgumentException("T must be an Enum type."); 
    } 
    else if (value == null || value == DBNull.Value) 
    { 
     // this is the key difference 
     if (! nullable) 
     { 
      throw new ArgumentException("Cannot parse enum, value is null."); 
     } 
    } 
    else if (value is string) 
    { 
     enumValue = (T)Enum.Parse(typeof(T), value.ToString()); 
    } 
    else 
    { 
     enumValue = (T)Enum.ToObject(typeof(T), value); 
    } 

    return enumValue; 
} 

用法:

MyEnum value1 = Parse<MyEnum>("A"); 
// returns null 
MyEnum? value2 = Parse<MyEnum>(null, true); 
// throws exception 
MyEnum? value2 = Parse<MyEnum>(null, false); 
+0

如果'Parse '返回null它可能是一個問題,將'null'轉換爲** Enum ** .. –

+0

它不能返回'null',因爲它通過'false'。 – pickypg

+0

是的,有趣的想法..雖然似乎有點奇怪,我寧願只是命名這兩個函數不同的東西.. –

2

爲什麼不只是刪除T上的約束,並且做這樣的事情:

public static T Parse<T>(Object value) 
    { 
     Boolean isNullable = typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>); 
     if (!isNullable && !typeof(T).IsEnum) 
     { 
      throw new ArgumentException(); 
     } 

     if (value == null || value == DBNull.Value) 
     { 
      throw new ArgumentException(); 
     } 

     if (!(value is String)) 
     { 
      return (T) Enum.ToObject(typeof (T), value); 
     } 

     if (!isNullable) 
     { 
      return (T) Enum.Parse(typeof (T), value.ToString()); 
     } 

     Type underlyingType = Nullable.GetUnderlyingType(typeof(T)); 
     try 
     { 
      return (T)Enum.Parse(underlyingType, value.ToString()); 
     } 
     catch (ArgumentException) 
     { 
      return default(T); 
     } 
    } 

這應該工作,如果沒有,讓我知道。

1

我相信你的問題的簡短答案是「否」在您問題開始時提供的示例中,您希望返回兩種不同的返回類型T和T ?.這本身就需要具有不同名稱的方法。

這是一個link到另一個問題,在泛型類型的可能的答案很好的答案,可能有助於澄清你的問題。

1

如果你真的想用一種方法,那麼這個怎麼樣? 缺點是您必須刪除where T : struct約束。 如果你想保留約束條件,那麼把它分成兩個方法是唯一的方法。

public static T Parse<T>(object value) 
    { 
     Type underlyingType = Nullable.GetUnderlyingType(typeof(T)); 
     bool isNullable = underlyingType != null; 

     if (!typeof(T).IsEnum && !isNullable) 
      throw new ArgumentException("T must be an Enum type."); 

     if (value == null || value == DBNull.Value) 
     { 
      if (isNullable) 
       return default(T); 

      throw new ArgumentNullException("value"); 
     } 

     if (value is String) 
      return (T)Enum.Parse(underlyingType ?? typeof(T), value.ToString()); 

     if (!value.GetType().IsValueType) 
      throw new ArgumentException("value must be a primitive type", "value"); 

     return (T)Enum.ToObject(underlyingType ?? typeof(T), value); 
    } 
+0

即使當'T'爲'Nullable <>'時,它仍然會拋出'null'異常。 – MarcinJuraszek

+0

謝謝我修復了它 – Hack

+0

是的,我正在考慮沿着這條路走下去,但是如果你最終會在運行時解決所有類型信息,它似乎真的會錯過泛型方法的全部觀點。但這種做法可能是我最好的選擇。 –