Was ist Missbrauch von Generika?


35

Beim Überprüfen von Code habe ich festgestellt, dass die Möglichkeit besteht, ihn auf die Verwendung von Generika umzustellen. Der (verschleierte) Code sieht folgendermaßen aus:

public void DoAllTheThings(Type typeOfTarget, object[] possibleTargets)
{
    var someProperty = typeOfTarget.GetProperty(possibleTargets[0]);
    ...
}

Dieser Code könnte durch Generika ersetzt werden:

public void DoAllTheThings<T>(object[] possibleTargets[0])
{
    var someProperty = type(T).getProperty(possibleTargets[0]);
    ...
}

Bei der Untersuchung der Vor- und Nachteile dieses Ansatzes fand ich einen Begriff namens generischer Missbrauch . Sehen:

Meine Frage besteht aus zwei Teilen:

  1. Gibt es irgendwelche Vorteile, wenn Sie auf solche Generika umsteigen? (Leistung? Lesbarkeit?)
  2. Was ist Generika-Missbrauch? Und ist die Verwendung eines Generikums jedes Mal, wenn es einen Typparameter gibt, ein Missbrauch ?

2
Ich bin verwirrt. Sie kennen den Namen der Eigenschaft im Voraus, aber nicht den Typ des Objekts, sodass Sie mithilfe der Reflexion einen Wert erhalten?
MetaFight

3
@MetaFight Ich weiß, es ist ein kompliziertes Beispiel, der eigentliche Code wäre zu lang und schwierig, ihn hier zu platzieren. Unabhängig davon besteht der Zweck meiner Frage darin, festzustellen, ob das Ersetzen des Parameters Type durch ein generisches als gute oder schlechte Form angesehen wird.
SyntaxRules

12
Ich habe immer gedacht, dass dies der typische Grund ist, warum Generika existieren (leckeres Wortspiel nicht beabsichtigt). Warum tippst du selbst, wenn du dein Typensystem nutzen kannst, um es für dich zu tun?
MetaFight

2
Ihr Beispiel ist seltsam, aber ich habe das Gefühl, es ist genau das, was viele IoD-Bibliotheken tun
Ewan

14
Keine Antwort, aber es ist erwähnenswert, dass das zweite Beispiel weniger flexibel ist als das erste. Wenn Sie die Funktion generisch festlegen, können Sie keinen Laufzeittyp mehr übergeben. Generics müssen den Typ zur Kompilierungszeit kennen.
KChaloux

Antworten:


87

Wenn Generika richtig angewendet werden, entfernen sie Code, anstatt ihn nur neu anzuordnen. Der Code, den Generika am besten entfernen können, ist in erster Linie Typumwandlung, Reflexion und dynamisches Schreiben. Daher könnte generischer Missbrauch im Vergleich zu einer nicht generischen Implementierung lose als das Erstellen von generischem Code definiert werden, ohne die Typisierung, Reflektion oder dynamische Typisierung wesentlich zu verringern.

In Bezug auf Ihr Beispiel würde ich eine angemessene Verwendung von Generika erwarten, um die object[]in eine T[]oder ähnliche zu ändern und zu vermeiden Typeoder typeinsgesamt. Dies erfordert möglicherweise an anderer Stelle erhebliche Umgestaltungen. Wenn jedoch die Verwendung von Generika in diesem Fall angemessen ist, sollte dies insgesamt einfacher ausfallen, wenn Sie fertig sind.


3
Das ist ein großartiger Punkt. Ich gebe zu, dass der Code, der mich dazu brachte, darüber nachzudenken, einige schwer lesbare Überlegungen anstellte. Ich werde sehen , ob ich ändern kann object[]zu T[].
SyntaxRules

3
Mir gefällt die Charakterisierung von Entfernen und Neuanordnen.
Kupfer.hat

13
Sie haben eine wichtige Verwendung von Generika verpasst - nämlich die Reduzierung von Doppelspurigkeiten. (Obwohl Sie die Doppelarbeit reduzieren können, indem Sie eine der anderen Sünden einführen, die Sie erwähnen). Wenn die Duplizierung reduziert wird, ohne Typecasts, Reflektionen oder dynamische Typisierung zu entfernen, ist dies in Ordnung.
Michael Anderson

4
Hervorragende Antwort. Ich würde hinzufügen, dass in diesem speziellen Fall das OP möglicherweise zu einer Schnittstelle anstelle von Generika wechseln muss . Es sieht so aus, als würde die Reflektion verwendet, um auf bestimmte Eigenschaften des Objekts zuzugreifen. Eine Schnittstelle ist viel besser geeignet, damit der Compiler feststellen kann, ob solche Eigenschaften tatsächlich vorhanden sind.
jpmc26

