C # Generika und Typprüfung

82

Ich habe eine Methode, die ein IList<T>als Parameter verwendet. Ich muss überprüfen, was der Typ dieses TObjekts ist, und etwas basierend darauf tun. Ich habe versucht, den TWert zu verwenden, aber der Compiler lässt ihn nicht zu. Meine Lösung lautet wie folgt:

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

Es muss einen besseren Weg geben, dies zu tun. Gibt es eine Möglichkeit, den Typ der Übergabe zu überprüfen Tund dann eine switchAnweisung zu verwenden?

Jon
quelle
1
Ich persönlich würde gerne wissen, was Sie für jeden Datentyp speziell tun. Wenn Sie für jeden Datentyp ungefähr dieselbe Transformation durchführen, ist es möglicherweise einfacher, verschiedene Typen einer gemeinsamen Schnittstelle zuzuordnen und diese Schnittstelle zu bearbeiten.
Julia

Antworten:

120

Sie könnten Überladungen verwenden:

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

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

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

Oder Sie können den Typ des generischen Parameters überprüfen:

Type listType = typeof(T);
if(listType == typeof(int)){...}
jonnii
quelle
23
+1: Überlastungen sind hier definitiv die beste Lösung in Bezug auf Design und langfristige Wartbarkeit. Eine Laufzeit-Typprüfung eines generischen Parameters scheint einfach zu ironisch, um mit einem geraden Gesicht zu codieren.
Julia
Ein gutes Beispiel dafür, wann dies nützlich wäre, ist die generische Serialisierung mit stark variierenden Typen. Wenn das übergebene Objekt eine Zeichenfolge ist, warum funktioniert die zusätzliche Arbeit? Wenn es sich um eine Zeichenfolge handelt, geben Sie einfach die ursprüngliche Zeichenfolge zurück, ohne eine zusätzliche Verarbeitung zu versuchen
watkinsmatthewp
Entschuldigung, gibt es eine Möglichkeit, dies durch Verwendung von switch-caseanstelle von zu erreichen if-else?
Tân
@HappyCoding leider nicht = (Sie können dies möglicherweise mit der nächsten Version von C # tun.
jonnii
7
Sie sollten sich NICHT auf generische Überlastungen verlassen (siehe diese Antwort ), wenn Ihre Überlastungen funktional unterschiedlich sind (berücksichtigen Sie auch Nebenwirkungen), da Sie nicht garantieren können, dass eine speziellere Überlastung aufgerufen wird. Die Regel lautet hier: Wenn Sie eine spezielle Logik für einen bestimmten Typ ausführen MÜSSEN, müssen Sie nach diesem Typ suchen und dürfen keine Überladung verwenden. Wenn Sie jedoch nur eine spezielle Logik bevorzugen (dh für Leistungsverbesserungen), aber alle Überladungen einschließlich des allgemeinen Falls zum gleichen Ergebnis führen, können Sie die Überladung anstelle der Typprüfung verwenden.
Tomosius
23

Sie können verwenden typeof(T).

private static string BuildClause<T>(IList<T> clause)
{
     Type itemType = typeof(T);
     if(itemType == typeof(int) || itemType == typeof(decimal))
    ...
}
bdowden
quelle
7

Standardmäßig wissen Sie, dass es keinen guten Weg gibt. Vor einiger Zeit war ich frustriert und schrieb eine kleine Utility-Klasse, die ein bisschen half und die Syntax ein bisschen sauberer machte. Im Wesentlichen verwandelt es den Code in

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

Der vollständige Blog-Beitrag und Details zur Implementierung finden Sie hier

JaredPar
quelle
6

Und da sich C # weiterentwickelt hat, können Sie (jetzt) ​​den Mustervergleich verwenden .

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

Und wieder wird mit Switch-Ausdrücken in C # 8.0 die Syntax noch prägnanter.

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")
        }
    }
}
Kit
quelle
4

Die Art des Operators ...

typeof(T)

... funktioniert nicht mit der Anweisung c # switch. Aber wie wäre es damit? Der folgende Beitrag enthält eine statische Klasse ...

Gibt es eine bessere Alternative zum Einschalten des Typs?

... damit können Sie Code wie folgt schreiben:

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
quelle
Siehe auch JaredPars Antwort hier.
Robert Harvey
3

Für alle, die sagen, dass es keine gute Idee für Generika ist, Typen zu überprüfen und etwas basierend auf dem Typ zu tun, stimme ich zu, aber ich denke, es könnte einige Umstände geben, unter denen dies vollkommen sinnvoll ist.

Zum Beispiel, wenn Sie eine Klasse haben, die sagt, dass sie wie folgt implementiert ist (Hinweis: Ich zeige der Einfachheit halber nicht alles, was dieser Code tut, und habe ihn hier einfach ausgeschnitten und eingefügt, damit er möglicherweise nicht wie beabsichtigt erstellt oder funktioniert, sondern der gesamte Code es bringt den Punkt auf den Punkt. Außerdem ist Unit eine Aufzählung):

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

Zusammenfassend denke ich, dass es triftige Gründe gibt, warum Sie vielleicht überprüfen möchten, um zu sehen, um welchen Typ es sich bei dem Generikum handelt, um etwas Besonderes zu tun.

John
quelle
2

Es gibt keine Möglichkeit, die switch-Anweisung für das zu verwenden, was Sie möchten. Die switch-Anweisung muss mit integralen Typen geliefert werden, die keine komplexen Typen wie ein "Typ" -Objekt oder einen anderen Objekttyp enthalten.

womp
quelle
2

Ihre Konstruktion macht den Zweck einer generischen Methode völlig zunichte. Es ist absichtlich hässlich, weil es einen besseren Weg geben muss, um das zu erreichen, was Sie erreichen wollen, obwohl Sie uns nicht genügend Informationen gegeben haben, um herauszufinden, was das ist.

mqp
quelle
2

Sie können dies tun typeOf(T), aber ich würde Ihre Methode überprüfen und sicherstellen, dass Sie hier nicht gegen die Einzelverantwortung verstoßen. Dies wäre ein Code-Geruch, und das heißt nicht, dass dies nicht getan werden sollte, sondern dass Sie vorsichtig sein sollten.

Der Sinn von Generika besteht darin, typunabhängige Algorthims zu erstellen, wenn es Ihnen egal ist, um welchen Typ es sich handelt oder solange er einem bestimmten Kriteriensatz entspricht. Ihre Implementierung ist nicht sehr allgemein.

JoshBerke
quelle
2

Ich hoffe, Sie finden das hilfreich:

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

https://dotnetfiddle.net/5qUZnt

Jaider
quelle
0

Wie wäre es damit :

            // Checks to see if the value passed is valid. 
            if (!TypeDescriptor.GetConverter(typeof(T)).IsValid(value))
            {
                throw new ArgumentException();
            }
Bert
quelle