2012-07-19 47 views
20

有什麼辦法讓方法返回一個方法中的任何一個泛型類型?例如,我有以下幾點:C#中的可變通用返回類型#

public static T ParseAttributeValue<T>(this XElement element, string attribute) 
    { 
     if(typeof(T) == typeof(Int32)) 
     { 
      return Int32.Parse(element.Attribute(attribute).Value); 
     } 

     if(typeof(T) == typeof(Double)) 
     { 
      return Double.Parse(element.Attribute(attribute).Value); 
     } 

     if(typeof(T) == typeof(String)) 
     { 
      return element.Attribute(attribute).Value; 
     } 

     if(typeof(T) == typeof(ItemLookupType)) 
     { 
      return Enum.Parse(typeof(T), element.Attribute(attribute).Value); 
     } 
    } 

(這僅僅是一個非常快速的樣機,我知道,任何產品代碼將需要在空支票等顯著更徹底...)

但編譯器不喜歡它,抱怨Int32不能隱式轉換爲T(它不適用於強制轉換)。我能理解這一點。在編譯時,它無法知道什麼是T,但我正在事先檢查它。無論如何,我可以做這項工作嗎?

+0

否,則''只是類型參數。您使用不帶括號的類型。 – Femaref 2012-07-19 18:46:57

+0

@Femaref - 好點。問題被撤銷。 – 2012-07-19 18:50:30

+0

如果將該值存儲爲Object,則可以對其進行轉換。也可以將該功能鍵入爲動態,這可能會幫助您在沒有投射的情況下進行操作。 – MrWednesday 2012-07-19 18:51:30

回答

20

我所做的這些類型的過去通用的方法。獲取類型推斷的最簡單方法是提供通用轉換器函數。

public static T ParseAttributeValue<T> 
      (this XElement element, string attribute, Func<string, T> converter) 
{ 
    string value = element.Attribute(attribute).Value; 
    if (String.IsNullOrWhiteSpace(value)) { 
    return default(T); 
    } 

    return converter(value); 
} 

您可以使用它像下面這樣:

int index = element.ParseAttributeValue("index", Convert.ToInt32); 
double price = element.ParseAttributeValue("price", Convert.ToDouble); 

你甚至可以提供自己的功能,並擁有世界上所有的樂趣(甚至返回匿名類型):

ItemLookupType lookupType = element.ParseAttributeValue("lookupType", 
    value => Enum.Parse(typeof(ItemLookupType), value)); 

var item = element.ParseAttributeValue("items", 
    value => { 
    List<string> items = new List<string>(); 
    items.AddRange(value.Split(new [] { ',' })); 
    return items; 
    }); 
+0

我認爲它不會比這更優雅。 +1。 – Adam 2012-07-19 19:04:05

+0

關於這個的好處是,'T'可以從使用中推斷,因爲它是'Func鍵<,>'裏面。所以,即使我們必須提供一個額外的參數(即'Func鍵<,>'代表),我們將不會有顯式地指定類型參數'T'。 – 2012-07-19 19:31:49

+1

很多通用轉換器已經存在在.NET Framework:'TypeDescriptor.GetTypeConverter(typeof運算(T));' – 2012-07-19 23:54:45

4

爲什麼你使用type參數作爲返回類型呢?這會工作,只是需要投打完電話後:

public static Object ParseAttributeValue<T>(this XElement element, string attribute) 
{ 
    if(typeof(T) == typeof(Int32)) 
    { 
     return Int32.Parse(element.Attribute(attribute).Value); 
    } 

    if(typeof(T) == typeof(Double)) 
    { 
     return Double.Parse(element.Attribute(attribute).Value); 
    } 

    if(typeof(T) == typeof(String)) 
    { 
     return element.Attribute(attribute).Value; 
    } 

    if(typeof(T) == typeof(ItemLookupType)) 
    { 
     return Enum.Parse(typeof(T), element.Attribute(attribute).Value); 
    } 
} 

或者更好的是:

public static Int32 ParseAsInt32(this XElement element, string attribute) 
{ 
    return Int32.Parse(element.Attribute(attribute).Value); 
} 

// etc, repeat for each type 

這第二種方法具有的入門內聯高得多的可能性的額外的好處,再加上它會(對於像Int32這樣的值類型)可以防止需要對值進行裝箱/取消裝箱。這兩種方法都會使該方法執行速度更快。

+0

+1您的第二個建議。沒有理由使用反射來找出類型,並在語言已經提供重載方法時執行相應的操作,這是一個爲您完成這項工作(並且可能更高效)的工具。 – 2012-07-19 18:59:31

+0

是的,放棄泛型並做出明確命名的方法是一個常見的解決方案,並沒有那麼糟糕。輸入'ParseAttributeValueAsString'幾乎是那麼容易,因爲'ParseAttributeValue ',帶或不帶智能感知。而通用方法已經是一大堆特殊情況了,所以如果你把它拉出到不同的方法上,就不會發生重複。 – 2012-07-19 19:42:32

2

不確定這是否正是你想要的,但是如果你先投入object再投注到T

public static T ParseAttributeValue<T>(this XElement element, string attribute) 
    { 
     if (typeof(T) == typeof(Int32)) 
     { 
      return (T)(object)Int32.Parse(element.Attribute(attribute).Value); 
     } 

     if (typeof(T) == typeof(Double)) 
     { 
      return (T)(object)Double.Parse(element.Attribute(attribute).Value); 
     } 

     if (typeof(T) == typeof(String)) 
     { 
      return (T)(object)element.Attribute(attribute).Value; 
     } 

     return default(T); 
    } 