4
@MichaelAnderson "Code entfernen, anstatt ihn nur neu
anzuordnen

5

Ich würde die No-Nonsense-Regel verwenden: Generics existieren, wie alle anderen Programmierkonstrukte, um ein Problem zu lösen . Wenn es kein Problem gibt, das für Generika gelöst werden kann, ist deren Verwendung Missbrauch.

Im speziellen Fall von Generika werden diese meistens von konkreten Typen weg abstrahiert, sodass Codeimplementierungen für die verschiedenen Typen zu einer generischen Vorlage zusammengefasst werden können (oder wie auch immer Ihre Sprache es nennt). Angenommen, Sie haben Code, der einen Typ verwendet Foo. Sie können diesen Typ durch einen generischen ersetzen T, aber wenn Sie diesen Code nur zum Arbeiten benötigen Foo, gibt es einfach keinen anderen Code, mit dem Sie ihn zusammenfalten können. Es gibt also kein Problem, das gelöst werden muss. Die Indirektion des Hinzufügens des Generikums würde also nur dazu dienen, die Lesbarkeit zu verringern.

Folglich würde ich vorschlagen, den Code nur ohne Verwendung von Generika zu schreiben, bis Sie die Notwendigkeit sehen, sie einzuführen (weil Sie eine zweite Instanziierung benötigen). Dann und nur dann ist es an der Zeit, den Code für die Verwendung von Generika umzugestalten. Jede Verwendung vor diesem Punkt ist in meinen Augen Missbrauch.


Das klingt ein bisschen zu umständlich, also möchte ich Sie daran erinnern:
Es gibt keine Regel ohne Ausnahmen bei der Programmierung. Diese Regel enthalten.


Interessante Gedanken. Eine geringfügige Neuformulierung Ihrer Kriterien könnte jedoch hilfreich sein. Angenommen, OP benötigt eine neue "Komponente", die ein int einem String zuordnet und umgekehrt. Es ist sehr offensichtlich, dass es ein allgemeines Bedürfnis löst und in Zukunft leicht wiederverwendbar sein könnte, wenn es generisch gemacht wird. Dieser Fall würde in Ihre Definition des Missbrauchs von Generika fallen. Wäre es nicht sinnvoll, einen unbedeutenden zusätzlichen Aufwand in das Design zu investieren, ohne dass dies missbräuchlich wäre, wenn der sehr allgemeine Grundbedarf erkannt worden wäre?
Christophe

@Christophe Das ist in der Tat eine knifflige Frage. Ja, es gibt Situationen, in denen es in der Tat besser ist, meine Regel zu ignorieren (es gibt keine Regel ohne Ausnahme bei der Programmierung!). Der spezielle String-Konvertierungsfall ist jedoch eher involviert: 1. Sie müssen vorzeichenbehaftete und vorzeichenlose Integer-Typen separat behandeln. 2. Sie müssen Float-Konvertierungen getrennt von Integer-Konvertierungen behandeln. 3. Sie können eine Implementierung für die größten verfügbaren Integer-Typen für kleinere Typen wiederverwenden. Möglicherweise möchten Sie proaktiv einen generischen Wrapper schreiben, um das Casting durchzuführen, aber der Kern wäre ohne Generika.
cmaster

1
Ich würde mich dagegen aussprechen, ein Generikum (Vorbenutzung statt Wiederverwendung) als wahrscheinliches YAGNI zu schreiben. Wenn Sie es brauchen, dann refactor.
Neil_UK

Als ich diese Frage schrieb, ging ich näher auf "Schreibe sie, wenn möglich, als generisches Mittel ein, um eine mögliche spätere Wiederverwendung zu vermeiden." Ich wusste, dass das ein bisschen extrem war. Es ist erfrischend, Ihren Standpunkt hier zu sehen. Vielen Dank!
SyntaxRules

0

Der Code sieht seltsam aus. Aber wenn wir es aufrufen, sieht es besser aus, den Typ als generisch anzugeben.

https://stackoverflow.com/questions/10955579/passing-just-a-type-as-a-parameter-in-c-sharp

Persönlich mag ich diese Verzerrungen nicht, die den aufrufenden Code hübsch aussehen lassen. Zum Beispiel das ganze "fließende" Ding und die Erweiterungsmethoden.

Aber Sie müssen zugeben, dass es eine beliebte Anhängerschaft hat. Sogar Microsoft verwendet es zum Beispiel in Unity

container.RegisterType<IInterface,Concrete>()

0

Für mich sieht es so aus, als ob Sie hier auf dem richtigen Weg waren, aber den Job nicht beendet haben.

Haben Sie darüber nachgedacht, den object[]Parameter Func<T,object>[]für den nächsten Schritt auf zu ändern ?

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.