Erkunden der objektorientierten Programmierung mit Klassen und Objekten

In diesem Tutorial erstellen Sie eine Konsolenanwendung und die grundlegenden objektorientierten Funktionen, die Teil der C#-Sprache sind.

Voraussetzungen

Erstellen Ihrer Anwendung

Erstellen Sie in einem Terminalfenster ein Verzeichnis namens Classes. Dort werden Sie Ihre Anwendung erstellen. Wechseln Sie in dieses Verzeichnis, und geben Sie dotnet new console im Konsolenfenster ein. Dieser Befehl erstellt die Anwendung. Öffnen Sie Program.cs. Es sollte wie folgt aussehen:

// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

In diesem Tutorial erstellen Sie neue Typen, die ein Bankkonto darstellen. Entwickler definieren jede Klasse in der Regel in einer anderen Textdatei. Dies erleichtert die Verwaltung bei zunehmender Programmgröße. Erstellen Sie eine neue Datei mit dem Namen BankAccount.cs im Verzeichnis classes.

Diese Datei enthält die Definition eines Bankkontos. In der objektorientierten Programmierung wird der Code organisiert, indem Typen in Form von Klassen erstellt werden. Diese Klassen enthalten den Code, der eine bestimmte Entität darstellt. Die BankAccount-Klasse stellt ein Bankkonto dar. Der Code implementiert bestimmte Vorgänge mittels Methoden und Eigenschaften. In diesem Tutorial unterstützt das Bankkonto dieses Verhalten:

  1. Es enthält eine 10-stellige Zahl, die das Bankkonto eindeutig identifiziert.
  2. Es enthält eine Zeichenfolge, die den bzw. die Namen des Besitzers speichert.
  3. Der Kontostand kann abgerufen werden.
  4. Es akzeptiert Einzahlungen.
  5. Es akzeptiert Abbuchungen.
  6. Der anfängliche Kontostand muss positiv sein.
  7. Abbuchungen dürfen nicht in einem negativen Kontostand resultieren.

Definieren des Bankkontotyps

Sie können beginnen, indem Sie die Grundlagen einer Klasse erstellen, die dieses Verhalten definiert. Erstellen Sie eine neue Datei mit dem Befehl File:New. Nennen Sie sie BankAccount.cs. Fügen Sie der Datei BankAccount.cs folgenden Code hinzu:

namespace Classes;

public class BankAccount
{
    public string Number { get; }
    public string Owner { get; set; }
    public decimal Balance { get; }

    public void MakeDeposit(decimal amount, DateTime date, string note)
    {
    }

    public void MakeWithdrawal(decimal amount, DateTime date, string note)
    {
    }
}

Bevor wir fortfahren, lassen Sie uns anschauen, was Sie erstellt haben. Die namespace-Deklaration ist eine Möglichkeit, Ihren Code logisch zu organisieren. Da dieses Tutorial relativ klein ist, platzieren Sie den gesamten Code in einen einzigen Namespace.

public class BankAccount definiert die Klasse oder den Typ, die/den Sie erstellen. Sämtliche Inhalte zwischen { und }, die der Klassendeklaration folgen, definieren den Zustand und das Verhalten der Klasse. Die BankAccount-Klasse verfügt über fünf Member. Die ersten drei sind Eigenschaften. Eigenschaften sind Datenelemente und können Code aufweisen, der eine Überprüfung oder andere Regeln erzwingt. Die letzten beiden sind Methoden. Methoden sind Codeblöcke, die eine einzelne Funktion ausführen. Das Lesen der Namen der einzelnen Member sollte Ihnen oder anderen Entwicklern genug Informationen liefern, um zu verstehen, welche Aufgabe die Klasse hat.

Eröffnen eines neuen Kontos

Die erste zu implementierende Funktion ist das Eröffnen eines Bankkontos. Wenn ein Kunde ein Konto eröffnet, muss er einen anfänglichen Kontostand bereitstellen und Informationen über den/die Besitzer dieses Kontos angeben.

Das Erstellen eines neuen Objekts des Typs BankAccount bedeutet, einen Konstruktor zu definieren, der diese Werte zuweist. Ein Konstruktor ist ein Member, der den gleichen Namen wie die Klasse aufweist. Er wird verwendet, um Objekte des betreffenden Klassentyps zu initialisieren. Fügen Sie dem BankAccount-Typ den folgenden Konstruktor hinzu,. Platzieren Sie folgenden Code oberhalb der Deklaration von MakeDeposit:

