Pourquoi ne puis-je pas définir un constructeur par défaut pour une structure dans .NET?

Dans .NET, un type de valeur (C # struct ) ne peut pas avoir de constructeur sans paramètre. Selon cet article, cela est requirejs par la spécification de la CLI. Qu’est-ce qui se passe 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 sortingviale 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 très cool.

PS : les parameters par défaut aideront-ils à résoudre ce problème pour C # 4.0 ou le constructeur par défaut défini par le CLR sera-t-il appelé?


Jon Skeet a répondu:

Pour utiliser votre exemple, que voudriez-vous qu’il se passe lorsque quelqu’un a fait:

  Rational[] fractions = new Rational[1000]; 

Doit-il parcourir votre constructeur 1000 fois?

Bien sûr que oui, 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 lorsque aucun constructeur par défaut explicite n’est défini; De cette façon, vous ne payez que pour ce que vous utilisez. Alors si je veux un conteneur de 1000 Rational s par défaut (et que je veuille optimiser les 1000 constructions), je vais utiliser un List 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.

Note: 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 elles ne seront toujours pas appelées dans toutes les situations (par exemple pour la création de tableaux) 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 valeur d’avoir des constructeurs sans paramètre, mais pas C #. Je crois que c’est parce que cela introduirait une attente que le constructeur serait appelé quand il ne le ferait pas. Par exemple, considérez ceci:

 MyStruct[] foo = new MyStruct[1000]; 

Le CLR est capable de le faire très efficacement en allouant la mémoire appropriée et en la mettant à zéro. S’il devait exécuter le constructeur MyStruct 1000 fois, ce serait beaucoup moins efficace. (En fait, ce n’est pas le cas – si vous avez un constructeur sans paramètre, il ne s’exécute 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 compter sur aucune initialisation”. Maintenant, ils auraient pu autoriser la définition de constructeurs sans parameters, mais ils n’avaient pas besoin d’exécuter ce constructeur dans tous les cas, mais cela aurait entraîné davantage de confusion. (Ou du moins, je crois que l’argument va.)

EDIT: Pour utiliser votre exemple, que voudriez-vous faire quand quelqu’un a fait:

 Rational[] fractions = new Rational[1000]; 

Doit-il parcourir votre constructeur 1000 fois?

  • Sinon, on se retrouve avec 1000 rationals invalides
  • Si c’est le cas, alors nous avons potentiellement gaspillé une charge de travail si nous sums sur le sharepoint remplir le tableau avec des valeurs réelles.

EDIT: (Répondre à un peu plus de la question) Le constructeur sans paramètre n’est pas créé par le compilateur. Les types de valeur n’ont pas besoin de constructeurs en ce qui concerne le CLR – même si cela s’avère possible si vous l’écrivez dans IL. Lorsque vous écrivez ” new Guid() ” dans C # qui émet une IL différente 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 parameters. Il ne fait aucun doute que NDepend pourrait me dire si je le lui ai assez bien dit. Le fait que C # l’interdit est un indice assez important pour que je pense que c’est probablement une mauvaise idée.

Une structure est un type de valeur et un type de valeur doit avoir une valeur par défaut dès qu’elle est déclarée.

 MyClass m; MyStruct m2; 

Si vous déclarez deux champs comme ci-dessus sans instancier non plus, alors brisez le débogueur, m sera nul mais m2 ne le sera pas. Compte tenu de cela, un constructeur sans paramètre n’aurait aucun sens. En fait, tout constructeur sur une structure atsortingbue des valeurs, la chose elle-même existe déjà en la déclarant. En effet, m2 pourrait très bien être utilisé dans l’exemple ci-dessus et ses méthodes appelées, le cas échéant, et ses champs et propriétés sont manipulés!

Vous pouvez créer une propriété statique qui initialise et renvoie un nombre “rationnel” par défaut:

 public static Rational One => new Rational(0, 1); 

Et l’utiliser comme:

 var rat = Rational.One; 

Explication plus courte:

En C ++, struct et class n’étaient que les deux faces d’une même pièce. 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 fournisse une sémantique de type valeur, alors que class fournit une sémantique de type référence. Lorsque vous commencez à penser aux implications de ce changement, d’autres modifications commencent à avoir plus de sens, y compris le comportement du constructeur que vous décrivez.

Bien 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 un appel au constructeur par défaut. Donc, même si vous avez défini 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 raison principale 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 besoin d’instancier votre structure avec le new mot clé. Au lieu de cela, il fonctionne comme un int; vous pouvez y accéder directement.

Les structures ne peuvent pas contenir de constructeurs explicites sans paramètre. Les membres Struct 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 celles de l’état entièrement zéro, ce qui serait un comportement inattendu. Le runtime .NET interdit donc les constructeurs par défaut pour une structure.

Juste un cas spécial Si vous voyez un numérateur de 0 et un dénominateur de 0, faites comme si vous aviez les valeurs souhaitées.

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.

Voici ma solution au dilemme constructeur par défaut. Je sais que c’est une solution tardive, mais je pense qu’il est intéressant de noter que c’est une solution.

 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( AX + BX , AZ + BZ ); } public static Point2D operator -( Point2D A , Point2D B ) { return new Point2D( AX - BX , AZ - BZ ); } public static Point2D operator *( Point2D A , int B ) { return new Point2D( B * AX , B * AZ ); } public static Point2D operator *( int A , Point2D B ) { return new Point2D( A * BZ , A * BZ ); } public override ssortingng ToSsortingng() { return ssortingng.Format( "({0},{1})" , this.X , this.Z ); } } 

en ignorant le fait que j’ai une structure statique appelée null, (Remarque: Ceci est pour tout quadrant positif uniquement), en utilisant get; set; en C #, vous pouvez avoir un try / catch / finally 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 c’est une solution difficile pour certaines personnes sur cette réponse. C’est surtout pourquoi j’ajoute le mien. L’utilisation de la fonctionnalité getter et setter dans C # vous permettra de contourner ce constructeur par défaut et de tester ce que vous n’avez pas initialisé. Pour moi, cela fonctionne bien, pour quelqu’un d’autre, vous voudrez peut-être append des déclarations if. Ainsi, dans le cas où vous voudriez une configuration de numérateur / dénominateur, ce code pourrait aider. Je voudrais juste réitérer que cette solution ne semble pas belle, fonctionne probablement encore pire du sharepoint vue de l’efficacité, mais, pour quelqu’un venant d’une version plus ancienne de C #, l’utilisation des types de données de tableau vous donne cette fonctionnalité. Si vous voulez juste quelque chose qui fonctionne, essayez ceci:

 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; } } 

Je n’ai pas vu l’équivalent d’une solution tardive que je vais donner, alors la voici.

utilisez les décalages pour déplacer les valeurs de 0 par défaut vers n’importe quelle valeur. ici les propriétés doivent être utilisées au lieu d’accéder directement aux champs. (peut-être avec une fonctionnalité c # 7 possible, vous définissez mieux les champs de propriété pour qu’ils restnt protégés contre l’access direct dans le code.)

Cette solution fonctionne pour des structures simples avec uniquement des types de valeur (pas de type ref ou de structure nullable).

 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; } } 

Ceci est différent de cette réponse, cette approche n’est pas un cas particulier, mais son utilisation de décalage qui fonctionnera pour toutes les gammes.

exemple avec enums comme champ.

 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; } } 

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 si cela fonctionne dans votre cas ou non. juste examiner. mais vous avez l’idée générale.

 public struct Rational { private long numerator; private long denominator; public Rational(long num = 0, long denom = 1) // This is allowed!!! { numerator = num; denominator = denom; } }