Creare un'istanza di oggetti usando inizializzatori e costruttori di copia
Gli inizializzatori di oggetti e i costruttori di copia sono due modi per creare un'istanza di oggetti in C#. Gli inizializzatori di oggetti consentono di assegnare valori a qualsiasi campo o proprietà accessibile di un oggetto in fase di creazione senza dover richiamare un costruttore seguito da righe di istruzioni di assegnazione. I costruttori di copia consentono di creare un nuovo oggetto copiando i valori di un oggetto esistente.
L'uso di inizializzatori di oggetti e costruttori di copia consente di scrivere codice più conciso e leggibile.
Inizializzatori di oggetti
Gli inizializzatori di oggetti consentono di assegnare valori a qualsiasi campo o proprietà accessibile di un oggetto in fase di creazione senza dover richiamare un costruttore seguito da righe di istruzioni di assegnazione. La sintassi dell'inizializzatore di oggetti consente di specificare argomenti per un costruttore o omettere gli argomenti (e la sintassi delle parentesi).
È possibile utilizzare gli inizializzatori di oggetto per inizializzare gli oggetti di tipo in modo dichiarativo senza richiamare in modo esplicito un costruttore per il tipo.
Il compilatore elabora gli inizializzatori di oggetti accedendo prima al costruttore dell'istanza senza parametri e quindi elaborando le inizializzazioni del membro. Pertanto, se il costruttore senza parametri viene dichiarato come privato nella classe, gli inizializzatori di oggetto che richiedono l'accesso pubblico avranno esito negativo.
Nell'esempio seguente viene illustrato come usare un inizializzatore di oggetto.
Il primo esempio di codice mostra la definizione della classe per una classe denominata Cat. La definizione include due costruttori, uno dei quali è un costruttore senza parametri. Il secondo esempio di codice mostra come creare un'istanza di un oggetto Cat usando un inizializzatore di oggetto. L'inizializzatore di oggetti assegna valori alle proprietà Age e Name dell'oggetto Cat.
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 };
}
}
Ecco un altro esempio che illustra come inizializzare un nuovo tipo di StudentName usando gli inizializzatori di oggetto. In questo esempio vengono impostate le proprietà nel tipo di StudentName:
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;
}
}
La sintassi degli inizializzatori di oggetti consente di creare un'istanza e, dopo, assegna l'oggetto appena creato, con le relative proprietà assegnate, alla variabile nell'assegnazione.
Oltre ad assegnare campi e proprietà, gli inizializzatori di oggetto possono impostare gli indicizzatori. Si consideri questa classe di base Matrix:
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; }
}
}
È possibile inizializzare la matrice di identità con il codice seguente:
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,
};
L'esempio seguente definisce una classe BaseballTeam che usa un indicizzatore per ottenere e impostare i giocatori in posizioni diverse. L'inizializzatore può assegnare giocatori, in base all'abbreviazione per la posizione o al numero utilizzato per ogni posizione su una scorecard di baseball:
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"]);
}
}
Inizializzatori di oggetti con il modificatore richiesto
Usare la parola chiave obbligatoria per forzare i chiamanti a impostare il valore di una proprietà o di un campo usando un inizializzatore di oggetto. Le proprietà obbligatorie non devono essere impostate come parametri del costruttore. Il compilatore garantisce che tutti i chiamanti inizializzino tali valori.
// 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; }
}
È una procedura tipica per garantire che l'oggetto venga inizializzato correttamente, soprattutto quando si dispone di più campi o proprietà da gestire e non si vuole includerli tutti nel costruttore.
Inizializzatori di oggetti con la funzione di accesso init
Assicurarsi che nessuna modifica dell'oggetto progettato possa essere limitata tramite una funzione di accesso init. Consente di limitare l'impostazione del valore della proprietà.
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!
Le proprietà init-only necessarie supportano strutture non modificabili, consentendo al tempo stesso la sintassi naturale per gli utenti del tipo.
Inizializzatori di oggetti con proprietà tipizzate dalla classe
È fondamentale considerare le implicazioni per le proprietà tipizzate dalla classe durante l'inizializzazione di un oggetto:
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
}
Nell'esempio seguente viene illustrato l'ordine di esecuzione delle inizializzazioni del costruttore e dei membri usando costruttori con e senza parametri:
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'
}
Costruttori di copia
Nell'esempio seguente, la classe Person definisce un costruttore di copia che accetta, come argomento, un'istanza di Person. I valori delle proprietà dell'argomento vengono assegnati alle proprietà della nuova istanza di Person. Il codice contiene un costruttore di copia alternativo che invia il Name e le proprietà Age dell'istanza che si desidera copiare nel costruttore dell'istanza della classe . La classe Person è sealed, quindi non è possibile dichiarare tipi derivati che potrebbero introdurre errori copiando solo la classe base.
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