2012-07-19 15 views
20

Czy istnieje sposób, aby metoda zwróciła jeden z wielu typów ogólnych z metody? Na przykład, mam następujący:Zmienna rodzajowy typ zwrotu w języku 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); 
     } 
    } 

(Jest to tylko bardzo szybkie makieta, jestem świadomy, że każdy kod produkcja musiałaby być znacznie bardziej dokładny w kontroli zerowych etc ...)

Ale kompilator tego nie lubi, narzekając, że Int32 nie może zostać niejawnie przekonwertowany na T (nie działa również z rzutowaniem). Mogę to zrozumieć. W czasie kompilacji nie ma możliwości dowiedzenia się, co to jest T, ale sprawdzam to wcześniej. Czy mimo to mogę to wykonać?

+0

Nie, '' jest tylko parametrem typu. Używasz tego typu bez nawiasów. – Femaref

+0

@Femaref - Dobra uwaga. Pytanie wycofane. –

+0

Jeśli przechowujesz wartość jako Obiekt, możesz ją przesłać. Jest również możliwe, że wpisanie funkcji jako dynamicznej może pomóc w wykonaniu tego zadania bez rzutowania. – MrWednesday

Odpowiedz

20

Zrobiłem te typy ogólnych metod w przeszłości. Najłatwiejszym sposobem wnioskowania o typ jest zapewnienie ogólnej funkcji konwertera.

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

Można go używać jak poniżej:

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

Można nawet podać swoje własne funkcje i mieć całą zabawę w świecie (nawet powrócić anonimowych typów):

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

Nie sądzę, że może stać się o wiele bardziej elegancka. +1. – Adam

+0

Dobrą rzeczą jest to, że 'T' można wywnioskować z użycia, ponieważ znajduje się wewnątrz' Func <,> '. Więc nawet jeśli musimy podać dodatkowy parametr (a mianowicie delegata 'Func <,>'), nie będziemy musieli jawnie określać parametru typu 'T'. –

+1

Wiele konwerterów generycznych już istnieje w środowisku .Net: 'TypeDescriptor.GetTypeConverter (typeof (T));' –

4

Dlaczego w ogóle używasz parametru typu jako typu zwrotu? To działa, tylko wymaga obsady po wywołaniu:

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

Albo jeszcze lepiej:

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

// etc, repeat for each type 

To drugie rozwiązanie ma dodatkową zaletę posiadania znacznie wyższe prawdopodobieństwo uzyskania inlined, plus to będzie (w przypadku typów wartości, takich jak Int32), nie ma potrzeby wstawiania/rozpakowywania wartości. Obie te metody spowodują, że metoda będzie działać nieco szybciej.

+0

+1 za drugą sugestię. Nie ma powodu, aby korzystać z refleksji, aby dowiedzieć się rodzaju i wykonać odpowiednie działanie, gdy język już zapewnia przeciążone metody, obiekt, który wykonuje tę pracę dla ciebie (i prawdopodobnie bardziej wydajnie). –

+0

Tak, wyrzeczenie się generyków i stosowanie metod o wyraźnie określonych nazwach jest powszechnym rozwiązaniem tego i nie jest takie złe. Wpisanie 'ParseAttributeValueAsString' jest prawie tak proste, jak' ParseAttributeValue ', z lub bez intellisense. A metoda generyczna była już zbiorem specjalnych przypadków, więc nie powielono jej, gdybyś wyciągnął ją na różne sposoby. –

2

Nie wiem, czy jest to dokładnie to, czego potrzebujesz, ale możesz przywrócić skuteczność, jeśli najpierw wyrzucisz na numer object, a następnie na 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); 
    } 

Jednak trzeba jeszcze zapewnić T w czasie kompilacji, wywołanie metody jak:

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

To natychmiastowe rozwiązanie. Zawsze można jawnie rzutować parametr typu na obiekt "object", a następnie "object" można rzutować na dowolne.Ale to jest brzydkie. Obejmuje to także boksowanie i rozpakowywanie w przypadku 'Int32',' Double' i innych struktur. –

2

Oto dwa sposoby robi ...

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' rzuci' NullReferenceException', jeśli typ ogólny jest typem wartości, ponieważ typy wartości nie mogą mieć wartości null. Dlatego niebezpiecznie jest poleć na "obiekt". – Joshua

+0

To prawda, ale może to być odpowiedni wynik w wielu przypadkach. Na przykład, udając, że nie jest to zwykły przykładowy kod napisany w celu pokazania metodologii, osoba wywołująca może rozsądnie oczekiwać wyjątku, jeśli próbuje odczytać ustawienie, którego nie można przeanalizować lub nie zostało określone. – MrWednesday

1

z C++ szablony, to coś by zadziałało, ale tylko jeśli każdy fragment kodu byłby w innej, odrębnej specjalizacji. Rzeczą, która czyni tę pracę jest to, że nieużywane szablony funkcji nie są kompilowane (lub dokładniej: nie w pełni utworzone), więc fakt, że fragment kodu byłby nieprawidłowy, gdyby ta kopia szablonu była tworzona za pomocą innego typu, nie wchodzić na górę.

C# jest inny, a AFAIK nie ma specjalizacji dla leków generycznych. Jednym ze sposobów osiągnięcia tego, co próbujesz zrobić, podczas pracy z ograniczeniami języka C#, byłoby utworzenie jednej funkcji z bardziej abstrakcyjnym typem zwracania i użycie parametru ParseAttributeValue tylko do rzucenia go do T.

Więc trzeba:

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

i

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

Net ma już kilka wielkich procedur konwersji ciąg można użyć! A TypeConverter może wykonać większość ciężkiego podnoszenia dla ciebie. Wtedy nie musisz się martwić dostarczając własnych implementacji parsowania dla wbudowanych typów.

Należy pamiętać, że istnieją interfejsy API obsługujące locale pod adresem TypeConverter, które mogą być używane, jeśli konieczne jest przetwarzanie wartości analizowanych w różnych kulturach.

Poniższy kod będzie analizować wartości przy użyciu domyślnego Kultura:

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

to będzie pracować dla wielu typów wbudowanych, i można ozdobić typów niestandardowych z TypeConverterAttribute celu umożliwienia im udziału w także konwersję typu. Oznacza to, że w przyszłości będziesz mógł analizować nowe typy bez konieczności zmiany implementacji ParseAttributeValue.

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

0

Sugerowałbym, że zamiast testowania parametru type każdym razem procedura jest wykonywana, należy utworzyć rodzajowe klasy statyczne coś takiego:

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

Stosując to podejście, jedna osoba będzie musiała wykonać specjalne czynności tylko przy pierwszym użyciu określonego typu. Następnie konwersję można przeprowadzić przy użyciu tylko jednego ogólnego wywołania delegowanego. Raz można łatwo dodać dowolną liczbę typów, a nawet umożliwić zarejestrowanie konwerterów dla dowolnego typu w środowisku wykonawczym.

+0

Dlaczego po prostu nie używać wbudowanej klasy 'TypeConverter'? –

+0

Jeśli ta klasa będzie działać poprawnie dla aplikacji, to świetnie. Jeśli ktoś potrzebuje konkretnych zachowań, których ta klasa nie popiera, powyższe podejście może z łatwością pomieścić dowolne dodatkowe tłumaczenia. – supercat