Überprüfen automatisch implementierter Eigenschaften

Abgeschlossen

Die Standardsyntax zum Definieren einer Eigenschaft in C# enthält einen get Accessor und einen set Accessor. Wenn jedoch keine andere Logik in den Accessoren erforderlich ist, können Sie automatisch implementierte Eigenschaften verwenden.

Automatisch implementierte Eigenschaften

Automatisch implementierte Eigenschaften machen die Eigenschaftsdeklaration präziser, wenn in den Eigenschaftenaccessoren keine andere Logik erforderlich ist. Außerdem können Clientcode Objekte erstellen. Wenn Sie eine Eigenschaft wie im folgenden Beispiel gezeigt deklarieren, erstellt der Compiler ein privates, anonymes Sicherungsfeld, auf das nur über die get und set Accessoren der Eigenschaft zugegriffen werden kann. init Accessoren können auch als automatisch implementierte Eigenschaften deklariert werden.

Das folgende Beispiel zeigt eine einfache Klasse mit automatisch implementierten Eigenschaften:


// This class is mutable. Its data can be modified from
// outside the class.
public class Customer
{
    // Auto-implemented properties for trivial get and set
    public double TotalPurchases { get; set; }
    public string Name { get; set; }
    public int CustomerId { get; set; }

    // Constructor
    public Customer(double purchases, string name, int id)
    {
        TotalPurchases = purchases;
        Name = name;
        CustomerId = id;
    }

    // Methods
    public string GetContactInfo() { return "ContactInfo"; }
    public string GetTransactionHistory() { return "History"; }

    // .. Other methods, events, etc.
}

class Program
{
    static void Main()
    {
        // Initialize a new object.
        Customer cust1 = new Customer(4987.63, "Northwind", 90108);

        // Modify a property.
        cust1.TotalPurchases += 499.99;
    }
}

Automatisch implementierte und feldgesicherte Eigenschaften deklarieren ein sicherungsendes Feld einer privaten Instanz. Sie können automatisch implementierte Eigenschaften ähnlich wie Felder initialisieren:


public string FirstName { get; set; } = "FirstName";

Die im vorherigen Beispiel gezeigte Customer Klasse ist änderbar. Clientcode kann die Werte in Objekten nach der Erstellung ändern. In komplexen Klassen, die erhebliche Verhaltensweisen (Methoden) und Daten enthalten, ist es häufig erforderlich, öffentliche Eigenschaften zu besitzen. Bei kleinen Klassen oder Strukturen, die nur eine Gruppe von Werten (Daten) kapseln und nur wenig oder gar kein Verhalten aufweisen, sollten Sie jedoch eine der folgenden Optionen verwenden, um die Objekte unveränderlich zu gestalten:

  • Deklarieren Sie nur einen Get-Accessor (unveränderlich überall außer dem Konstruktor).
  • Deklarieren Sie einen Get-Accessor und einen Init-Accessor (unveränderlich überall außer beim Objektaufbau).
  • Deklarieren Sie den Set-Accessor als privat (unveränderlich für Consumer).

Möglicherweise müssen Sie einer automatisch implementierten Eigenschaft eine Überprüfung hinzufügen. C# 13 fügt feldgesicherte Eigenschaften als Vorschaufeature hinzu. Sie verwenden das Feldschlüsselwort, um auf das compilersynthetisierte Sicherungsfeld einer automatisch implementierten Eigenschaft zuzugreifen. Sie können beispielsweise sicherstellen, dass die FirstName-Eigenschaft im vorherigen Beispiel nicht auf NULL oder die leere Zeichenfolge festgelegt werden kann:


public string FirstName 
{ 
    get; 
    set 
    { 
        field = (string.IsNullOrWhiteSpace(value) is false
            ? value
            : throw new ArgumentException(nameof(value), "First name can't be whitespace or null"));
    }
} = "FirstName";