但是你還是要在編譯時提供T,調用像方法:

int value = element.ParseAttributeValue<int>("attribute"); 
+0

這就是立即解決。總是允許顯式地將類型參數轉換爲'object',然後'object'可以轉換爲任何東西。但它很醜陋。它涉及到在Int32,Double和其他結構的情況下裝箱和拆箱。 – 2012-07-19 19:35:02

2

下面是做這件事的兩個方面...

static T ReadSetting<T>(string value) 
    { 
     object valueObj = null; 
     if (typeof(T) == typeof(Int32)) 
      valueObj = Int32.Parse(value); 
     return (T)valueObj; 
    } 
    static dynamic ReadSetting2<T>(string value) 
    { 
     if (typeof(T) == typeof(Int32)) 
      return Int32.Parse(value); 
     throw new UnsupportedException("Type is unsupported"); 
    } 
    static void Main(string[] args) 
    { 
     int val1 = ReadSetting<Int32>("2"); 
     int val2 = ReadSetting2<Int32>("3"); 
    } 
+1

如果泛型類型是值類型,那麼'ReadSetting'將拋出'NullReferenceException',因爲值類型不能爲空。這就是爲什麼把一個對象放在一個對象上是危險的。 – Joshua 2012-07-19 19:02:42

+0

這是真的,但在很多情況下它可能是一個合適的結果。例如,假設這不僅僅是簡單的示例代碼,用於顯示方法,如果調用者試圖讀取無法解析或未指定的設置,則可能會合理預期發生異常。 – MrWednesday 2012-07-19 19:58:53

1

C++模板,這有些東西會起作用,但前提是每一段代碼都在不同的,不同的專業領域。這使得這項工作是未使用的函數模板不編譯(或者更準確地說:不完全實例化)的事情,所以實際上的一段代碼將是無效的,如果該模板的副本中實例化與不同類型的不上來。

C#是不同的,據我所知沒有專業化的仿製藥。一種方式來完成你正在嘗試做的,而C#的限制範圍內工作是創建一個函數有一個更抽象的返回類型,並使用ParseAttributeValue只丟給T.

所以你會:

private static Object AbstractParseValue(System.Type t, XElement element, string attribute) 

public static T ParseAttributeValue<T>(this XElement element, string attribute) 
{ 
    return (T)AbstractParseValue(typeof(T), element, attribute); 
} 
8

的.Net已經有一堆可以用偉大的字符串轉換例程的! A TypeConverter可以爲您完成大部分繁重工作。然後,您不必擔心爲內置類型提供您自己的解析實現。

請注意,如果您需要處理在不同文化中表達的解析值,則可以使用TypeConverter上的API的區域識別版本。

using System.ComponentModel; 

public static T ParseAttributeValue<T>(this XElement element, string attribute) 
{ 
    var converter = TypeDescriptor.GetConverter(typeof(T)); 
    if (converter.CanConvertFrom(typeof(string))) 
    { 
     string value = element.Attribute(attribute).Value; 
     return (T)converter.ConvertFromString(value); 
    } 

    return default(T); 
} 

這將爲很多內置的類型,你可以裝飾自定義類型的具有TypeConverterAttribute工作,讓他們參與到:

下面的代碼將使用默認的文化解析值類型轉換遊戲。這意味着將來您將能夠解析新類型而無需更改ParseAttributeValue的實現。

看到:http://msdn.microsoft.com/en-us/library/system.componentmodel.typeconverter.aspx

0

我會建議,而不是每次執行例行一次測試時類型參數,你應該創建一個通用的靜態類是這樣的:

 
internal static class ElementParser<T> 
{ 
    public static Func<XElement, string, T> Convert = InitConvert; 

    T DefaultConvert(XElement element, string attribute) 
    { 
    return Default(T); // Or maybe throw exception, or whatever 
    } 

    T InitConvert(XElement element, string attribute) 
    { 
    if (ElementParser<int>.Convert == ElementParser<int>.InitConvert) 
    { // First time here for any type at all 
     Convert = DefaultConvert; // May overwrite this assignment below 
     ElementParser<int>.Convert = 
     (XElement element, string attribute) => 
      Int32.Parse(element.Attribute(attribute).Value); 
     ElementParser<double>.Convert = 
     (XElement element, string attribute) => 
      Int32.Parse(element.Attribute(attribute).Value); 
     // etc. for other types 
    } 
    else // We've done other types, but not this type, and we don't do anything nice for it 
    { 
     Convert = DefaultConvert; 
    } 
    return Convert(element, attribute);  
    } 
} 
public static T ParseAttributeValue(this XElement element, string attribute) 
{ 
    ElementParser<T>.Convert(element, attribute); 
} 

使用這種方法,只有在第一次使用特定類型時才需要進行特殊處理。之後,轉換隻能使用一個通用的委託調用來執行。一旦可以輕鬆添加任意數量的類型,甚至允許轉換器在運行時註冊爲任何所需類型。

+0

爲什麼不使用內建的TypeConverter類? – 2012-07-19 23:51:39

+0

如果這個特定的班級適合自己的申請,那就太好了。如果需要某個類別不支持的特定行爲,上面提供的方法可以很容易地適應任何所需的額外翻譯。 – supercat 2012-07-20 05:57:47