Instanziieren von Objekten mithilfe von Initialisierern und Kopieren von Konstruktoren

Abgeschlossen

Objektinitialisierer und Kopierkonstruktoren sind zwei Methoden zum Instanziieren von Objekten in C#. Mit Objektinitialisierern können Sie allen barrierefreien Feldern oder Eigenschaften eines Objekts zur Erstellungszeit Werte zuweisen, ohne einen Konstruktor aufrufen zu müssen, gefolgt von Zeilen von Zuordnungsanweisungen. Durch Kopieren von Konstruktoren können Sie ein neues Objekt erstellen, indem Sie die Werte eines vorhandenen Objekts kopieren.

Mithilfe von Objektinitialisierern und Kopierkonstruktoren können Sie präziseren und lesbareren Code schreiben.

Objektinitialisierer

Mit Objektinitialisierern können Sie allen barrierefreien Feldern oder Eigenschaften eines Objekts zur Erstellungszeit Werte zuweisen, ohne einen Konstruktor aufrufen zu müssen, gefolgt von Zeilen mit Zuordnungsanweisungen. Mit der Objektinitialisierungssyntax können Sie Argumente für einen Konstruktor angeben oder die Argumente (und Klammernsyntax) weglassen.

Sie können Objektinitialisierer verwenden, um Typobjekte auf deklarative Weise zu initialisieren, ohne explizit einen Konstruktor für den Typ aufzurufen.

Der Compiler verarbeitet Objektinitialisierer, indem zuerst auf den parameterlosen Instanzkonstruktor zugegriffen und dann die Memberinitialisierungen verarbeitet werden. Wenn der parameterlose Konstruktor in der Klasse als privat deklariert wird, schlägt daher objektinitialisierer, die öffentlichen Zugriff erfordern, fehl.

Im folgenden Beispiel wird die Verwendung eines Objektinitialisierungsprogramms veranschaulicht.

Das erste Codebeispiel zeigt die Klassendefinition für eine Klasse mit dem Namen Cat. Die Definition enthält zwei Konstruktoren, von denen einer ein parameterloser Konstruktor ist. Das zweite Codebeispiel zeigt, wie ein Cat-Objekt mithilfe eines Objektinitialisierer instanziieren. Der Objektinitialisierer weist den Eigenschaften Age und Name des Cat Objekts Werte zu.


public class Cat
{
    // Automatically implemented properties.
    public int Age { get; set; }
    public string? Name { get; set; }

    public Cat()
    {
    }

    public Cat(string name)
    {
        this.Name = name;
    }
}


public class Program
{
    public static void Main()
    {
        // Declare and instantiate a new Cat object by using an object initializer.
        Cat cat = new Cat { Age = 10, Name = "Fluffy" };

        // Declare and instantiate a new Cat object by using an object initializer.
        Cat sameCat = new Cat("Fluffy") { Age = 10 };
    }
}

Hier ist ein weiteres Beispiel, das zeigt, wie Sie einen neuen StudentName Typ mithilfe von Objektinitialisierern initialisieren. In diesem Beispiel werden Eigenschaften im StudentName Typ festgelegt:


public class HowToObjectInitializers
{
    public static void Main()
    {
        // Declare a StudentName by using the constructor that has two parameters.
        StudentName student1 = new StudentName("Lisa", "Yeh");

        // Make the same declaration by using an object initializer and sending
        // arguments for the first and last names. The parameterless constructor is
        // invoked in processing this declaration, not the constructor that has
        // two parameters.
        StudentName student2 = new StudentName
        {
            FirstName = "Sandy",
            LastName = "Zoeng"
        };

        // Declare a StudentName by using an object initializer and sending
        // an argument for only the ID property. No corresponding constructor is
        // necessary. Only the parameterless constructor is used to process object
        // initializers.
        StudentName student3 = new StudentName
        {
            ID = 183
        };

        // Declare a StudentName by using an object initializer and sending
        // arguments for all three properties. No corresponding constructor is
        // defined in the class.
        StudentName student4 = new StudentName
        {
            FirstName = "Thomas",
            LastName = "Margand",
            ID = 116
        };

        Console.WriteLine(student1.ToString());
        Console.WriteLine(student2.ToString());
        Console.WriteLine(student3.ToString());
        Console.WriteLine(student4.ToString());
    }
    // Output:
    // Lisa  0
    // Sandy  0
    //   183
    // Thomas  116