Mit diesem Feature können Sie Accessoren Logik hinzufügen, ohne dass Sie das Sicherungsfeld explizit deklarieren müssen. Sie verwenden das Feldschlüsselwort, um auf das vom Compiler generierte Sicherungsfeld zuzugreifen.

Wichtig

Das Schlüsselwort field ist ein Vorschaufeature in C# 13. Sie müssen .NET 9 verwenden und das <LangVersion> Element auf preview in Der Projektdatei festlegen, um das kontextbezogene Schlüsselwort field zu verwenden.

Achten Sie darauf, die field Schlüsselwortfunktion in einer Klasse zu verwenden, die ein Feld mit dem Namen fieldhat. Das neue field Schlüsselwort schattiert ein Feld mit dem Namen field im Bereich eines Eigenschaftenaccessors. Sie können entweder den Namen der field Variablen ändern oder das @-Token verwenden, um auf den field Bezeichner als @fieldzu verweisen.

Eigenschaften mit Sicherungsfeldern

Sie können das Konzept einer berechneten Eigenschaft mit einem privaten Feld kombinieren und eine zwischengespeicherte ausgewertete Eigenschaft erstellen. Aktualisieren Sie beispielsweise die FullName-Eigenschaft so, dass die Zeichenfolgenformatierung für den ersten Zugriff erfolgt:


public class Person
{
    public Person() { }

    [SetsRequiredMembers]
    public Person(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }

    public required string FirstName { get; init; }
    public required string LastName { get; init; }

    private string? _fullName;
    public string FullName
    {
        get
        {
            if (_fullName is null)
                _fullName = $"{FirstName} {LastName}";
            return _fullName;
        }
    }
}

Diese Implementierung funktioniert, da die eigenschaften FirstName und LastName schreibgeschützt sind. Personen können ihren Namen ändern. Wenn Sie die eigenschaften FirstName und LastName so aktualisieren, dass set Accessoren zulassen, müssen Sie alle zwischengespeicherten Werte für fullNameungültig machen. Sie müssen die set-Accessoren der Eigenschaften FirstName und LastName aktualisieren, damit das fullName-Feld erneut berechnet wird:


public class Person
{
    private string? _firstName;
    public string? FirstName
    {
        get => _firstName;
        set
        {
            _firstName = value;
            _fullName = null;
        }
    }

    private string? _lastName;
    public string? LastName
    {
        get => _lastName;
        set
        {
            _lastName = value;
            _fullName = null;
        }
    }

    private string? _fullName;
    public string FullName
    {
        get
        {
            if (_fullName is null)
                _fullName = $"{FirstName} {LastName}";
            return _fullName;
        }
    }
}

Diese endgültige Version wertet die FullName Eigenschaft nur bei Bedarf aus. Die zuvor berechnete Version wird verwendet, sofern gültig. Andernfalls aktualisiert die Berechnung den zwischengespeicherten Wert. Entwickler, die diese Klasse verwenden, müssen die Details der Implementierung nicht kennen. Keine dieser internen Änderungen wirkt sich auf die Verwendung des Person-Objekts aus.

Ab C# 13 können Sie partial properties in Teilklassen erstellen. Die Implementierungsdeklaration für eine partial-Eigenschaft kann keine automatisch implementierte Eigenschaft sein. Eine automatisch implementierte Eigenschaft verwendet dieselbe Syntax wie eine deklarierende partielle Eigenschaftsdeklaration.

Implementieren einer einfachen Klasse mit automatisch implementierten Eigenschaften

Es können Situationen auftreten, in denen Sie eine unveränderliche einfache Klasse erstellen müssen, die nur zum Kapseln einer Reihe automatisch implementierter Eigenschaften dient. Verwenden Sie diese Art von Konstrukt anstelle einer struct, wenn Sie die Referenztypsemantik verwenden müssen.

