Jak ustawić zwracany typ metody jako ogólny?

166

Czy istnieje sposób, aby uczynić tę metodę ogólną, aby móc zwracać ciąg, bool, int lub double? W tej chwili zwraca ciąg, ale jeśli może znaleźć „prawda” lub „fałsz” jako wartość konfiguracyjną, chciałbym na przykład zwrócić wartość logiczną.

    public static string ConfigSetting(string settingName)
    {  
         return ConfigurationManager.AppSettings[settingName];
    }
MacGyver
źródło
Czy jest sposób, aby dowiedzieć się, jakiego typu są poszczególne ustawienia?
thecoshman
2
Myślę, że pytanie, które naprawdę chcesz zadać, brzmi: „Jak sprawić, aby konfiguracja mojej aplikacji była silnie wpisywana?” Jednak minęło zbyt dużo czasu, odkąd nad tym pracowałem, żeby napisać poprawną odpowiedź.
Simon,
Tak, najlepiej byłoby, gdybyś nie musiał przekazywać typu do metody. Zamierzam mieć tylko 4 typy, o których wspomniałem. Więc jeśli ustawione jest „true” / „false”, chcę, aby ta funkcja zwracała wartość logiczną (bez konieczności przekazywania jej do metody), prawdopodobnie mogę połączyć int i double w po prostu double, a wszystko inne powinno być ciągiem. To, co zostało już odebrane, będzie działało dobrze, ale za każdym razem muszę podawać typ, co prawdopodobnie jest w porządku.
MacGyver,
3
Twój komentarz brzmi tak, jakbyś prosił o metodę, która zwróci silnie wpisaną wartość bool (lub string, lub int, lub to, co masz) w czasie wykonywania na podstawie rzeczywistych danych pobranych dla klucza nazwy ustawienia. C # nie zrobi tego za Ciebie; w żaden sposób nie możesz poznać typu tej wartości w czasie kompilacji. Innymi słowy, jest to pisanie dynamiczne, a nie statyczne. C # może to zrobić za Ciebie, jeśli użyjesz dynamicsłowa kluczowego. Wiąże się to z kosztem wydajności, ale w przypadku odczytu pliku konfiguracyjnego koszt wydajności jest prawie na pewno nieistotny.
phoog

Odpowiedzi:

354

Musisz uczynić z tego metodę ogólną, na przykład:

public static T ConfigSetting<T>(string settingName)
{  
    return /* code to convert the setting to T... */
}

Ale dzwoniący będzie musiał określić typ, którego oczekuje. Możesz wtedy potencjalnie użyć Convert.ChangeType, zakładając, że obsługiwane są wszystkie odpowiednie typy:

public static T ConfigSetting<T>(string settingName)
{  
    object value = ConfigurationManager.AppSettings[settingName];
    return (T) Convert.ChangeType(value, typeof(T));
}

Nie jestem do końca przekonany, że to wszystko jest dobry pomysł, pamiętajcie ...

Jon Skeet
źródło
21
/ * kod konwertujący ustawienie na T ... * / i oto cała powieść :)
Adrian Iftode
1
Czy nie wymagałoby to znajomości typu ustawienia, które chcesz uzyskać, co może nie być możliwe.
thecoshman,
2
@thecoshman: Tak, ale gdybyś tego nie zrobił, to co zrobisz ze zwróconą wartością?
George Duckett,
5
Chociaż ta odpowiedź jest oczywiście poprawne, i jak można zauważyć, spełnia prośbę PO za, to chyba warto wspomnieć, że stare podejście odrębnych metod ( ConfigSettingString, ConfigSettingBooletc.) ma tę zaletę ciał metod, które będą krótsze, bardziej przejrzyste i lepiej koncentruje .
phoog
4
Jeśli nie jest to zalecane, jaki jest cel ogólnych typów zwracanych?
bobbyalex
29

Możesz użyć Convert.ChangeType():

public static T ConfigSetting<T>(string settingName)
{
    return (T)Convert.ChangeType(ConfigurationManager.AppSettings[settingName], typeof(T));
}
Rozbite szkło
źródło
13

