Creación de instancias de objetos mediante inicializadores y constructores de copia
Los inicializadores de objetos y los constructores de copia son dos maneras de crear instancias de objetos en C#. Los inicializadores de objeto permiten asignar valores a los campos o propiedades accesibles de un objeto en tiempo de creación sin tener que invocar un constructor seguido de líneas de instrucciones de asignación. Los constructores de copia permiten crear un nuevo objeto copiando los valores de un objeto existente.
El uso de inicializadores de objetos y constructores de copia puede ayudarle a escribir código más conciso y legible.
Inicializadores de objeto
Los inicializadores de objeto permiten asignar valores a los campos o propiedades accesibles de un objeto en tiempo de creación sin tener que invocar un constructor seguido de líneas de instrucciones de asignación. La sintaxis del inicializador de objeto permite especificar argumentos para un constructor o omitir los argumentos (y sintaxis entre paréntesis).
Puede usar inicializadores de objeto para inicializar objetos de tipo de forma declarativa sin invocar explícitamente un constructor para el tipo.
El compilador procesa inicializadores de objeto accediendo primero al constructor de instancia sin parámetros y, a continuación, procesando las inicializaciones de miembro. Por lo tanto, si el constructor sin parámetros se declara como privado en la clase , se producirá un error en los inicializadores de objeto que requieren acceso público.
En el ejemplo siguiente se muestra cómo usar un inicializador de objeto.
En el primer ejemplo de código se muestra la definición de clase de una clase denominada Cat. La definición incluye dos constructores, uno de los cuales es un constructor sin parámetros. En el segundo ejemplo de código se muestra cómo crear una instancia de un objeto Cat mediante un inicializador de objeto. El inicializador de objeto asigna valores a las propiedades Age y Name del objeto 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 };
}
}
Este es otro ejemplo que muestra cómo inicializar un nuevo tipo de StudentName mediante inicializadores de objeto. En este ejemplo se establecen las propiedades del tipo 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 sintaxis de inicializadores de objeto permite crear una instancia y, después, asigna el objeto recién creado, con sus propiedades asignadas, a la variable de la asignación.
Además de asignar campos y propiedades, los inicializadores de objetos pueden establecer indizadores. Tenga en cuenta esta clase de Matrix básica:
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; }
}
}
Puede inicializar la matriz de identidades con el código siguiente:
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,
};
En el ejemplo siguiente se define una clase BaseballTeam que usa un indexador para obtener y establecer jugadores en diferentes posiciones. El inicializador puede asignar jugadores, según la abreviatura de la posición, o el número utilizado para cada posición en un cuadro de mandos de béisbol:
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"]);
}
}
Inicializadores de objeto con el modificador necesario
Use la palabra clave necesaria para forzar a los autores de llamada a establecer el valor de una propiedad o campo mediante un inicializador de objeto. No es necesario establecer las propiedades necesarias como parámetros de constructor. El compilador garantiza que todos los autores de llamadas inicialicen esos valores.
// 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 una práctica habitual garantizar que el objeto se inicialice correctamente, especialmente cuando tiene varios campos o propiedades para administrar y no desea incluirlos todos en el constructor.
Inicializadores de objeto con el descriptor de acceso init
Asegúrese de que nadie cambie el objeto diseñado podría estar limitado mediante un descriptor de acceso init. Ayuda a restringir la configuración del valor de la propiedad.
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!
Las propiedades de solo inicialización necesarias admiten estructuras inmutables, al tiempo que permiten la sintaxis natural para los usuarios del tipo.
Inicializadores de objeto con propiedades con tipo de clase
Es fundamental tener en cuenta las implicaciones de las propiedades con tipo de clase al inicializar un objeto:
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
}
En el ejemplo siguiente se muestra el orden de ejecución de inicializaciones de constructores y miembros mediante constructores con y sin parámetros:
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'
}
Copiar constructores
En el ejemplo siguiente, la clase Person define un constructor de copia que toma, como argumento, una instancia de Person. Los valores de las propiedades del argumento se asignan a las propiedades de la nueva instancia de Person. El código contiene un constructor de copia alternativo que envía las propiedades Name y Age de la instancia que desea copiar en el constructor de instancia de la clase . La clase Person está sellada, por lo que no se puede declarar ningún tipo derivado que pueda introducir errores copiando solo la clase 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