    public class StudentName
    {
        // This constructor has no parameters. The parameterless constructor
        // is invoked in the processing of object initializers.
        // You can test this by changing the access modifier from public to
        // private. The declarations in Main that use object initializers will
        // fail.
        public StudentName() { }

        // The following constructor has parameters for two of the three
        // properties.
        public StudentName(string first, string last)
        {
            FirstName = first;
            LastName = last;
        }

        // Properties.
        public string? FirstName { get; set; }
        public string? LastName { get; set; }
        public int ID { get; set; }

        // Override the ToString method of the Object class.
        public override string ToString() => FirstName + "  " + ID;
    }
}

Mit der Objektinitialisierungssyntax können Sie eine Instanz erstellen, und danach wird der Variablen in der Zuordnung das neu erstellte Objekt mit seinen zugewiesenen Eigenschaften zugewiesen.

Zusätzlich zum Zuweisen von Feldern und Eigenschaften können Objektinitialisierer Indexer festlegen. Betrachten Sie diese grundlegende Matrix Klasse:


public class Matrix
{
    private double[,] storage = new double[3, 3];

    public double this[int row, int column]
    {
        // The embedded array will throw out of range exceptions as appropriate.
        get { return storage[row, column]; }
        set { storage[row, column] = value; }
    }
}

Sie können die Identitätsmatrix mit dem folgenden Code initialisieren:


var identity = new Matrix
{
    [0, 0] = 1.0,
    [0, 1] = 0.0,
    [0, 2] = 0.0,

    [1, 0] = 0.0,
    [1, 1] = 1.0,
    [1, 2] = 0.0,

    [2, 0] = 0.0,
    [2, 1] = 0.0,
    [2, 2] = 1.0,
};

Im folgenden Beispiel wird eine BaseballTeam Klasse definiert, die einen Indexer verwendet, um Spieler an verschiedenen Positionen abzurufen und festzulegen. Der Initialisierer kann Spieler basierend auf der Abkürzung für die Position oder die Für jede Position auf einer Baseball-Scorecard verwendete Nummer zuweisen:


public class HowToIndexInitializer
{
    public class BaseballTeam
    {
        private string[] players = new string[9];
        private readonly List<string> positionAbbreviations = new List<string>
        {
            "P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF"
        };

        public string this[int position]
        {
            // Baseball positions are 1 - 9.
            get { return players[position-1]; }
            set { players[position-1] = value; }
        }
        public string this[string position]
        {
            get { return players[positionAbbreviations.IndexOf(position)]; }
            set { players[positionAbbreviations.IndexOf(position)] = value; }
        }
    }

    public static void Main()
    {
        var team = new BaseballTeam
        {
            ["RF"] = "Lisa Yeh",
            [4] = "Sandy Zoeng",
            ["CF"] = "Thomas Margand"
        };

        Console.WriteLine(team["2B"]);
    }
}

Objektinitialisierer mit dem erforderlichen Modifizierer

Sie verwenden das erforderliche Schlüsselwort, um Aufrufer zu erzwingen, den Wert einer Eigenschaft oder eines Felds mithilfe eines Objektinitialisierers festzulegen. Erforderliche Eigenschaften müssen nicht als Konstruktorparameter festgelegt werden. Der Compiler stellt sicher, dass alle Aufrufer diese Werte initialisieren.

// The `FirstName` property is optional and has a default value of an empty string.        
// The `LastName` property is required and must be initialized during object creation.
// You can create a new instance of the Person class using both properties.
var friend1 = new Person() { FirstName = "Lisa", LastName = "Yeh" };
Console.WriteLine($"Hello, {friend1.FirstName} {friend1.LastName}!");

// You can create a new instance of the Person class using only the LastName property.
var friend2 = new Person() { LastName = "Yeh"};
Console.WriteLine($"Hello, {friend2.FirstName} {friend2.LastName}!");

// You can assign a different value to the properties after the object is created.
friend2.FirstName = "Sandy";
friend2.LastName = "Chen";
Console.WriteLine($"Hello, {friend2.FirstName} {friend2.LastName}!");

// Compiler error: Required property 'LastName' must be initialized.
//var friend3 = new Person() { FirstName = "Lisa"};
//var friend4 = new Person();

