Wie funktionieren Klassen in C#?

3 Antworten

Frage 1

Das hängt damit zusammen, wie die Operatoren ausgewertet werden.
Es wird von links nach rechts immer nur ein Operator ausgewertet und das Ergebnis wird dann für den nächsten Operator verwendet und so weiter.

Beispiel:

var a = new Test(1);
var b = new Test(2);
var c = new Test(3);
var d = new Test(4);
var x = a + b + c + d;

Console.WriteLine(x.Zahl);

class Test
{
    public static Test operator +(Test x, Test y)
    {
        Console.WriteLine($"{x.Zahl} + {y.Zahl}");
        return new Test(x.Zahl + y.Zahl);
    }
    
    public int Zahl { get; }
    
    public Test(int zahl)
    {
        Zahl = zahl;
    }
}

Ausgabe:

1 + 2
3 + 3
6 + 4
10

Frage 2:

Das musst Du den Lehrer fragen.
Ich halte es für sinnvoll, solche Funktionen zu kennen, aber für einen Anfänger ist das dann doch etwas unnötig, besonders da man es in der Praxis so gut wie nie braucht. Häufiger sind da die Operatoren "==" und "!=", das sehe ich tatsächlich immer mal wieder.
Generell halte ich das Beispiel für fragwürdig.

Frage 3:

Ich finde es etwas befremdlich, dass Du diese Frage stellen musst, das lässt an der Qualität des Kurses zweifeln.

Du kannst nicht direkt auf die Methoden zugreifen, Du brauchst dafür eine Instanz der Klasse.

Wiederstand a = new Wiederstand(Box1);
Wiederstand b = new Wiederstand(Box2);
Wiederstand c = a + b;
Wiederstand d = a / b;

Anders wäre das, wenn die Methode statisch ist, dann brauchst Du keine Instanz, sondern kannst sie direkt aufrufen. Aber Du sollst ja mit Operatoren arbeiten und dafür brauchst Du Instanzen.

Frage 4:

Ich hatte ja schon geschrieben, dass Operatoren von links nach rechts ausgewertet werden. Zur Laufzeit wird dann die Variable links vom Operator und die rechts davon verwendet. Der Operator selber ist am Ende nur eine besondere statische Methode, die vom Compiler eingesetzt wird.

Mein Beispiel von oben:

var a = new Test(1);
var b = new Test(2);
var c = new Test(3);
var d = new Test(4);
var x = a + b + c + d;

Das kann man auch so umschreiben:

var x = ((a + b) + c) + d;

Oder so:

var x1 = a + b;
var x2 = x1 + c;
var x3 = x2 + d;

Und der Compiler macht daraus:

var x1 = Test.op_Addition(a, b);
var x2 = Test.op_Addition(x1, c);
var x3 = Test.op_Addition(x2, d);

Die Methode heißt tatsächlich so, sieht man auch daran, dass folgender Code nicht funktioniert:

public static Test operator +(Test x, Test y)
{
    return new Test(x.Zahl + y.Zahl);
}
    
public static Test op_Addition(Test x, Test y)
{
    return new Test(x.Zahl + y.Zahl);
}
error CS0111: Type 'Test' already defines a member called 'op_Addition' with the same parameter types

PS:

Das alles gilt natürlich auch für die anderen Operatoren.

Woher ich das weiß:Berufserfahrung – C#.NET Senior Softwareentwickler

Sonnenblume0468 
Fragesteller
 22.05.2024, 22:34

Vielen Dank, hat sehr viele Unklarheiten aufgeklärt!

1

zu Punkt 3:

Um aus einem Zahlenwert, der von User eingegeben wurde, einen Widerstand zu machen, musst du tatsächlich eine neue Instanz der Klasse aufrufen. Das geht mit einer Anweisung der Art:

Wiederstand w = new Wiederstand (<Zahlenwert hier>);

So wie du den Konstruktur definiert hast, sollte der Zahlenwert in einer Variablen vom Typ double stehen. In deinen Operatordefinitionen machst du das ja auch, und zwar in den Zeilen 34 und 40 (In Datei Wiederand.cs).

Zu Punkt 4:

In der Datei MainWindow.xaml.cs ist der Code unvollständig und es stehen in den Kommentaren einige Fragen. Hier meine Vorschläge zur Vervollständigung deines Codes:

Nachdem du die Zahlenwerte Box1, Box2 auf den Textfeldern ausgelesen hast, kannst du Widerstände erzeugen. Füge nach Zeile 34 ein:

