Wann sollte ich den impliziten Typkonvertierungsoperator von C # verwenden?

14

In C # können wir den impliziten Konvertierungsoperator wie folgt überladen (Beispiel von MSDN ):

struct Digit
{
    /* ... */
    public static implicit operator byte(Digit d)  // implicit digit to byte conversion operator
    {
        /* ... */
    }
}

So können wir einen Typ haben, eine Art benutzerdefinierten Wert , auf magische Weise selbst zu einem anderen (nicht verwandten) Typ konvertieren, das Publikum fassungslos verlassen (bis sie in den hinter den Kulissen und sehen Sie die implizite Konvertierung Operator Peer, das ist).

Ich lasse nicht gerne jemanden, der meinen Code verwirrt liest. Ich glaube nicht, dass es viele Leute tun.

Die Frage ist, welche Anwendungsfälle der implizite Typkonvertierungsoperator bietet, der das Verständnis meines Codes nicht wesentlich erschwert.

Mints97
quelle
1
Beeindruckend. Ich wusste eigentlich nicht, dass es das gibt. Nicht, dass es unbedingt gut zu gebrauchen wäre. Ich weiß, dass die Leute über diese Art von Funktionalität, die sich in C ++ verbirgt, sehr verärgert waren.
Katana314
@ Katana314: Das war es nicht, worüber sich die Leute ärgerten, sondern dass jemand eine Überladung (sei es Operator, Konvertierungsfunktion, Konstruktor, freie Funktion oder Member-Funktion) mit überraschendem Verhalten hinzufügte, vorzugsweise auf subtile Weise überraschend.
Deduplikator
Ich empfehle, dass Sie sich in C ++ über das "Überladen von Operatoren" informieren, insbesondere über die "Casting" -Operatoren. Ich vermute, dass viele der gleichen Argumente für / gegen dasselbe sind, mit der Ausnahme, dass die Debatte dreimal fortgesetzt wurde, solange es in C # noch viel mehr zu lesen gab.

Antworten:

18

Ich würde nur implizite Konvertierungen zwischen Typen empfehlen, die ungefähr dieselben Werte auf unterschiedliche Weise darstellen. Zum Beispiel:

  • Verschiedene Farbtypen wie RGB, HSL, HSVund CMYK.
  • Unterschiedliche Einheiten für dieselbe physikalische Größe ( Metervs Inch).
  • Unterschiedliche Koordinatensysteme (polar vs kartesisch).

Es gibt jedoch einige strenge Richtlinien, die angeben, wann es nicht angemessen ist, eine implizite Konvertierung zu definieren:

  • Wenn die Konvertierung einen signifikanten Verlust an Genauigkeit oder Reichweite verursacht, sollte dies nicht implizit sein (z. B. von float64 nach float32 oder von long nach int).
  • Wenn die Konvertierung eine ( InvalidCast) - Ausnahme auslösen kann, sollte sie nicht implizit sein.
  • Wenn die Konvertierung bei jeder Ausführung eine Heap-Zuweisung verursacht, sollte dies nicht implizit sein.
  • Wenn die Konvertierung keine O(1)Operation ist, sollte sie nicht implizit sein.
  • Wenn der Quelltyp oder der Zieltyp veränderbar ist, sollte die Konvertierung nicht implizit sein.
  • Wenn die Konvertierung von einer Art Kontext abhängt (Datenbank, Kultureinstellungen, Konfiguration, Dateisystem usw.), sollte dies nicht implizit sein (in diesem Fall würde ich auch von einem expliziten Konvertierungsoperator abraten).

Angenommen, Ihr Conversion-Operator f: T1 -> T2verstößt nicht gegen eine der oben genannten Regeln, dann weist das folgende Verhalten nachdrücklich darauf hin, dass die Conversion implizit sein kann:

  • Wenn a == bdann f(a) == f(b).
  • Wenn a != bdann f(a) != f(b).
  • Wenn a.ToString() == b.ToString()dann f(a).ToString() == f(b).ToString().
  • Usw. für andere Operationen , die auf beide definiert sind , T1und T2.