Sie können eine unveränderliche Eigenschaft auf folgende Weise erstellen:

  • Deklarieren Sie nur den get Accessor, wodurch die Eigenschaft überall unveränderlich wird, außer im Konstruktor des Typs.
  • Deklarieren Sie einen init Accessor anstelle eines set Accessors, wodurch die Eigenschaft nur im Konstruktor oder mithilfe eines Objektinitialisierers festgelegt werden kann.
  • Deklarieren Sie den set Accessor als private. Die Eigenschaft ist innerhalb des Typs festgelegt, kann aber für Verbraucher unveränderlich sein.

Sie können der Eigenschaftsdeklaration den required Modifizierer hinzufügen, um zu erzwingen, dass die Eigenschaft als Teil der Initialisierung eines neuen Objekts festgelegt wird.

Das folgende Beispiel zeigt, wie sich eine Eigenschaft mit nur get accessor unterscheidet als eine eigenschaft mit get und private set.


class Contact
{
    public string Name { get; }
    public string Address { get; private set; }

    public Contact(string contactName, string contactAddress)
    {
        // Both properties are accessible in the constructor.
        Name = contactName;
        Address = contactAddress;
    }

    // Name isn't assignable here. This will generate a compile error.
    //public void ChangeName(string newName) => Name = newName;

    // Address is assignable here.
    public void ChangeAddress(string newAddress) => Address = newAddress;
}

Das folgende Beispiel zeigt zwei Möglichkeiten zum Implementieren einer unveränderlichen Klasse mithilfe automatisch implementierter Eigenschaften. Jede Methode deklariert eine der Eigenschaften mit einem privaten set und einer der Eigenschaften nur mit einem get. Die erste Klasse verwendet einen Konstruktor nur zum Initialisieren der Eigenschaften, und die zweite Klasse verwendet eine statische Factorymethode, die einen Konstruktor aufruft.


// This class is immutable. After an object is created,
// it can't be modified from outside the class. It uses a
// constructor to initialize its properties.
class Contact
{
    // Read-only property.
    public string Name { get; }

    // Read-write property with a private set accessor.
    public string Address { get; private set; }

    // Public constructor.
    public Contact(string contactName, string contactAddress)
    {
        Name = contactName;
        Address = contactAddress;
    }
}

// This class is immutable. After an object is created,
// it can't be modified from outside the class. It uses a
// static method and private constructor to initialize its properties.
public class Contact2
{
    // Read-write property with a private set accessor.
    public string Name { get; private set; }

    // Read-only property.
    public string Address { get; }

    // Private constructor.
    private Contact2(string contactName, string contactAddress)
    {
        Name = contactName;
        Address = contactAddress;
    }

    // Public factory method.
    public static Contact2 CreateContact(string name, string address)
    {
        return new Contact2(name, address);
    }
}

public class Program
{
    static void Main()
    {
        // Some simple data sources.
        string[] names = ["Person One","Person Two", "Person Three",
                            "Person Four", "Person Five"];
        string[] addresses = ["123 Main St.", "345 Cypress Ave.", "678 1st Ave",
                                "12 108th St.", "89 E. 42nd St."];

        // Simple query to demonstrate object creation in select clause.
        // Create Contact objects by using a constructor.
        var query1 = from i in Enumerable.Range(0, 5)
                    select new Contact(names[i], addresses[i]);

        // List elements can't be modified by client code.
        var list = query1.ToList();
        foreach (var contact in list)
        {
            Console.WriteLine("{0}, {1}", contact.Name, contact.Address);
        }

        // Create Contact2 objects by using a static factory method.
        var query2 = from i in Enumerable.Range(0, 5)
                        select Contact2.CreateContact(names[i], addresses[i]);

        // Console output is identical to query1.
        var list2 = query2.ToList();

        // List elements can't be modified by client code.
        // CS0272:
        // list2[0].Name = "Person Six";
    }
}

/* Output:
    Person One, 123 Main St.
    Person Two, 345 Cypress Ave.
    Person Three, 678 1st Ave
    Person Four, 12 108th St.
    Person Five, 89 E. 42nd St.
*/

Der Compiler erstellt Sicherungsfelder für jede automatisch implementierte Eigenschaft. Auf die Felder kann nicht direkt über Quellcode zugegriffen werden.