C # typy ogólne i sprawdzanie typów

83

Mam metodę, która używa IList<T>parametru jako parametru. Muszę sprawdzić, jaki jest typ tego Tobiektu i zrobić coś na jego podstawie. Próbowałem użyć Twartości, ale kompilator na to nie pozwala. Moje rozwiązanie jest następujące:

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        if (clause[0] is int || clause[0] is decimal)
        {
           //do something
        }
        else if (clause[0] is String)
        {
           //do something else
        }
        else if (...) //etc for all the types
        else
        {
           throw new ApplicationException("Invalid type");
        }
    } 
}

Musi być na to lepszy sposób. Czy istnieje sposób, aby sprawdzić, jaki typ Tjest przekazywany, a następnie użyć switchinstrukcji?

Jon
źródło
1
Osobiście chciałbym wiedzieć, co robisz specjalnie dla każdego typu danych. Jeśli wykonujesz mniej więcej taką samą transformację dla każdego typu danych, może być łatwiej mapować różne typy do wspólnego interfejsu i operować na tym interfejsie.
Juliet

Odpowiedzi:

121

Możesz użyć przeciążeń:

public static string BuildClause(List<string> l){...}

public static string BuildClause(List<int> l){...}

public static string BuildClause<T>(List<T> l){...}

Możesz też sprawdzić typ parametru ogólnego:

Type listType = typeof(T);
if(listType == typeof(int)){...}
jonnii
źródło
23
+1: przeciążenia są tutaj zdecydowanie najlepszym rozwiązaniem pod względem konstrukcji i długoterminowej konserwacji. Sprawdzanie typu w czasie wykonywania parametru ogólnego wydaje się zbyt ironiczne, aby kodować z prostą twarzą.
Juliet
Doskonałym przykładem tego, kiedy byłoby to przydatne, jest serializacja ogólna z bardzo różnymi typami. Jeśli przekazywany obiekt jest łańcuchem, po co ta dodatkowa praca? Jeśli jest to ciąg znaków, po prostu wrócić oryginalny łańcuch bez próby jakiegokolwiek dodatkowego przetwarzania
watkinsmatthewp
Przepraszam, czy istnieje sposób na osiągnięcie tego poprzez używanie switch-casezamiast if-else?
Tân
@HappyCoding niestety nie = (możesz to zrobić z następną wersją C #.
jonnii
7
NIE powinieneś polegać na przeciążeniu ogólnym (zobacz tę odpowiedź ), jeśli przeciążenia są funkcjonalnie różne (weź pod uwagę również skutki uboczne), ponieważ nie możesz zagwarantować, że zostanie wywołane bardziej wyspecjalizowane przeciążenie. Zasada jest taka: jeśli MUSISZ wykonać specjalną logikę dla określonego typu, to musisz sprawdzić ten typ i nie używać przeciążania; jednakże, jeśli WOLISZ wykonywać tylko wyspecjalizowaną logikę (np. w celu poprawy wydajności), ale wszystkie przeciążenia, w tym przypadek ogólny, dają ten sam wynik, możesz użyć przeciążenia zamiast sprawdzania typów.
tomosius
23

Możesz użyć typeof(T).

private static string BuildClause<T>(IList<T> clause)
{
     Type itemType = typeof(T);
     if(itemType == typeof(int) || itemType == typeof(decimal))
    ...
}
bdowden
źródło
7

Domyślnie wiem, że nie ma świetnego sposobu. Jakiś czas temu zdenerwowałem się tym i napisałem małą klasę narzędzi, która trochę pomogła i sprawiła, że ​​składnia była nieco czystsza. Zasadniczo zamienia kod w

TypeSwitcher.Do(clause[0],
  TypeSwitch.Case<int>(x => ...),  // x is an int
  TypeSwitch.Case<decimal>(d => ...), // d is a decimal 
  TypeSwitch.Case<string>(s => ...)); // s is a string

Pełny post na blogu i szczegóły dotyczące implementacji są dostępne tutaj

JaredPar
źródło
6

A ponieważ C # ewoluował, możesz (teraz) używać dopasowywania wzorców .

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        switch (clause[0])
        {
            case int x: // do something with x, which is an int here...
            case decimal x: // do something with x, which is a decimal here...
            case string x: // do something with x, which is a string here...
            ...
            default: throw new ApplicationException("Invalid type");
        }
    }
}

I znowu w przypadku wyrażeń przełączających w C # 8.0 składnia staje się jeszcze bardziej zwięzła.

private static string BuildClause<T>(IList<T> clause)
{
    if (clause.Count > 0)
    {
        return clause[0] switch
        {
            int x => "some string related to this int",
            decimal x => "some string related to this decimal",
            string x => x,
            ...,
            _ => throw new ApplicationException("Invalid type")
        }
    }
}
Zestaw
źródło
4