Można to zrobić na wiele sposobów (wymienione według priorytetów, specyficznych dla problemu PO)

  1. Opcja 1: Proste podejście - utwórz wiele funkcji dla każdego oczekiwanego typu zamiast jednej funkcji ogólnej.

    public static bool ConfigSettingInt(string settingName)
    {  
         return Convert.ToBoolean(ConfigurationManager.AppSettings[settingName]);
    }
  2. Opcja 2: Jeśli nie chcesz używać wymyślnych metod konwersji - prześlij wartość na obiekt, a następnie na typ ogólny.

    public static T ConfigSetting<T>(string settingName)
    {  
         return (T)(object)ConfigurationManager.AppSettings[settingName];
    }

    Uwaga - spowoduje to wyświetlenie błędu, jeśli rzut jest nieprawidłowy (Twój przypadek). Nie polecałbym tego robić, jeśli nie masz pewności co do typu rzutowania, raczej wybierz opcję 3.

  3. Opcja 3: Ogólna z bezpieczeństwem typów - Utwórz funkcję ogólną do obsługi konwersji typów.

    public static T ConvertValue<T,U>(U value) where U : IConvertible
    {
        return (T)Convert.ChangeType(value, typeof(T));
    } 

    Uwaga - T jest typem oczekiwanym, zwróć uwagę na ograniczenie gdzie (typ U musi być IConvertible, aby uchronić nas przed błędami)

RollerCosta
źródło
4
Dlaczego trzecia opcja jest generyczna U? Nie ma sensu tego robić, a to utrudnia wywołanie metody. Po prostu zaakceptuj IConvertiblezamiast tego. Myślę, że nie warto uwzględniać drugiej opcji dla tego pytania, ponieważ nie odpowiada ona na zadane pytanie. Prawdopodobnie powinieneś także zmienić nazwę metody w pierwszej opcji ...
Jon Skeet,
7

Musisz przekonwertować typ wartości zwracanej metody na typ Generic, który przekazujesz do metody podczas wywoływania.

    public static T values<T>()
    {
        Random random = new Random();
        int number = random.Next(1, 4);
        return (T)Convert.ChangeType(number, typeof(T));
    }

Musisz przekazać typ, który można kastować dla wartości zwracanej przez tę metodę.

Jeśli chcesz zwrócić wartość, która nie jest kastowalna do przekazywanego typu ogólnego, być może będziesz musiał zmienić kod lub upewnić się, że przekazujesz typ, który jest kastowalny dla wartości zwracanej metody. Dlatego takie podejście nie jest zalecane.

Vinay Chanumolu
źródło
Na miejscu - dla mnie ostatnia linia return (T)Convert.ChangeType(number, typeof(T));była dokładnie tym, czego mi brakowało -
pozdrawiam
1

Utwórz funkcję i przekaż parametr put jako typ ogólny.

 public static T some_function<T>(T out_put_object /*declare as Output object*/)
    {
        return out_put_object;
    }
Prabhakar
źródło
W kilku przypadkach jest to całkiem sprytne. Jak wyciąganie danych z bazy danych. Wiesz, że otrzymasz listę danych typu T. Metoda ładowania po prostu nie wie, jakiego typu T chcesz teraz. Po prostu przekaż do tego nowy List <WantedObject>, a metoda może wykonać swoje zadanie i wypełnić listę przed jej zwróceniem. Miły!
Marco Heumann
0

Spróbuj poniższego kodu:

public T? GetParsedOrDefaultValue<T>(string valueToParse) where T : struct, IComparable
{
 if(string.EmptyOrNull(valueToParse))return null;
  try
  {
     // return parsed value
     return (T) Convert.ChangeType(valueToParse, typeof(T));
  }
  catch(Exception)
  {
   //default as null value
   return null;
  }
 return null;
}
Sid
źródło
-1
 private static T[] prepareArray<T>(T[] arrayToCopy, T value)
    {
        Array.Copy(arrayToCopy, 1, arrayToCopy, 0, arrayToCopy.Length - 1);
        arrayToCopy[arrayToCopy.Length - 1] = value;
        return (T[])arrayToCopy;
    }

Robiłem to w całym kodzie i chciałem umieścić to w metodzie. Chciałem to tutaj udostępnić, ponieważ nie musiałem używać Convert.ChangeType dla mojej wartości zwracanej. Może to nie jest najlepsza praktyka, ale zadziałała. Ta metoda przyjmuje tablicę typu ogólnego i wartość do dodania na końcu tablicy. Tablica jest następnie kopiowana z usuniętą pierwszą wartością, a wartość pobrana do metody jest dodawana na końcu tablicy. Ostatnią rzeczą jest to, że zwracam ogólną tablicę.

Adam Howard
źródło