Wiederstand r1 = new Wiederstand(Box1);
Wiederstand r2 = new Wiederstand(Box2);

Jetzt hast du alles, was du brauchst, um mit deinen Operatoren zu rechnen. Den Code von Zeile 41 bis 50 würde ich durch den folgenden Programmtext ersetzen:

Wiederstand gesamtwiderstand;
if (this.RadioButton_Rreihe.IsChecked.Value)
{
   gesamtwiderstand = r1 + r2;
}
else if (this.RadioButton_Parallel.IsChecked.Value)
{
   gesamtwiderstand = r1 / r2;
}  

Du trägst also die erhaltenen Zahlenwerte in neu erzeugten Instanzen der Klasse Wiederstand ein, um anschließend mit den Operatoren, die für Instanzen dieser Klasse definiert sind, rechnen zu können.

Anmerkung: In den Programmfragmenten habe ich den Klassennamen "Wiederstand" übernommen, damit cut & paste funktioniert, ohne dass der Compiler Fehlermeldungen ausgibt. Natürlich ist der Klassenname mißraten, und falls du dein Programm irgendwo herzeigen musst, solltest du "Wiederstand" systematisch durch "Widerstand" ersetzen.

Woher ich das weiß:Berufserfahrung – Softwareentwickler

Sonnenblume0468 
Fragesteller
 22.05.2024, 22:44

Dankeschön, hab jetzt verstanden wozu Operatoren überhaupt gut sind. Und das mit dem Wi"e"derstand, wäre mir so schnell nicht aufgefallen, vielen Dank für die Anmerkung 🙈!

1
BorisG2011  23.05.2024, 21:55

Es lohnt sich, darauf hinzuweisen, dass für Operatoren in C# Vorrangregeln gelten, die auch auf selbst definierte Operatoren angewendet werden. Es gilt, dass der Operator "/" Vorrang hat vor dem Operator "+". Das hat eine Reihe interessanter Konsequenzen für Formeln, in denen mehr als zwei Widerstände und beide Operatoren vorkommen.

Wenn wir drei Widerstände r1, r2, r3 haben und den Ausdruck

gesamtwiderstand = r1 + r2/r3

anschreiben, wird auf der Grundlage der Vorrangregeln zuerst r2/r3 berechnet. Mit dem erhaltenen Ergebnis wird anschließend der Additionsoperator ausgeführt. Der Ausdruck berechnet den Gesamtwidersand für die folgende Schaltung:

                                   +---------+
                            +------|   r2    |----+
             +-------+      |      +---------+    |
     --------|   r1  | -----+                     +-----
             +-------+      |      +---------+    |
                            +------|   r3    |----+
                                   +---------+

Man könnte den Rechenausdruck natürlich auch mit Klammern in der folgenden Form schreiben

 gesamtwiderstand = r1 + (r2/r3)

wegen der Vorrangregeln sind diese Klammern allerdings nicht erforderlich.

In der Formel

   gesamtwiderstand = (r1 + r2)/r3;

werden Klammern verwendet, um von den Vorrangregeln abzuweichen. Es wird nun erst die Reihenschaltung von r1 und r2 bestimmt und der erhaltene Ersatzwiderstand sodann mit r3 parallel geschaltet. Das zugehörige Schaltbild ist:

                 +-------+   +-------+
            +----|  r1   |---|  r2   |---+
            |    +-------+   +-------+   |
 -----------+                            +---------
            |          +-------+         |
            +----------|  r3   |---------+
                       +-------+

Gucken wir uns noch eine kompliziertere Schaltung an:

                          +-------+   +-------+
                     +----|  r2   |---|  r3   |---+
       +-------+     |    +-------+   +-------+   |
 ------|  r1   |-----+                            +-----
       +-------+     |          +-------+         |
                     +----------|  r4   |---------+
                                +-------+

Den Gesamtwiderstand erhalten wir mit den Ausdruck

  gesamtwiderstand = r1 + (r2 + r3)/r4 

Wenn das zu kompliziert erscheint, kann man natürlich auch kleinere Teilaufgaben rechnen und die erhaltenen Zwischenergebnisse speichern. Das geht dann so:

  r23 = r2 +r3;
  r234 = r23/r4;
  gesamtwiderstand = r1 + r234;

So würde man den Gesamtwiderstand der gezeigten Schaltung nach alter Väter Sitte mit Bleistift und Papier ausrechnen.

