En .NET, un tipo de valor (C # struct
) no puede tener un constructor sin parámetros. De acuerdo con esta publicación, esto es obligatorio según la especificación CLI. Lo que sucede es que para cada tipo de valor se crea un constructor predeterminado (¿el compilador?) Que inicializa todos los miembros a cero (o null
).
¿Por qué no está permitido definir un 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 el 0/0
que no es tan genial.
PD : ¿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 su ejemplo, ¿qué le gustaría que sucediera cuando alguien lo hizo:
Rational[] fractions = new Rational[1000];
¿Debería pasar por su constructor 1000 veces?
Claro que debería, por eso escribí el constructor predeterminado en primer lugar. El CLR debe usar el constructor de puesta a cero predeterminado cuando no se define un constructor predeterminado explícito; de esa manera solo paga por lo que usa. Entonces, si quiero un contenedor de 1000 s no predeterminados Rational
(y quiero optimizar las 1000 construcciones), List<Rational>
usaré una matriz en lugar de una matriz.
Esta razón, en mi opinión, no es lo suficientemente fuerte como para evitar la definición de un constructor predeterminado.
Rational()
invoca el ctor sin parámetros en lugar delRational(long num=0, long denom=1)
.new Rational()
invocará al constructor si existe, sin embargo, si no existe,new Rational()
será equivalente adefault(Rational)
. En cualquier caso, se le recomienda utilizar la sintaxisdefault(Rational)
cuando desee el "valor cero" de su estructura (que es un número "malo" con su diseño propuesto deRational
). El valor predeterminado para un tipo de valorT
es siempredefault(T)
. Pornew Rational[1000]
lo tanto , nunca invocará constructores de estructuras.denominator - 1
dentro de la estructura, de modo que el valor predeterminado se convierta en 0/1Then if I want a container of 1000 non-default Rationals (and want to optimize away the 1000 constructions) I will use a List<Rational> rather than an array.
¿Por qué esperarías que una matriz invoque un constructor diferente a una Lista para una estructura?Respuestas:
Nota: la respuesta a continuación se escribió mucho antes de C # 6, que planea introducir la capacidad de declarar constructores sin parámetros en estructuras, pero aún no se invocará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 sobre el CLR.
El CLR permite que los tipos de valores 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, considere esto:
El CLR puede hacer esto de manera muy eficiente simplemente asignando la memoria adecuada y poniendo a cero todo. Si tuviera que ejecutar el constructor MyStruct 1000 veces, sería mucho menos eficiente. (De hecho, no es así - si hacer tener un constructor sin parámetros, que no consigue ejecutar cuando se crea una matriz, o cuando se tiene una variable de instancia sin inicializar.)
La regla básica en C # es "el valor predeterminado para cualquier tipo no puede confiar en ninguna inicialización". Ahora podrían haber permitido que se definieran constructores sin parámetros, pero no hubieran requerido que se ejecutara ese constructor en todos los casos, pero eso habría llevado a una mayor confusión. (O al menos, así que creo que el argumento continúa).
EDITAR: Para usar su ejemplo, ¿qué le gustaría que sucediera cuando alguien lo hizo:
¿Debería pasar por su constructor 1000 veces?
EDITAR: (Respondiendo un poco más de la pregunta) El compilador no crea el constructor sin parámetros. Los tipos de valor no tienen que tener constructores en lo que respecta al CLR, aunque resulta que sí puede si lo escribe en IL. Cuando escribe "
new Guid()
" en C # que emite IL diferente a lo que obtiene si llama a un constructor normal. Vea esta pregunta SO para obtener un poco más sobre ese aspecto.Yo sospecho que no hay ningún tipo de valor en el marco con los constructores sin parámetros. Sin duda, NDepend podría decirme si lo pregunto lo suficientemente bien ... El hecho de que C # lo prohíba es una pista lo suficientemente grande como para pensar que probablemente sea una mala idea.
fuente
Rational[] fractions = new Rational[1000];
También desperdicia una gran cantidad de trabajo siRational
es una clase en lugar de una estructura? Si es así, ¿por qué las clases tienen un ctor predeterminado?Rational
es una clase, terminará con una matriz de 1000 referencias nulas.Una estructura es un tipo de valor y un tipo de valor debe tener un valor predeterminado tan pronto como se declare.
Si declara dos campos como el anterior sin crear ninguna instancia, entonces romper el depurador,
m
será nulo perom2
no lo será. Dado esto, un constructor sin parámetros no tendría sentido, de hecho, todo constructor en una estructura hace es asignar valores, la cosa en sí ya existe simplemente declarándolo. ¡De hecho, m2 podría utilizarse con mucho gusto en el ejemplo anterior y tener sus métodos llamados, si los hay, y sus campos y propiedades manipulados!fuente
new
It, luego tratar de usar sus miembrosAunque 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 predeterminado ni generan una llamada al constructor predeterminado. Por lo tanto, incluso si 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 debido a que no genera un constructor predeterminado, no puede inicializar campos al definirlos.
O la gran razón es que una estructura es un tipo de valor y los tipos de valor se inicializan con un valor predeterminado y el constructor se usa para la inicialización.
No tiene que instanciar su estructura con la
new
palabra clave. En cambio, funciona como un int; puedes acceder directamente a él.Las estructuras no pueden contener constructores explícitos sin parámetros. Los miembros de estructura se inicializan automáticamente a sus valores predeterminados.
Un constructor predeterminado (sin parámetros) para una estructura podría establecer valores diferentes que el estado todo 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.
fuente
MyStruct s;
no llamar al constructor predeterminado que proporcionó.Puede crear una propiedad estática que inicialice y devuelva un número "racional" predeterminado:
Y úsalo como:
fuente
Rational.Zero
podría ser un poco menos confuso.Breve explicación:
En C ++, struct y class eran solo dos caras de la misma moneda. La única diferencia real es que uno era público por defecto y el otro privado.
En .NET , hay una diferencia mucho mayor entre una estructura y una clase. Lo principal es que struct proporciona una semántica de tipo de valor, mientras que la clase proporciona una semántica de tipo de referencia. Cuando empiezas a pensar en las implicaciones de este cambio, otros cambios también comienzan a tener más sentido, incluido el comportamiento del constructor que describes.
fuente
new
realmente debe escribirse para llamar a un constructor. En C ++, los constructores se llaman de forma oculta, en la declaración o instancia de las matrices. En C #, o bien todo es un puntero, así que comienza en nulo, o bien es una estructura y debe comenzar en algo, pero cuando no puede escribirnew
... (como array init), eso rompería una regla fuerte de C #.No he visto el equivalente a una solución tardía que voy a dar, así que aquí está.
use desplazamientos para mover valores del valor predeterminado 0 a cualquier valor que desee. aquí las propiedades deben usarse en lugar de acceder directamente a los campos. (tal vez con la posible característica de c # 7, defina mejor los campos con ámbito de propiedad para que permanezcan protegidos contra el acceso directo en código).
Esta solución funciona para estructuras simples con solo tipos de valor (sin tipo de referencia o estructura anulable).
Esto es diferente a esta respuesta, este enfoque no es una carcasa especial, sino que utiliza un desplazamiento que funcionará para todos los rangos.
ejemplo con enumeraciones como campo.
Como dije, este truco puede no funcionar en todos los casos, incluso si struct solo tiene campos de valor, solo usted lo sabe si funciona en su caso o no. solo examina. pero entiendes la idea general.
fuente
Solo caso especial. Si ve un numerador de 0 y un denominador de 0, imagine que tiene los valores que realmente desea.
fuente
Nullable<T>
(por ejemploint?
).new Rational(x,y)
donde x e y resultan ser 0?Lo que uso es el operador de fusión nula (??) combinado con un campo de respaldo como este:
Espero que esto ayude ;)
Nota: la asignación de fusión nula es actualmente una propuesta de función para C # 8.0.
fuente
No puede definir un constructor predeterminado porque está usando C #.
Las estructuras pueden tener constructores predeterminados en .NET, aunque no conozco ningún lenguaje específico que lo admita.
fuente
Aquí está mi solución al dilema del constructor no predeterminado. Sé que esta es una solución tardía, pero creo que vale la pena señalar que es una solución.
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 intento / captura / finalmente, para tratar los errores en los que un tipo de datos en particular no es inicializado por el constructor predeterminado Point2D (). Supongo que esto es esquivo como una solución para algunas personas en esta respuesta. Eso es sobre todo 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 poner a prueba lo que no ha inicializado. Para mí, esto funciona bien, para alguien más puede que desee agregar algunas declaraciones if. Entonces, en el caso de que desee una configuración de Numerador / Denominador, este código podría ayudar. Solo quisiera reiterar que esta solución no se ve bien, probablemente funciona aún 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:
fuente
fuente