Jeder kann coden / Programmieren & TicTacToe / Objektorientierte Programmierung / Objektorientierte Programmierung in C#
💡 Vergleich von Objekten¶
In der prozeduralen Version von TicTacToe in der sandbox haben wir einen Array aus string
s genutzt. In der neuen Fassung mit objektorientierten Ansätzen nutzen wir ein Array aus Objekten von unserem eigenen Typen Spielstein
. Bei diesem Wechsel muss man nun auch berücksichtigen, dass ein Vergleich zweier Strings mittels ==
der Wert beider string
s verglichen wird, bei Objekten hingegen das Objekt selbst.
Ursprungssituation: Vergleich von Strings¶
In deinem ursprünglichen Code hast du direkt mit einem zweidimensionalen Array von Strings gearbeitet:
static string[,] spielfeld = {
{ " ", " ", " " },
{ " ", " ", " " },
{ " ", " ", " " }
};
Hier war der Vergleich einfach:
if (spielfeld[i, 0] == spielfeld[i, 1])
Der Vergleichsoperator ==
prüft bei Strings in C# auf den Inhalt (Wertvergleich), nicht auf die Referenz. Das funktioniert, weil Strings in C# immutable und speziell behandelt werden. Zwei Strings mit gleichem Inhalt gelten als gleich, selbst wenn sie intern unterschiedliche Objekte wären.
Objektorientierte Umsetzung: Vergleich von Objekten¶
Nach der Umstellung auf eine objektorientierte Lösung sieht dein Code etwa so aus:
internal class Spielstein
{
public string Wert { get; set; }
}
Spielstein[,] theGameBoard = new Spielstein[3, 3];
Jetzt vergleichst du in prüfeGewinner
jedoch die Objekte Spielstein
, z. B.:
if (theGameBoard[i, 0] == theGameBoard[i, 1])
Hier prüft ==
nicht mehr den Inhalt (den Wert der Eigenschaft Wert
), sondern die Referenzen der Objekte. Selbst wenn zwei Spielstein
-Objekte den gleichen Wert
(z. B. "X"
) haben, sind sie nicht dasselbe Objekt, und der Vergleich gibt false
zurück.
Beispiel:
Spielstein a = new Spielstein { Wert = "X" };
Spielstein b = new Spielstein { Wert = "X" };
Console.WriteLine(a == b); // False, weil a und b unterschiedliche Objekte sind.
Console.WriteLine(a.Wert == b.Wert); // True, weil die Inhalte ("X") gleich sind.
Lösung: Vergleich der inneren Werte¶
Um den Vergleich korrekt zu machen, musst du sicherstellen, dass nicht die Objekte, sondern deren innere Werte (Wert
) verglichen werden. Deshalb hast du die Methode prüfeWert
eingeführt:
public string prüfeWert(int x, int y)
{
Spielstein temp = theGameBoard[x, y];
return (temp == null) ? " " : temp.Wert;
}
Damit kannst du in prüfeGewinner
die inneren Werte vergleichen:
private bool prüfeGewinner()
{
for (int i = 0; i < 3; i++)
{
if ((prüfeWert(i, 0) == prüfeWert(i, 1))
&& (prüfeWert(i, 1) == prüfeWert(i, 2))
&& (prüfeWert(i, 0) != " ")) return true;
}
for (int i = 0; i < 3; i++)
{
if ((prüfeWert(0, i) == prüfeWert(1, i))
&& (prüfeWert(1, i) == prüfeWert(2, i))
&& (prüfeWert(0, i) != " ")) return true;
}
if ((prüfeWert(0, 0) == prüfeWert(1, 1))
&& (prüfeWert(1, 1) == prüfeWert(2, 2))
&& (prüfeWert(0, 0) != " ")) return true;
if ((prüfeWert(2, 0) == prüfeWert(1, 1))
&& (prüfeWert(1, 1) == prüfeWert(0, 2))
&& (prüfeWert(2, 0) != " ")) return true;
return false;
}
Fazit¶
Der Kern des Problems ist:
- Vorher: Du hast Strings verglichen, die mit
==
inhaltlich verglichen werden. - Jetzt: Du vergleichst Objekte, und
==
prüft hier auf Referenzgleichheit (ob es exakt dasselbe Objekt ist). - Lösung: Du vergleichst explizit die Inhalte (
Wert
) der Objekte, um das gewünschte Verhalten wiederherzustellen.
Die Methode prüfeWert
hilft dir, dies sauber und mit zusätzlicher Validierung zu lösen.
Überladung des ==
Operators¶
Um den ==
-Operator so zu überladen, dass er bei der Klasse Spielstein
den Wert (Wert
) der Objekte vergleicht, kannst du die operator
-Methoden in C# nutzen. Zusätzlich solltest du die Methode Equals
und GetHashCode
überschreiben, da das gute Praxis ist, um Konsistenz zwischen den verschiedenen Vergleichsmechanismen sicherzustellen.
Hier ist ein Beispiel, wie du dies implementieren könntest:
Schritt 1: ==
und !=
überladen¶
Füge in der Klasse Spielstein
die folgenden Operatoren hinzu:
internal class Spielstein
{
public string Wert { get; set; }
// Überladen des == Operators
public static bool operator ==(Spielstein a, Spielstein b)
{
// Beide null
if (ReferenceEquals(a, b)) return true;
// Einer null
if (a is null || b is null) return false;
// Wertvergleich
return a.Wert == b.Wert;
}
// Überladen des != Operators
public static bool operator !=(Spielstein a, Spielstein b)
{
return !(a == b);
}
// Überschreiben von Equals für Konsistenz
public override bool Equals(object obj)
{
if (obj is Spielstein other)
{
return this.Wert == other.Wert;
}
return false;
}
// Überschreiben von GetHashCode für Konsistenz
public override int GetHashCode()
{
return Wert?.GetHashCode() ?? 0;
}
}
Schritt 2: Anpassung des Vergleichs in prüfeGewinner
¶
Jetzt kannst du den Vergleich in prüfeGewinner
wieder so schreiben wie in deinem ursprünglichen Code:
private bool prüfeGewinner()
{
for (int i = 0; i < 3; i++)
{
if ((theGameBoard[i, 0] == theGameBoard[i, 1])
&& (theGameBoard[i, 1] == theGameBoard[i, 2])
&& (theGameBoard[i, 0] != null && theGameBoard[i, 0].Wert != " ")) return true;
}
for (int i = 0; i < 3; i++)
{
if ((theGameBoard[0, i] == theGameBoard[1, i])
&& (theGameBoard[1, i] == theGameBoard[2, i])
&& (theGameBoard[0, i] != null && theGameBoard[0, i].Wert != " ")) return true;
}
if ((theGameBoard[0, 0] == theGameBoard[1, 1])
&& (theGameBoard[1, 1] == theGameBoard[2, 2])
&& (theGameBoard[0, 0] != null && theGameBoard[0, 0].Wert != " ")) return true;
if ((theGameBoard[2, 0] == theGameBoard[1, 1])
&& (theGameBoard[1, 1] == theGameBoard[0, 2])
&& (theGameBoard[2, 0] != null && theGameBoard[2, 0].Wert != " ")) return true;
return false;
}
Erklärung des Codes¶
Operator
==
überladen:- Vergleicht die Referenzen, wenn beide Objekte gleich sind (inkl.
null
). - Vergleicht die Werte (
Wert
), wenn beide Objekte nichtnull
sind.
- Vergleicht die Referenzen, wenn beide Objekte gleich sind (inkl.
Operator
!=
überladen:- Negiert das Ergebnis von
==
.
- Negiert das Ergebnis von
Methode
Equals
überschreiben:- Wird von vielen Frameworks und Datenstrukturen genutzt (z. B. Dictionaries,
Contains
-Methoden).
- Wird von vielen Frameworks und Datenstrukturen genutzt (z. B. Dictionaries,
Methode
GetHashCode
überschreiben:- Stellt sicher, dass Objekte, die als "gleich" betrachtet werden, denselben Hash-Code haben (wichtig für z. B. Dictionaries).
Vorteil¶
Jetzt kannst du den ==
-Operator wie bei den ursprünglichen Strings nutzen, ohne zusätzliche Methoden wie prüfeWert
aufzurufen. Du hast aber weiterhin die Flexibilität der objektorientierten Lösung.