Dans .NET, un type de valeur (C # struct
) ne peut pas avoir de constructeur sans paramètres. Selon cet article, cela est rendu obligatoire par la spécification CLI. Ce qui se passe, c'est que pour chaque type de valeur, un constructeur par défaut est créé (par le compilateur?) Qui initialise tous les membres à zéro (ou null
).
Pourquoi est-il interdit de définir un tel constructeur par défaut?
Une utilisation triviale est pour les nombres rationnels:
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;
}
}
En utilisant la version actuelle de C #, un Rational par défaut est 0/0
ce qui n'est pas si cool.
PS : les paramètres par défaut aideront-ils à résoudre ce problème pour C # 4.0 ou le constructeur par défaut défini par CLR sera-t-il appelé?
Jon Skeet a répondu:
Pour utiliser votre exemple, que voudriez-vous qu'il se passe lorsque quelqu'un:
Rational[] fractions = new Rational[1000];
Doit-il parcourir 1000 fois votre constructeur?
Bien sûr, c'est pourquoi j'ai écrit le constructeur par défaut en premier lieu. Le CLR doit utiliser le constructeur de mise à zéro par défaut quand aucun constructeur par défaut explicite n'est défini; de cette façon, vous ne payez que ce que vous utilisez. Ensuite, si je veux un conteneur de 1000 Rational
s par défaut (et que je souhaite optimiser les 1000 constructions), j'utiliserai List<Rational>
plutôt qu'un tableau.
Cette raison, à mon avis, n'est pas assez forte pour empêcher la définition d'un constructeur par défaut.
Rational()
invoque le ctor sans paramètre plutôt que leRational(long num=0, long denom=1)
.new Rational()
, invoquera le constructeur s'il existe, mais s'il n'existe pas,new Rational()
sera équivalent àdefault(Rational)
. Dans tous les cas, nous vous encourageons à utiliser la syntaxedefault(Rational)
lorsque vous voulez la "valeur zéro" de votre structure (qui est un "mauvais" nombre avec votre conception proposée deRational
). La valeur par défaut pour un type de valeurT
est toujoursdefault(T)
. N'invoquera doncnew Rational[1000]
jamais les constructeurs struct.denominator - 1
à l'intérieur de la structure, de sorte que la valeur par défaut devienne 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.
Pourquoi attendriez-vous qu'un tableau appelle un constructeur différent à une liste pour une structure?Réponses:
Remarque: la réponse ci-dessous a été écrite longtemps avant C # 6, qui prévoit d'introduire la possibilité de déclarer des constructeurs sans paramètre dans les structures - mais ils ne seront toujours pas appelés dans toutes les situations (par exemple pour la création de tableaux)(à la fin cette fonctionnalité n'a pas été ajoutée à C # 6 ).EDIT: J'ai édité la réponse ci-dessous en raison de la perspicacité de Grauenwolf dans le CLR.
Le CLR permet aux types de valeurs d'avoir des constructeurs sans paramètres, mais pas C #. Je crois que c'est parce que cela introduirait une attente que le constructeur soit appelé alors qu'il ne le ferait pas. Par exemple, considérez ceci:
Le CLR est capable de le faire très efficacement simplement en allouant la mémoire appropriée et en mettant tout à zéro. S'il devait exécuter le constructeur MyStruct 1000 fois, ce serait beaucoup moins efficace. (En fait, il ne fonctionne pas - si vous n'avez un constructeur parameterless, il ne se tourne pas lorsque vous créez un tableau, ou lorsque vous avez une variable d'instance non initialisée.)
La règle de base en C # est "la valeur par défaut pour tout type ne peut pas compter sur une initialisation". Maintenant, ils auraient pu permettre de définir des constructeurs sans paramètres, mais ils n'auraient alors pas exigé que ce constructeur soit exécuté dans tous les cas - mais cela aurait conduit à plus de confusion. (Ou du moins, donc je crois que l'argument va.)
EDIT: Pour utiliser votre exemple, que voudriez-vous qu'il se passe quand quelqu'un:
Doit-il parcourir 1000 fois votre constructeur?
EDIT: (Répondant un peu plus à la question) Le constructeur sans paramètre n'est pas créé par le compilateur. Les types de valeur n'ont pas besoin d'avoir de constructeurs en ce qui concerne le CLR - bien qu'il s'avère que cela peut être si vous l'écrivez en IL. Lorsque vous écrivez "
new Guid()
" en C #, cela émet un IL différent de ce que vous obtenez si vous appelez un constructeur normal. Voir cette question SO pour un peu plus sur cet aspect.Je soupçonne qu'il n'y a aucun type de valeur dans le framework avec des constructeurs sans paramètres. Nul doute que NDepend pourrait me dire si je le lui ai demandé assez gentiment ... Le fait que C # l'interdise est un indice assez important pour que je pense que c'est probablement une mauvaise idée.
la source
Rational[] fractions = new Rational[1000];
aussi une perte de travail siRational
c'est une classe au lieu d'une structure? Si oui, pourquoi les classes ont-elles un ctor par défaut?Rational
est une classe, vous vous retrouverez avec un tableau de 1000 références nulles.Un struct est un type de valeur et un type de valeur doit avoir une valeur par défaut dès qu'il est déclaré.
Si vous déclarez deux champs comme ci-dessus sans instancier non plus, alors le débogueur
m
sera cassé, sera nul maism2
ne le sera pas. Compte tenu de cela, un constructeur sans paramètre n'aurait aucun sens, en fait, tout constructeur sur une structure ne fait qu'affecter des valeurs, la chose elle-même existe déjà juste en la déclarant. En effet m2 pourrait très bien être utilisé dans l'exemple ci-dessus et avoir ses méthodes appelées, le cas échéant, et ses champs et propriétés manipulés!la source
new
It, puis d'essayer d'utiliser ses membresBien que le CLR le permette, C # ne permet pas aux structures d'avoir un constructeur sans paramètre par défaut. La raison en est que, pour un type de valeur, les compilateurs par défaut ne génèrent pas de constructeur par défaut, ni ne génèrent d'appel au constructeur par défaut. Donc, même s'il vous arrivait de définir un constructeur par défaut, il ne sera pas appelé, et cela ne fera que vous embrouiller.
Pour éviter de tels problèmes, le compilateur C # interdit la définition d'un constructeur par défaut par l'utilisateur. Et comme il ne génère pas de constructeur par défaut, vous ne pouvez pas initialiser les champs lors de leur définition.
Ou la grande raison est qu'une structure est un type de valeur et que les types de valeur sont initialisés par une valeur par défaut et que le constructeur est utilisé pour l'initialisation.
Vous n'avez pas à instancier votre structure avec le
new
mot clé. Il fonctionne à la place comme un int; vous pouvez y accéder directement.Les structures ne peuvent pas contenir de constructeurs explicites sans paramètre. Les membres de structure sont automatiquement initialisés à leurs valeurs par défaut.
Un constructeur par défaut (sans paramètre) pour une structure pourrait définir des valeurs différentes de l'état de remise à zéro, ce qui serait un comportement inattendu. Le runtime .NET interdit donc les constructeurs par défaut pour une structure.
la source
MyStruct s;
ne pas appeler le constructeur par défaut que vous avez fourni.Vous pouvez créer une propriété statique qui initialise et renvoie un nombre "rationnel" par défaut:
Et utilisez-le comme:
la source
Rational.Zero
pourrait être un peu moins déroutant.Explication plus courte:
En C ++, struct et class n'étaient que les deux faces d'une même médaille. La seule vraie différence est que l'un était public par défaut et l'autre privé.
Dans .NET , il existe une différence beaucoup plus grande entre une structure et une classe. L'essentiel est que struct fournit une sémantique de type valeur, tandis que class fournit une sémantique de type référence. Lorsque vous commencez à réfléchir aux implications de ce changement, d'autres changements commencent également à avoir plus de sens, y compris le comportement du constructeur que vous décrivez.
la source
new
doit vraiment être écrite pour appeler un constructeur. En C ++, les constructeurs sont appelés de manière cachée, lors de la déclaration ou de l'instanciation des tableaux. En C #, soit tout est un pointeur, alors commencez à null, soit c'est une structure et devez commencer à quelque chose, mais quand vous ne pouvez pas écrirenew
... (comme le tableau init), cela briserait une règle C # forte.Je n'ai pas vu d'équivalent à une solution tardive que je vais donner, alors voici.
utilisez les décalages pour déplacer les valeurs de 0 par défaut dans n'importe quelle valeur que vous aimez. ici, les propriétés doivent être utilisées au lieu d'accéder directement aux champs. (peut-être qu'avec la fonctionnalité c # 7 possible, vous feriez mieux de définir les champs de portée de propriété afin qu'ils restent protégés contre l'accès direct dans le code.)
Cette solution fonctionne pour les structures simples avec uniquement des types de valeur (pas de type ref ou de structure nullable).
Ceci est différent de cette réponse, cette approche n'est pas un boîtier spécial mais son utilisation de l'offset qui fonctionnera pour toutes les gammes.
exemple avec des énumérations comme champ.
Comme je l'ai dit, cette astuce peut ne pas fonctionner dans tous les cas, même si struct n'a que des champs de valeur, vous seul savez que cela fonctionne dans votre cas ou non. il suffit d'examiner. mais vous avez l'idée générale.
la source
Juste cas spécial. Si vous voyez un numérateur de 0 et un dénominateur de 0, faites comme s'il avait les valeurs que vous voulez vraiment.
la source
Nullable<T>
(par exempleint?
).new Rational(x,y)
où x et y se trouvent être 0?Ce que j'utilise est l' opérateur de coalescence nulle (??) combiné avec un champ de support comme celui-ci:
J'espère que cela t'aides ;)
Remarque: l' affectation de coalescence nulle est actuellement une proposition de fonctionnalité pour C # 8.0.
la source
Vous ne pouvez pas définir un constructeur par défaut car vous utilisez C #.
Les structures peuvent avoir des constructeurs par défaut dans .NET, bien que je ne connaisse aucun langage spécifique qui le supporte.
la source
Voici ma solution au dilemme du constructeur par défaut. Je sais que c'est une solution tardive, mais je pense qu'il vaut la peine de noter que c'est une solution.
ignorant le fait que j'ai une structure statique appelée null, (Remarque: Ceci est pour tous les quadrants positifs uniquement), en utilisant get; set; en C #, vous pouvez avoir un try / catch / enfin, pour traiter les erreurs où un type de données particulier n'est pas initialisé par le constructeur par défaut Point2D (). Je suppose que cela est insaisissable comme solution à certaines personnes sur cette réponse. C'est surtout pourquoi j'ajoute le mien. L'utilisation de la fonctionnalité getter et setter en C # vous permettra de contourner ce constructeur par défaut non-sens et de tenter de rattraper ce que vous n'avez pas initialisé. Pour moi, cela fonctionne bien, pour quelqu'un d'autre, vous voudrez peut-être ajouter des instructions if. Donc, dans le cas où vous souhaiteriez une configuration Numérateur / Dénominateur, ce code pourrait vous aider. Je voudrais simplement répéter que cette solution n'a pas l'air bien, fonctionne probablement encore pire du point de vue de l'efficacité, mais, pour quelqu'un provenant d'une ancienne version de C #, l'utilisation de types de données de tableau vous donne cette fonctionnalité. Si vous voulez juste quelque chose qui fonctionne, essayez ceci:
la source
la source