// Output:
// Hello, Lisa Yeh!
// Hello,  Yeh!
// Hello, Sandy Chen!

public class Person
{
    public string FirstName { get; set; } = string.Empty;
    public required string LastName { get; set; }

}

Es ist eine typische Methode, sicherzustellen, dass Ihr Objekt ordnungsgemäß initialisiert wird, insbesondere wenn Sie mehrere Felder oder Eigenschaften verwalten und nicht alle in den Konstruktor aufnehmen möchten.

Objektinitialisierer mit dem Init-Accessor

Stellen Sie sicher, dass niemand das entworfene Objekt ändert, indem sie einen Init-Accessor verwenden. Es hilft, die Einstellung des Eigenschaftswerts einzuschränken.


public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; init; }
}

// You can create a new instance of the Person class any combination of the properties.
var friend0 = new Person();
var friend1 = new Person() { FirstName = "Lisa" };
var friend2 = new Person() { LastName = "Yeh" };
var friend3 = new Person() { FirstName = "Lisa", LastName = "Yeh" };

Console.WriteLine($"Hello, {friend0.FirstName} {friend0.LastName}!");
Console.WriteLine($"Hello, {friend1.FirstName} {friend1.LastName}!");
Console.WriteLine($"Hello, {friend2.FirstName} {friend2.LastName}!");
Console.WriteLine($"Hello, {friend3.FirstName} {friend3.LastName}!");

// You can assign a different value to the FirstName property after the object is created, but not the LastName property.
friend3.FirstName = "Sandy";

// Compiler error:
// Error CS8852  Init - only property or indexer 'Person.LastName' can only be assigned in an object initializer,
//               or on 'this' or 'base' in an instance constructor or an 'init' accessor.
//friend3.LastName = "Chen";

Console.WriteLine($"Hello, {friend3.FirstName} {friend3.LastName}!");


// Output:
// Hello,  unknown!
// Hello, Lisa unknown!
// Hello,  Yeh!
// Hello, Lisa Yeh!
// Hello, Sandy Yeh!

Erforderliche init-only-Eigenschaften unterstützen unveränderliche Strukturen und ermöglichen gleichzeitig natürliche Syntax für Benutzer des Typs.

Objektinitialisierer mit Klassentypeigenschaften

Es ist wichtig, die Auswirkungen auf Klassentypeigenschaften beim Initialisieren eines Objekts zu berücksichtigen:


public class HowToClassTypedInitializer
{
    public class EmbeddedClassTypeA
    {
        public int I { get; set; }
        public bool B { get; set; }
        public string S { get; set; }
        public EmbeddedClassTypeB ClassB { get; set; }

        public override string ToString() => $"{I}|{B}|{S}|||{ClassB}";

        public EmbeddedClassTypeA()
        {
            Console.WriteLine($"Entering EmbeddedClassTypeA constructor. Values are: {this}");
            I = 3;
            B = true;
            S = "abc";
            ClassB = new() { BB = true, BI = 43 };
            Console.WriteLine($"Exiting EmbeddedClassTypeA constructor. Values are: {this})");
        }
    }

    public class EmbeddedClassTypeB
    {
        public int BI { get; set; }
        public bool BB { get; set; }
        public string BS { get; set; }

        public override string ToString() => $"{BI}|{BB}|{BS}";

        public EmbeddedClassTypeB()
        {
            Console.WriteLine($"Entering EmbeddedClassTypeB constructor. Values are: {this}");
            BI = 23;
            BB = false;
            BS = "BBBabc";
            Console.WriteLine($"Exiting EmbeddedClassTypeB constructor. Values are: {this})");
        }
    }

    public static void Main()
    {
        var a = new EmbeddedClassTypeA
        {
            I = 103,
            B = false,
            ClassB = { BI = 100003 }
        };
        Console.WriteLine($"After initializing EmbeddedClassTypeA: {a}");

        var a2 = new EmbeddedClassTypeA
        {
            I = 103,
            B = false,
            ClassB = new() { BI = 100003 } //New instance
        };
        Console.WriteLine($"After initializing EmbeddedClassTypeA a2: {a2}");
    }