public BankAccount(string name, decimal initialBalance)
{
    this.Owner = name;
    this.Balance = initialBalance;
}

Der obige Code identifiziert die Eigenschaften des zu erstellenden Objekts, indem er den this-Qualifizierer einschließt. Dieser Qualifizierer ist in der Regel optional und wird weggelassen. Sie könnten auch Folgendes geschrieben haben:

public BankAccount(string name, decimal initialBalance)
{
    Owner = name;
    Balance = initialBalance;
}

Der this-Qualifizierer ist nur erforderlich, wenn eine lokale Variable oder ein lokaler Parameter denselben Namen wie das jeweilige Feld oder die jeweilige Eigenschaft hat. Der this-Qualifizierer wird im restlichen Teil dieses Artikels weggelassen, sofern er nicht erforderlich ist.

Konstruktoren werden bei der Erstellung eines Objekts mit new aufgerufen. Ersetzen Sie die Zeile Console.WriteLine("Hello World!"); in Program.cs durch den folgende Code (ersetzen Sie <name> durch Ihren Namen):

using Classes;

var account = new BankAccount("<name>", 1000);
Console.WriteLine($"Account {account.Number} was created for {account.Owner} with {account.Balance} initial balance.");

Wir führen nun aus, was wir bisher erstellt haben. Wenn Sie Visual Studio verwenden, klicken Sie im Menü Debuggen auf Ohne Debuggen starten. Wenn Sie eine Befehlszeile verwenden, geben Sie dotnet run in dem Verzeichnis ein, wo Sie das Projekt erstellt haben.

Haben Sie bemerkt, dass die Kontonummer leer ist? Es ist höchste Zeit, dies zu ändern. Die Kontonummer sollten zugewiesen werden, wenn das Objekt erstellt wird. Jedoch sollte nicht der Aufrufende für das Erstellen verantwortlich sein. Der BankAccount-Klassencode sollte wissen, wie neue Kontonummern zugewiesen werden. Eine einfache Möglichkeit dafür ist, mit einer 10-stelligen Zahl zu beginnen. Lassen Sie sie bei jeder Erstellung eines neuen Kontos erhöhen. Speichern Sie schließlich die aktuelle Kontonummer, wenn ein Objekt erstellt wird.