Elian Ebbing
quelle
Alle Ihre Beispiele sind wahrscheinlich verlustreich. Ob sie trotzdem genau genug sind, ...
Deduplizierer
Ja, das habe ich gemerkt :-). Ich könnte mir keinen besseren Begriff für "verlustbehaftet" vorstellen. Was ich unter "verlustbehaftet" verstehe, sind Conversions, bei denen der Bereich oder die Genauigkeit erheblich verringert sind. ZB von float64 nach float32 oder von long nach int.
Elian Ebbing
Ich denke a! = B => f (a)! = F (b), sollte wahrscheinlich nicht zutreffen. Es gibt viele Funktionen, die denselben Wert für verschiedene Eingaben zurückgeben können, z. B. floor () und ceil () auf der mathematischen Seite
cdkMoose
@cdkMoose Sie haben natürlich Recht, und deshalb sehe ich diese Eigenschaften eher als "Bonuspunkte", nicht als Regeln. Die zweite Eigenschaft bedeutet einfach, dass die Konvertierungsfunktion injektiv ist. Dies ist häufig der Fall, wenn Sie in einen Typ konvertieren, der einen streng größeren Bereich aufweist, z. B. von int32 nach int64.
Elian Ebbing
@cdkMoose Andererseits besagt die erste Eigenschaft nur, dass zwei Werte innerhalb derselben Äquivalenzklasse von T1(impliziert durch die ==Beziehung von T1) immer zwei Werten innerhalb derselben Äquivalenzklasse von zugeordnet sind T2. Nun, da ich darüber nachdenke, denke ich, dass die erste Eigenschaft für eine implizite Konvertierung tatsächlich erforderlich sein sollte.
Elian Ebbing
6

Die Frage ist, welche Anwendungsfälle der implizite Typkonvertierungsoperator bietet, der das Verständnis meines Codes nicht wesentlich erschwert.

Wenn die Typen nicht unabhängig sind (für Programmierer). Es gibt (seltene) Szenarien, in denen Sie zwei nicht verwandte Typen haben (was den Code betrifft), die tatsächlich verwandt sind (was die Domäne oder vernünftige Programmierer betrifft).

Zum Beispiel Code für den String-Abgleich. Ein häufiges Szenario besteht darin, ein Zeichenfolgenliteral abzugleichen. Anstatt anzurufen IsMatch(input, new Literal("some string")), können Sie bei einer impliziten Konvertierung diese Zeremonie - das Rauschen im Code - loswerden und sich auf das Zeichenfolgenliteral konzentrieren.

Die meisten Programmierer werden IsMatch(input, "some string")schnell erkennen und verstehen, was gerade vor sich geht. Dadurch wird Ihr Code auf der Anrufseite klarer. Kurz gesagt, es macht es etwas einfacher zu verstehen , was los ist, in einem geringen Aufwand an , wie das vor sich geht.

Nun könnten Sie argumentieren, dass eine einfache Funktionsüberladung, um dasselbe zu tun, besser wäre. Und es ist. Aber wenn so etwas allgegenwärtig ist, ist eine einzige Konvertierung sauberer (weniger Code, höhere Konsistenz) als ein Haufen Funktionsüberladungen.

Und Sie könnten argumentieren, dass es besser ist, Programmierer zu verpflichten, den Zwischentyp explizit zu erstellen, damit sie sehen, "was wirklich los ist". Das ist weniger einfach. Persönlich denke ich, dass das wörtliche String-Match-Beispiel sehr deutlich macht, was wirklich vor sich geht - der Programmierer muss nicht wissen, wie alles abläuft. Wissen Sie, wie der gesamte Code von den verschiedenen Prozessoren ausgeführt wird, auf denen der Code ausgeführt wird? Es gibt immer eine Linie der Abstraktion , wo Programmierer fürsorglich aufhören , darüber , wie etwas funktioniert. Wenn Sie der Meinung sind, dass die impliziten Konvertierungsschritte wichtig sind, verwenden Sie die implizite Konvertierung nicht. Wenn Sie denken, sie sind nur eine Zeremonie, um den Computer bei Laune zu halten, und der Programmierer wäre besser dran, wenn er dieses Geräusch nicht überall sehen würde,

Telastyn
quelle
Ihr letzter Punkt kann und sollte noch weiter gehen: Es gibt auch eine Linie, über die ein Programmierer sich verdammt noch mal Gedanken darüber gemacht hat, wie etwas gemacht wird, weil das nicht vertraglich ist.
Deduplizierer