    // Output:
    //Entering EmbeddedClassTypeA constructor Values are: 0|False||||
    //Entering EmbeddedClassTypeB constructor Values are: 0|False|
    //Exiting EmbeddedClassTypeB constructor Values are: 23|False|BBBabc)
    //Exiting EmbeddedClassTypeA constructor Values are: 3|True|abc|||43|True|BBBabc)
    //After initializing EmbeddedClassTypeA: 103|False|abc|||100003|True|BBBabc
    //Entering EmbeddedClassTypeA constructor Values are: 0|False||||
    //Entering EmbeddedClassTypeB constructor Values are: 0|False|
    //Exiting EmbeddedClassTypeB constructor Values are: 23|False|BBBabc)
    //Exiting EmbeddedClassTypeA constructor Values are: 3|True|abc|||43|True|BBBabc)
    //Entering EmbeddedClassTypeB constructor Values are: 0|False|
    //Exiting EmbeddedClassTypeB constructor Values are: 23|False|BBBabc)
    //After initializing EmbeddedClassTypeA a2: 103|False|abc|||100003|False|BBBabc
}

Das nächste Beispiel zeigt die Reihenfolge der Ausführung von Konstruktor- und Memberinitialisierungen mithilfe von Konstruktoren mit und ohne Parameter:


public class ObjectInitializersExecutionOrder
{
    public static void Main()
    {
        new Person { FirstName = "Lisa", LastName = "Yeh", City = "unknown" };
        new Dog(2) { Name = "Oscar" };
    }

    public class Dog
    {
        private int age;
        private string name;

        public Dog(int age)
        {
            Console.WriteLine("Hello from Dog's non-parameterless constructor");
            this.age = age;
        }

        public required string Name
        {
            get { return name; }

            set
            {
                Console.WriteLine("Hello from setter of Dog's required property 'Name'");
                name = value;
            }
        }
    }

    public class Person
    {
        private string firstName;
        private string lastName;
        private string city;

        public Person()
        {
            Console.WriteLine("Hello from Person's parameterless constructor");
        }

        public required string FirstName
        {
            get { return firstName; }

            set
            {
                Console.WriteLine("Hello from setter of Person's required property 'FirstName'");
                firstName = value;
            }
        }

        public string LastName
        {
            get { return lastName; }

            init
            {
                Console.WriteLine("Hello from setter of Person's init property 'LastName'");
                lastName = value;
            }
        }

        public string City
        {
            get { return city; }

            set
            {
                Console.WriteLine("Hello from setter of Person's property 'City'");
                city = value;
            }
        }
    }

    // Output:
    // Hello from Person's parameterless constructor
    // Hello from setter of Person's required property 'FirstName'
    // Hello from setter of Person's init property 'LastName'
    // Hello from setter of Person's property 'City'
    // Hello from Dog's non-parameterless constructor
    // Hello from setter of Dog's required property 'Name'
}

Kopieren von Konstruktoren

Im folgenden Beispiel definiert die Person-Klasse einen Kopierkonstruktor, der als Argument eine Instanz von Personverwendet. Die Werte der Eigenschaften des Arguments werden den Eigenschaften der neuen Instanz von Personzugewiesen. Der Code enthält einen alternativen Kopierkonstruktor, der die Name und Age Eigenschaften der Instanz sendet, die Sie in den Instanzkonstruktor der Klasse kopieren möchten. Die Person Klasse ist versiegelt, sodass keine abgeleiteten Typen deklariert werden können, die Fehler durch Kopieren nur der Basisklasse auftreten können.


public sealed class Person
{
    // Copy constructor.
    public Person(Person previousPerson)
    {
        Name = previousPerson.Name;
        Age = previousPerson.Age;
    }

    //// Alternate copy constructor calls the instance constructor.
    //public Person(Person previousPerson)
    //    : this(previousPerson.Name, previousPerson.Age)
    //{
    //}

    // Instance constructor.
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    public int Age { get; set; }

    public string Name { get; set; }

    public string Details()
    {
        return Name + " is " + Age.ToString();
    }
}

class TestPerson
{
    static void Main()
    {
        // Create a Person object by using the instance constructor.
        Person person1 = new Person("Lisa", 40);

        // Create another Person object, copying person1.
        Person person2 = new Person(person1);

        // Change each person's age.
        person1.Age = 39;
        person2.Age = 41;

        // Change person2's name.
        person2.Name = "Sandy";

        // Show details to verify that the name and age fields are distinct.
        Console.WriteLine(person1.Details());
        Console.WriteLine(person2.Details());

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
// Output:
// Lisa is 39
// Sandy is 41