Typ operatora ...

typeof(T)

... nie będzie działać z instrukcją switch c #. Ale co powiesz na to? Poniższy post zawiera klasę statyczną ...

Czy istnieje lepsza alternatywa dla „włączania typu”?

... to pozwoli ci napisać taki kod:

TypeSwitch.Do(
    sender,
    TypeSwitch.Case<Button>(() => textBox1.Text = "Hit a Button"),
    TypeSwitch.Case<CheckBox>(x => textBox1.Text = "Checkbox is " + x.Checked),
    TypeSwitch.Default(() => textBox1.Text = "Not sure what is hovered over"));
Robert Harvey
źródło
Zobacz także odpowiedź JaredPar tutaj.
Robert Harvey
3

Dla każdego, kto mówi, że sprawdzanie typów i robienie czegoś w oparciu o typ nie jest dobrym pomysłem w przypadku leków generycznych, w pewnym sensie się zgadzam, ale myślę, że mogą zaistnieć okoliczności, w których ma to sens.

Na przykład, jeśli masz klasę, która mówi, że jest zaimplementowana w ten sposób (Uwaga: nie pokazuję wszystkiego, co robi ten kod dla uproszczenia i po prostu wyciąłem i wkleiłem tutaj, więc może nie budować lub nie działać zgodnie z przeznaczeniem, tak jak cały kod, ale to ma sens. Ponadto Unit to wyliczenie):

public class FoodCount<TValue> : BaseFoodCount
{
    public TValue Value { get; set; }

    public override string ToString()
    {
        if (Value is decimal)
        {
            // Code not cleaned up yet
            // Some code and values defined in base class

            mstrValue = Value.ToString();
            decimal mdecValue;
            decimal.TryParse(mstrValue, out mdecValue);

            mstrValue = decimal.Round(mdecValue).ToString();

            mstrValue = mstrValue + mstrUnitOfMeasurement;
            return mstrValue;
        }
        else
        {
            // Simply return a string
            string str = Value.ToString() + mstrUnitOfMeasurement;
            return str;
        }
    }
}

...

public class SaturatedFat : FoodCountWithDailyValue<decimal>
{
    public SaturatedFat()
    {
        mUnit = Unit.g;
    }

}

public class Fiber : FoodCount<int>
{
    public Fiber()
    {
        mUnit = Unit.g;
    }
}

public void DoSomething()
{
       nutritionFields.SaturatedFat oSatFat = new nutritionFields.SaturatedFat();

       string mstrValueToDisplayPreFormatted= oSatFat.ToString();
}

Podsumowując, myślę, że istnieją uzasadnione powody, dla których warto sprawdzić, jaki jest typ generyczny, aby zrobić coś specjalnego.

Jan
źródło
2

Nie ma sposobu, aby użyć instrukcji switch do tego, co chcesz. Instrukcja switch musi być dostarczona z typami całkowitymi, które nie obejmują typów złożonych, takich jak obiekt „Type”, ani żadnego innego typu obiektu w tym zakresie.

womp
źródło
2

Twoja konstrukcja całkowicie przeczy celowi metody ogólnej. Celowo jest to brzydkie, ponieważ musi istnieć lepszy sposób na osiągnięcie tego, co próbujesz osiągnąć, chociaż nie dostarczyłeś nam wystarczających informacji, aby dowiedzieć się, co to jest.

mqp
źródło
2

Możesz to zrobić typeOf(T), ale dwukrotnie sprawdzę twoją metodę i upewnię się, że nie naruszasz tutaj pojedynczej odpowiedzialności. Byłby to zapach kodu, co nie oznacza, że ​​nie powinno się tego robić, ale należy być ostrożnym.

Celem generycznych jest możliwość tworzenia algorytmów niezależnych od typu, gdybyś nie obchodziło, jaki jest typ lub jeśli mieści się w określonym zestawie kryteriów. Twoja implementacja nie jest zbyt ogólna.

JoshBerke
źródło
2

Mam nadzieję, że okaże się to pomocne:

  • typeof(IList<T>).IsGenericType == true
  • typeof(IList<T>).GetGenericTypeDefinition() == typeof(IList<>)
  • typeof(IList<int>).GetGenericArguments()[0] == typeof(int)

https://dotnetfiddle.net/5qUZnt

Jaider
źródło
0

Co powiesz na to :

            // Checks to see if the value passed is valid. 
            if (!TypeDescriptor.GetConverter(typeof(T)).IsValid(value))
            {
                throw new ArgumentException();
            }
Bert
źródło