Völlig gleichwertig ist der folgende Ausdruck:

gesamtwiderstand = (r2 + r3)/r4 + r1;

Auf die Klammern um den Teilausdruck "r2 + r3" kann nicht verzichtet werden, der Audruck

gesamtwiderstand = r2 + r3/r4 + r1;

berechnet nämlich den Widerstand der folgenden Schaltung:

                         +--------+
                    +----|   r3   |----+
       +------+     |    +--------+    |   +------+
  -----|  r2  |-----+                  +---|  r1  |---
       +------+     |    +--------+    |   +------+
                    +----|   r4   |----+
                         +--------+
0

Die Anwendung des Additionsoperators auf mehr als zwei Widerstände ist unproblematisch, weil sowohl die Addition als auch die Reihenschaltung kommutativ und assoziativ sind.

Die Parallelschaltung ist das auch, aber die Division ist nur linksassoziativ. Das genügt aber, denn der Compiler macht aus x / y / z dann immer (x / y) / z, obwohl auch x / (y / z) richtig wäre. Für den Compiler ist das nur eine Division.


tunik123  22.05.2024, 19:44

Nachträge:

Zu 1.

Es geht auch R1 + R2 + R3 + R4, denn der Compiler macht daraus ((R1 + R2) + R3) + R4. Er kann die Klammern auch anders setzen (Assoziativität) oder Summanden vertauschen (Kommutativität).

Zu 2.

Operatoren sehen eben hübscher (übersichtlicher) aus.

R1 + R2 + R3 sieht besser aus, als

(R1.Add(R2)).Add(R3)

Zu 3.

Du musst nicht eine neue Klasse erstellen, sondern nur eine neue Instanz der Klasse.

Wiederstand(3) erzeugt einen Widerstand (ohne e) von 3 Ohm. Der muss nicht mal einen Namen haben.

Zu 4.

Die Werte werden schon vom Konstruktior hineingeschrieben.

Wenn es in C++ keinen Standardkonstruktor gibt, dann wird das vom Compiler sogar erzwungen. Über C# weiß ich zu wenig, und das dort bestätigen zu können. Auch weiß ich nicht, ob es in C# Copy-Konstruktoren gibt und ob diese oder Zuweisungsoperatoren den Widerstandswert eines bereits vorhandenen Widerstands ändern können. In C++ muss man als Programmierer das explizit verhindern.

0
Palladin007  22.05.2024, 20:50
@tunik123
Es geht auch R1 + R2 + R3 + R4, denn der Compiler macht daraus ((R1 + R2) + R3) + R4. Er kann die Klammern auch anders setzen (Assoziativität) oder Summanden vertauschen (Kommutativität).

Der Compiler tauscht da nichts, die von dir gesetzten Klammern sind so, wie es der Compiler ausspuckt.

Wenn es in C++ keinen Standardkonstruktor gibt, dann wird das vom Compiler sogar erzwungen. Über C# weiß ich zu wenig, und das dort bestätigen zu können.

WENN die Klasse gar keinen Konstruktur hat, nur dann wird ein Standardkonstruktor erstellt.
Wenn die Klasse Konstruktoren mit beliebigen Parametern hat, aber keinen Standardkonstruktor, dann wird kein Standardkonstruktor generiert.

Auch weiß ich nicht, ob es in C# Copy-Konstruktoren gibt

Du kannst einen Konstruktor mit einem Parameter vom Typ derselben Klasse erstellen und im Konstruktor dann alle Werte übernehmen, das wäre ein Copy-Konstruktor.

Ein Feature so in der Art gibt es (leider) nur bei records, da gibt es die "with" Syntax, die eine neue Instanz erstellt, aber die Werte im "with"-Block ändert.

ob diese oder Zuweisungsoperatoren den Widerstandswert eines bereits vorhandenen Widerstands ändern können

Ich bin nicht 100% sicher was Du meinst, aber ich würde behaupten: Nein.
Im Operator können zwar die Eigenschaften der übergebenen Parameter geändert werden, aber auch nur, wenn deren Typen das zulassen.

Es reicht also, die Eigenschaft der Klasse readonly zu machen (sprich: kein set) und ist abgesichert.

2
tunik123  22.05.2024, 22:12
@Palladin007

Danke. Da habe ich mal wieder was gelernt. 😀

Ich habe zumindest mit meinen C++ - Kenntnissen versucht, eine C# - Frage zu beantworten.

0