Fügen Sie der BankAccount-Klasse eine Memberdeklaration hinzu. Platzieren Sie die folgende Codezeile nach der öffnenden geschweiften Klammer { am Anfang der BankAccount-Klasse:

private static int s_accountNumberSeed = 1234567890;

accountNumberSeed ist ein Datenelement. Es ist private, d.h. der Zugriff darauf ist nur über Code in der BankAccount-Klasse möglich. Dies ist eine Möglichkeit, die öffentlichen Verantwortlichkeiten (z. B. Besitz einer Kontonummer) von der privaten Implementierung (wie Kontonummern generiert werden) zu trennen. Es ist auch als static definiert, wird also von allen BankAccount-Objekten gemeinsam genutzt. Der Wert einer nicht statischen Variable ist für jede Instanz des BankAccount-Objekts eindeutig. accountNumberSeed ist ein private static-Feld und hat daher nach dem C#-Namenskonventionen das Präfix „s_“. s bezeichnet static und _ bezeichnet das private-Feld. Fügen Sie dem Konstruktor die folgenden zwei Zeilen hinzu, um die Kontonummer zuzuweisen. Platzieren Sie sie nach der Zeile, die this.Balance = initialBalance enthält:

Number = s_accountNumberSeed.ToString();
s_accountNumberSeed++;

Geben Sie dotnet run ein, um die Ergebnisse anzuzeigen.

Erstellen von Einzahlungen und Abbuchungen

Um ordnungsgemäß zu funktionieren, muss Ihre Bankkontoklasse Einzahlungen und Abbuchungen akzeptieren. Einzahlungen und Abbuchungen implementieren Sie, indem Sie eine Erfassung jeder Transaktion für das Konto erstellen. Das Nachverfolgen jeder Transaktion ist vorteilhafter, als bei jeder Transaktion lediglich den Kontostand zu aktualisieren. Der Verlauf kann zum Überwachen aller Transaktionen und Verwalten täglicher Kontostände verwendet werden. Durch bedarfsabhängige Berechnung des Kontostand aus dem Verlauf aller Transaktionen werden etwaige Korrekturen von Fehlern in einer einzelnen Transaktion bei der nächsten Berechnung im Kontostand ordnungsgemäß berücksichtigt.

Zunächst erstellen wir einen neuen Typ, um eine Transaktion darzustellen. Die Transaktion ist ein einfacher Typ, der keine Verantwortlichkeiten hat. Er benötigt einige Eigenschaften. Erstellen Sie eine neue Datei mit dem Namen Transaction.cs. Fügen Sie ihr folgenden Code hinzu:

namespace Classes;

public class Transaction
{
    public decimal Amount { get; }
    public DateTime Date { get; }
    public string Notes { get; }

    public Transaction(decimal amount, DateTime date, string note)
    {
        Amount = amount;
        Date = date;
        Notes = note;
    }
}

Nun fügen wir eine List<T> von Transaction-Objekten der BankAccount-Klasse hinzu. Fügen Sie die folgende Deklaration nach dem Konstruktor in ihrer BankAccount.cs-Datei hinzu:

private List<Transaction> _allTransactions = new List<Transaction>();

Nun werden Sie Balance korrekt berechnen. Der aktuelle Saldo kann durch Summieren der Werte aller Transaktionen ermittelt werden. Mit dem aktuellen Code können Sie nur den anfänglichen Saldo des Kontos abrufen. Sie müssen daher die Eigenschaft Balance aktualisieren. Ersetzen Sie die Zeile public decimal Balance { get; } in BankAccount.cs durch den folgenden Code:

public decimal Balance
{
    get
    {
        decimal balance = 0;
        foreach (var item in _allTransactions)
        {
            balance += item.Amount;
        }

        return balance;
    }
}

Dieses Beispiel veranschaulicht einen wichtigen Aspekt von Eigenschaften. Jetzt berechnen Sie den Kontostand, wenn ein anderer Programmierer den Wert anfordert. Die Berechnung zählt alle Transaktionen auf und gibt die Summe als aktuellen Kontostand zurück.

Implementieren Sie nun die MakeDeposit- und MakeWithdrawal-Methode. Diese Methoden erzwingen die letzten beiden Regeln: Der anfängliche Kontostand muss positiv sein, und eine Abbuchung darf nicht in einem negativen Kontostand resultieren.

Diese Regeln führen das Konzept der Ausnahmen ein. Standardmäßig wird eine Ausnahme ausgelöst, um anzuzeigen, dass eine Methode ihre Aufgabe nicht erfolgreich ausführen kann. Der Typ der Ausnahme und die zugeordnete Nachricht beschreiben den Fehler. Hier löst die MakeDeposit-Methode eine Ausnahme aus, wenn der Einzahlungsbetrag nicht größer als 0 (null) ist. Die MakeWithdrawal-Methode löst eine Ausnahme aus, wenn der Abbuchungsbetrag nicht größer als 0 ist, oder wenn aus der Abbuchung ein negativer Kontostand resultiert. Fügen Sie nach der Deklaration der _allTransactions-Liste den folgenden Code hinzu:

public void MakeDeposit(decimal amount, DateTime date, string note)
{
    if (amount <= 0)
    {
        throw new ArgumentOutOfRangeException(nameof(amount), "Amount of deposit must be positive");
    }
    var deposit = new Transaction(amount, date, note);
    _allTransactions.Add(deposit);
}

public void MakeWithdrawal(decimal amount, DateTime date, string note)
{
    if (amount <= 0)
    {
        throw new ArgumentOutOfRangeException(nameof(amount), "Amount of withdrawal must be positive");
    }
    if (Balance - amount < 0)
    {
        throw new InvalidOperationException("Not sufficient funds for this withdrawal");
    }
    var withdrawal = new Transaction(-amount, date, note);
    _allTransactions.Add(withdrawal);
}

Die throw-Anweisunglöst eine Ausnahme aus. Die Ausführung des aktuellen Block wird beendet, und die Steuerung wird an den ersten übereinstimmenden catch-Block übertragen, der in der Aufrufliste gefunden wurde. Sie fügen einen catch-Block hinzu, um diesen Code etwas später zu testen.

Der Konstruktor sollte so geändert werden, dass er eine anfängliche Transaktion hinzufügt, anstatt den Kontostand direkt zu aktualisieren. Da Sie die MakeDeposit-Methode bereits geschrieben haben, rufen Sie sie aus dem Konstruktor auf. Der fertige Konstruktor sollte wie folgt aussehen:

public BankAccount(string name, decimal initialBalance)
{
    Number = s_accountNumberSeed.ToString();
    s_accountNumberSeed++;

    Owner = name;
    MakeDeposit(initialBalance, DateTime.Now, "Initial balance");
}

DateTime.Now ist eine Eigenschaft, die das aktuelle Datum und die Uhrzeit zurückgibt. Testen Sie diesen Code, indem Sie in Ihrer Main-Methode nach dem Code, der ein neues BankAccount erstellt, einige Einzahlungen und Abbuchungen hinzugefügt:

account.MakeWithdrawal(500, DateTime.Now, "Rent payment");
Console.WriteLine(account.Balance);
account.MakeDeposit(100, DateTime.Now, "Friend paid me back");
Console.WriteLine(account.Balance);

Testen Sie anschließend, ob Sie Fehlerbedingungen abfangen, indem Sie versuchen, ein Konto mit einem negativen Kontostand zu erstellen. Fügen Sie den folgenden Code nach dem vorhergehenden Code hinzu:

// Test that the initial balances must be positive.
BankAccount invalidAccount;
try
{
    invalidAccount = new BankAccount("invalid", -55);
}
catch (ArgumentOutOfRangeException e)
{
    Console.WriteLine("Exception caught creating account with negative balance");
    Console.WriteLine(e.ToString());
    return;
}

Sie verwenden die try-catch-Anweisung, um einen Codeblock zu markieren, der Ausnahmen auslösen kann, und um die erwarteten Fehler abzufangen. Mit dem gleichen Verfahren können Sie den Code testen, der bei einem negativen Kontostand eine Ausnahme auslöst. Fügen Sie den folgenden Code vor der Deklaration von invalidAccount in Ihrer Main-Methode hinzu:

// Test for a negative balance.
try
{
    account.MakeWithdrawal(750, DateTime.Now, "Attempt to overdraw");
}
catch (InvalidOperationException e)
{
    Console.WriteLine("Exception caught trying to overdraw");
    Console.WriteLine(e.ToString());
}

Speichern Sie die Datei, und geben Sie dotnet run zum Testen ein.

Herausforderung – Protokollieren aller Transaktionen

Um dieses Tutorial abzuschließen, können Sie die GetAccountHistory-Methode schreiben, die eine Zeichenfolge (string) für den Transaktionsverlauf erstellt. Fügen Sie diese Methode dem BankAccount-Typ hinzu:

public string GetAccountHistory()
{
    var report = new System.Text.StringBuilder();

    decimal balance = 0;
    report.AppendLine("Date\t\tAmount\tBalance\tNote");
    foreach (var item in _allTransactions)
    {
        balance += item.Amount;
        report.AppendLine($"{item.Date.ToShortDateString()}\t{item.Amount}\t{balance}\t{item.Notes}");
    }

    return report.ToString();
}

Der Verlauf verwendet die StringBuilder-Klasse zum Formatieren einer Zeichenfolge, die eine Zeile für jede Transaktion enthält. Sie haben den Zeichenfolgen-Formatierungscode in diesen Tutorials bereits gesehen. \t ist ein neues Zeichen. Damit wird ein Tabulator zum Formatieren der Ausgabe hinzugefügt.

Fügen Sie diese Zeile Program.cs zum Testen hinzu:

Console.WriteLine(account.GetAccountHistory());

Führen Sie das Programm aus, um die Ergebnisse zu sehen.

Nächste Schritte

Wenn Sie nicht weiterkommen, sehen Sie sich die Quelle für dieses Tutorial in unserem GitHub-Repository an.

Sie können mit dem Tutorial zur objektorientierten Programmierung fortfahren.

Weitere Informationen zu diesen Begriffen finden Sie in diesen Artikeln: