En C# 9 podemos crear registros posicionales haciendo que obtengan un constructor, que el borrador de especificaciones llama constructor primario . También podemos crear un constructor personalizado, pero como se indica en la especificación:
Si un registro tiene un constructor principal, cualquier constructor definido por el usuario, excepto el "constructor de copia", debe tener un inicializador de este constructor explícito.
Así que esto está prohibido:
public record A(string Foo, int Bar) { public A(MyClass x) { Foo = x.Name; Bar = x.Number; } }y, de hecho, hace que CS8862 "Un constructor declarado en un registro con una lista de parámetros debe tener 'este' inicializador de constructor". Tenemos que escribir:
public record A(string Foo, int Bar) { public A(MyClass x) : this(x.Name, x.Number) {} } en lugar de. En este caso, esto no es un problema, pero uno podría imaginar una lógica de inicialización mucho más larga que simplemente no encajaba en el inicializador de this constructor.
La pregunta es: ¿por qué existe esta restricción? Supongo que levantarlo habilitaría una forma de romper algunas de las características de los registros, pero es una característica lo suficientemente nueva como para que no se me ocurra una forma de hacerlo. ¿El constructor primario autogenerado hace algo que es crucial para que el registro funcione correctamente y, por lo tanto, debe llamarse?
Esto se debe a que los parámetros del constructor primario son un poco especiales: están dentro del alcance durante la inicialización del registro. Adivina lo que imprime el siguiente programa:
System.Console.WriteLine(new Foo(Bar: 42).Baz); public record Foo(int Bar) { public int Bar => 41; public int Baz = Bar; } 41 o 42 ?
Y la respuesta es...
redoble de tambores por favor...
42 !
¿Que está pasando aqui?
Durante la inicialización del registro, cualquier referencia a Bar no se refiere a la propiedad Bar , sino al parámetro principal del constructor Bar .
Lo que esto significa es que se debe llamar al constructor principal. De lo contrario, qué sucedería en este caso:
System.Console.WriteLine(new Foo().Baz); public record Foo(int Bar) { public Foo(){} public int Bar => 41; public int Baz = Bar; // What is Bar here when the primary constructor isn't called. } El parámetro Bar solo está dentro del alcance durante la inicialización. Después de la inicialización, la propiedad Bar está dentro del alcance. Si tuviéramos que cambiar nuestro ejemplo muy ligeramente:
System.Console.WriteLine(new Foo(Bar: 42).Baz); public record Foo(int Bar) { public int Bar => 41; public int Baz => Bar; //Note this is `=>` not `=` } Imprimiría 41 .
Basado en una descompilación rápida de Linqpad, sí, parece que este constructor ESTÁ haciendo un trabajo que, de otro modo, podría no ser deducible debido a que no sabe cómo asignar el tipo a las propiedades del registro.
public A(string Foo, int Bar) { this.Foo = Foo; this.Bar = Bar; base..ctor(); } public A(MyClass x) : this(x.Name, x.Number) { } Un registro espera que todas las propiedades se inicialicen a través del constructor. Si las propiedades pudieran asignarse arbitrariamente en un constructor adicional sin llamar explícitamente a this , no habría una forma (inmediatamente obvia) de garantizar que se hayan cumplido todos los requisitos de los parámetros de propiedad. Como resultado, se requiere una llamada a this(params) para aplicar la asignación de propiedades.
No estoy seguro de tener una respuesta definitiva, pero aquí están mis pensamientos basados en lo que he leído. Esta
public record MyRecord(string foo, int bar);es equivalente a:
public class MyRecord { string foo { get; init; } // Code correction - set to init int bar { get; init; } }El constructor y las propiedades se infieren en una sola línea. Si bien estoy seguro de que es posible agregar una asignación concreta a este conjunto inferido de construcciones (y tal vez lo haga en una versión futura), probablemente sea más fácil trabajar con el encadenamiento de constructores para el primer paso. Como alguien que ha usado constructores encadenados en algunas clases (no generalmente pocas), tiene sentido. Pero no estoy seguro de cuántas veces sobrecargaría el constructor y no veo un gran beneficio, al menos desde el punto de vista arquitectónico, al hacerlo de la forma en que lo hizo.