c# - sobrecarga - ¿Por qué no puedo definir un constructor predeterminado para una estructura en.NET?
sobrecarga de metodos c# pdf (10)
Aquí está mi solución al dilema de constructor no predeterminado. Sé que esta es una solución tardía, pero creo que vale la pena señalar que esta es una solución.
public struct Point2D {
public static Point2D NULL = new Point2D(-1,-1);
private int[] Data;
public int X {
get {
return this.Data[ 0 ];
}
set {
try {
this.Data[ 0 ] = value;
} catch( Exception ) {
this.Data = new int[ 2 ];
} finally {
this.Data[ 0 ] = value;
}
}
}
public int Z {
get {
return this.Data[ 1 ];
}
set {
try {
this.Data[ 1 ] = value;
} catch( Exception ) {
this.Data = new int[ 2 ];
} finally {
this.Data[ 1 ] = value;
}
}
}
public Point2D( int x , int z ) {
this.Data = new int[ 2 ] { x , z };
}
public static Point2D operator +( Point2D A , Point2D B ) {
return new Point2D( A.X + B.X , A.Z + B.Z );
}
public static Point2D operator -( Point2D A , Point2D B ) {
return new Point2D( A.X - B.X , A.Z - B.Z );
}
public static Point2D operator *( Point2D A , int B ) {
return new Point2D( B * A.X , B * A.Z );
}
public static Point2D operator *( int A , Point2D B ) {
return new Point2D( A * B.Z , A * B.Z );
}
public override string ToString() {
return string.Format( "({0},{1})" , this.X , this.Z );
}
}
ignorando el hecho de que tengo una estructura estática llamada nula, (Nota: esto es solo para todos los cuadrantes positivos), usando get; set; en C #, puede tener un try / catch / finalmente, para tratar los errores en los que el constructor predeterminado Point2D () no inicializa un tipo de datos en particular. Supongo que esto es esquivo como una solución para algunas personas en esta respuesta. Eso es principalmente por qué estoy agregando el mío. El uso de la funcionalidad getter y setter en C # le permitirá omitir este constructor predeterminado sin sentido y probar lo que no ha inicializado. Para mí esto funciona bien, para otra persona es posible que desee agregar algunas declaraciones if. Por lo tanto, en el caso de que desee una configuración de Numerador / Denominador, este código podría ayudar. Me gustaría reiterar que esta solución no se ve bien, probablemente funcione incluso peor desde el punto de vista de la eficiencia, pero para alguien que viene de una versión anterior de C #, el uso de tipos de datos de matriz le brinda esta funcionalidad. Si solo quieres algo que funcione, prueba esto:
public struct Rational {
private long[] Data;
public long Numerator {
get {
try {
return this.Data[ 0 ];
} catch( Exception ) {
this.Data = new long[ 2 ] { 0 , 1 };
return this.Data[ 0 ];
}
}
set {
try {
this.Data[ 0 ] = value;
} catch( Exception ) {
this.Data = new long[ 2 ] { 0 , 1 };
this.Data[ 0 ] = value;
}
}
}
public long Denominator {
get {
try {
return this.Data[ 1 ];
} catch( Exception ) {
this.Data = new long[ 2 ] { 0 , 1 };
return this.Data[ 1 ];
}
}
set {
try {
this.Data[ 1 ] = value;
} catch( Exception ) {
this.Data = new long[ 2 ] { 0 , 1 };
this.Data[ 1 ] = value;
}
}
}
public Rational( long num , long denom ) {
this.Data = new long[ 2 ] { num , denom };
/* Todo: Find GCD etc. */
}
public Rational( long num ) {
this.Data = new long[ 2 ] { num , 1 };
this.Numerator = num;
this.Denominator = 1;
}
}
En .NET, un tipo de valor (C # struct
) no puede tener un constructor sin parámetros. De acuerdo con este post, esto es obligatorio por la especificación CLI. Lo que sucede es que para cada tipo de valor se crea un constructor predeterminado (por el compilador?) Que inicializa todos los miembros a cero (o null
).
¿Por qué no está permitido definir tal constructor predeterminado?
Un uso trivial es para números racionales:
public struct Rational {
private long numerator;
private long denominator;
public Rational(long num, long denom)
{ /* Todo: Find GCD etc. */ }
public Rational(long num)
{
numerator = num;
denominator = 1;
}
public Rational() // This is not allowed
{
numerator = 0;
denominator = 1;
}
}
Usando la versión actual de C #, un Rational predeterminado es 0/0
que no es tan bueno.
PS : ¿Los parámetros predeterminados ayudarán a resolver esto para C # 4.0 o se llamará al constructor predeterminado definido por CLR?
Jon Skeet respondió:
Para usar tu ejemplo, ¿qué querrías que sucediera cuando alguien lo hiciera?
Rational[] fractions = new Rational[1000];
¿Debería correr a través de su constructor 1000 veces?
Claro que debería, es por eso que escribí el constructor predeterminado en primer lugar. El CLR debe usar el constructor de reducción a cero predeterminado cuando no se define ningún constructor predeterminado explícito; De esa manera solo pagas por lo que usas. Luego, si quiero un contenedor de 1000 Rational
no predeterminados (y quiero optimizar las 1000 construcciones) usaré una List<Rational>
lugar de una matriz.
Esta razón, en mi opinión, no es lo suficientemente fuerte como para impedir la definición de un constructor predeterminado.
Aunque el CLR lo permite, C # no permite que las estructuras tengan un constructor predeterminado sin parámetros. La razón es que, para un tipo de valor, los compiladores por defecto no generan un constructor por defecto, ni generan una llamada al constructor por defecto. Entonces, incluso si usted definió un constructor predeterminado, no se llamará, y eso solo lo confundirá.
Para evitar tales problemas, el compilador de C # no permite la definición de un constructor predeterminado por parte del usuario. Y como no genera un constructor predeterminado, no puede inicializar los campos al definirlos.
O el motivo principal es que una estructura es un tipo de valor y los valores se inicializan con un valor predeterminado y el constructor se usa para la inicialización.
No tiene que crear una instancia de su estructura con la new
palabra clave. En cambio, funciona como un int; Usted puede acceder directamente a él.
Las estructuras no pueden contener constructores explícitos sin parámetros. Los miembros de Struct se inicializan automáticamente a sus valores predeterminados.
Un constructor predeterminado (sin parámetros) para una estructura podría establecer valores diferentes al estado de puesta a cero, lo que sería un comportamiento inesperado. El tiempo de ejecución .NET, por lo tanto, prohíbe los constructores predeterminados para una estructura.
No he visto el equivalente a la solución tardía que voy a dar, así que aquí está.
use las compensaciones para mover valores del valor predeterminado 0 a cualquier valor que desee. Aquí se deben usar las propiedades en lugar de acceder directamente a los campos. (Tal vez con la posible función c # 7 es mejor que defina los campos del ámbito de la propiedad para que estén protegidos del acceso directo en el código).
Esta solución funciona para estructuras simples con solo tipos de valor (sin tipo de referencia o estructura con posibilidad de nulos).
public struct Tempo
{
const double DefaultBpm = 120;
private double _bpm; // this field must not be modified other than with its property.
public double BeatsPerMinute
{
get => _bpm + DefaultBpm;
set => _bpm = value - DefaultBpm;
}
}
Esto es diferente than esta respuesta, este enfoque no es especial, sino que utiliza un desplazamiento que funcionará para todos los rangos.
Ejemplo con enumeraciones como campo.
public struct Difficaulty
{
Easy,
Medium,
Hard
}
public struct Level
{
const Difficaulty DefaultLevel = Difficaulty.Medium;
private Difficaulty _level; // this field must not be modified other than with its property.
public Difficaulty Difficaulty
{
get => _level + DefaultLevel;
set => _level = value - DefaultLevel;
}
}
Como dije, este truco puede no funcionar en todos los casos, incluso si la estructura tiene solo campos de valor, solo usted sabe si funciona en su caso o no. solo examina Pero tienes la idea general.
No puede definir un constructor predeterminado porque está utilizando C #.
Structs puede tener constructores predeterminados en .NET, aunque no conozco ningún idioma específico que lo admita.
Puede crear una propiedad estática que se inicializa y devuelve un número "racional" predeterminado:
public static Rational One => new Rational(0, 1);
Y utilízalo como:
var rat = Rational.One;
Sólo un caso especial. Si ve un numerador de 0 y un denominador de 0, simule que tiene los valores que realmente desea.
Una estructura es un tipo de valor y un tipo de valor debe tener un valor predeterminado tan pronto como se declara.
MyClass m;
MyStruct m2;
Si declara dos campos como los anteriores sin crear una instancia de ninguno de los dos, entonces rompa el depurador, m
será nulo pero no lo hará m2
. Dado esto, un constructor sin parámetros no tendría sentido, de hecho, todo lo que cualquier constructor en una estructura hace es asignar valores, la cosa en sí ya existe con solo declararlo. De hecho, m2 podría muy bien ser usado en el ejemplo anterior y tener sus métodos llamados, si los hay, y sus campos y propiedades manipulados.
Una explicación más corta:
En C ++, la estructura y la clase eran solo dos caras de la misma moneda. La única diferencia real es que uno era público por defecto y el otro era privado.
En .NET , hay una diferencia mucho mayor entre una estructura y una clase. Lo principal es que la estructura proporciona semántica de tipo de valor, mientras que la clase proporciona semántica de tipo de referencia. Cuando empiezas a pensar en las implicaciones de este cambio, otros cambios también tendrán más sentido, incluido el comportamiento del constructor que describas.
Nota: la respuesta a continuación se escribió mucho tiempo antes de C # 6, que planea introducir la capacidad de declarar constructores sin parámetros en estructuras, pero aún no se llamarán en todas las situaciones (por ejemplo, para la creación de matrices) (al final esta característica no se agregó a C # 6 ).
EDITAR: He editado la respuesta a continuación debido a la visión de Grauenwolf en el CLR.
El CLR permite que los tipos de valor tengan constructores sin parámetros, pero C # no. Creo que esto se debe a que introduciría una expectativa de que se llamaría al constructor cuando no lo haría. Por ejemplo, considera esto:
MyStruct[] foo = new MyStruct[1000];
El CLR puede hacer esto de manera muy eficiente con solo asignar la memoria apropiada y ponerla a cero. Si tuviera que ejecutar el constructor MyStruct 1000 veces, sería mucho menos eficiente. (De hecho, no es así: si tiene un constructor sin parámetros, no se ejecuta cuando crea una matriz o cuando tiene una variable de instancia sin inicializar).
La regla básica en C # es "el valor predeterminado para cualquier tipo no puede depender de ninguna inicialización". Ahora podrían haber permitido que se definieran constructores sin parámetros, pero luego no requerían que ese constructor se ejecutara en todos los casos, pero eso habría llevado a más confusión. (O al menos, así que creo que el argumento va).
EDITAR: Para usar tu ejemplo, ¿qué desearías que sucediera cuando alguien lo hiciera?
Rational[] fractions = new Rational[1000];
¿Debería correr a través de su constructor 1000 veces?
- Si no, terminamos con 1000 racionales inválidos.
- Si lo hace, entonces potencialmente hemos desperdiciado una carga de trabajo si estamos a punto de completar la matriz con valores reales.
EDIT: (Respondiendo un poco más a la pregunta) El constructor sin parámetros no es creado por el compilador. Los tipos de valor no tienen que tener constructores en lo que concierne al CLR, aunque resulta que puede escribirlo en IL. Cuando escribe " new Guid()
" en C # que emite diferentes IL a lo que obtiene si llama a un constructor normal. Vea esta pregunta SO para un poco más sobre ese aspecto.
Sospecho que no hay ningún tipo de valor en el marco con constructores sin parámetros. Sin duda, NDepend podría decirme si lo pregunté lo suficientemente bien ... El hecho de que C # lo prohíba es un indicio suficiente para que piense que es probablemente una mala idea.
public struct Rational
{
private long numerator;
private long denominator;
public Rational(long num = 0, long denom = 1) // This is allowed!!!
{
numerator = num;
denominator = denom;
}
}