Pourquoi ne puis-je pas définir un constructeur par défaut pour une struct in.NET Je ne sais pas.

dans .NET, un type de valeur (C# struct ) ne peut pas avoir de constructeur sans paramètres. Selon ce post ceci est prescrit par la spécification CLI. Que se passe-t-il si pour chaque type de valeur un constructeur par défaut est créé (par le compilateur?) qui initialisé tous les membres à zéro (ou null ).

Pourquoi est-il interdit de définir un tel constructeur par défaut?

une utilisation insignifiante est pour le rationnel numéros:

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 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 arrive quand quelqu'un l'a fait:

 Rational[] fractions = new Rational[1000];

doit-il passer par votre constructeur 1000 fois?

bien sûr qu'il devrait, c'est pourquoi j'ai écrit le constructeur par défaut en premier lieu. Le CLR devrait utiliser le constructeur par défaut lorsqu'aucun constructeur par défaut explicite n'est défini; de cette façon, vous ne payez que pour ce que vous utilisez. Puis si je veux un conteneur de 1000 non-défaut Rational s (et que je veux optimiser loin des 1000 constructions), je vais utiliser un List<Rational> plutôt qu'un tableau.

Cette raison, dans mon esprit, n'est pas suffisamment forte pour empêcher la définition d'un constructeur par défaut.

210
demandé sur Community 2008-12-02 15:39:25

10 réponses

Note: la réponse ci - dessous a été écrite longtemps avant C# 6, qui prévoit d'introduire la capacité de déclarer les constructeurs paramétrés dans les structures-mais ils ne seront toujours pas appelés dans toutes les situations (par exemple pour la création de tableau) (à la fin cette fonctionnalité n'a pas été ajoutée à C# 6 ).


modifier: j'ai modifié 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 serait appelé alors qu'il ne le serait pas. Par exemple, considérez ceci:

MyStruct[] foo = new MyStruct[1000];

le CLR est capable de faire cela très efficacement juste 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, ce n'est pas le cas - si vous do ont 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 dans C# est"la valeur par défaut pour n'importe quel type ne peut pas compter sur n'importe quelle initialisation". Maintenant, ils pourrait ont permis de définir des constructeurs sans paramètres, mais alors pas exigé que le constructeur soit exécuté dans tous les cas - mais cela aurait conduit à plus de confusion. (Du moins, je crois que l'argument est valable.)

EDIT: pour utiliser votre exemple, que voudriez-vous arriver quand quelqu'un le ferait:

Rational[] fractions = new Rational[1000];

doit-il passer par votre constructeur 1000 fois?

  • si ce n'est pas le cas, nous nous retrouvons avec 1000 rationaux invalides
  • S'il le fait, alors nous avons potentiellement gaspillé une charge de travail si nous sommes sur le point de remplir le tableau avec les 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 valeurs n'ont pas à avoir de constructeur en ce qui concerne le CLR - bien qu'il s'avère qu'il peut si vous l'écrivez dans IL. Quand vous écrivez " new Guid() " dans C# qui émet IL différent de ce que vous obtenez si vous appelez un constructeur normal. Voir cette question SO pour un peu plus sur cette aspect.

I suspect qu'il n'y a aucun type de valeur dans le cadre avec des constructeurs sans paramètres. NDepend pourrait me le dire si je le demandais gentiment... Le fait que C# interdise c'est un indice assez grand pour que je pense que c'est probablement une mauvaise idée.

167
répondu Jon Skeet 2017-05-23 12:26:23

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 instanciation non plus, alors cassez le débogueur, m sera null mais m2 ne le sera pas. Étant donné cela, un constructeur sans paramétrage n'aurait aucun sens, en fait tout constructeur sur une structure n'attribue que des valeurs, la chose elle-même existe déjà simplement en la déclarant. En effet m2 pourrait tout à fait heureusement ê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ées!

36
répondu user42467 2014-10-10 14:36:01

vous pouvez faire 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;
14
répondu AustinWBryan 2018-07-15 02:44:41

plus Courte explication:

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 est public par défaut et l'autre privé.

Dans .NET , il y a plus de différence 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 à penser aux conséquences de ce changement, d'autres changements commencent à avoir plus de sens, y compris le comportement du constructeur que vous décrivez.

13
répondu Joel Coehoorn 2013-03-06 19:56:08

bien que le CLR le permette, C# ne permet pas aux structures d'avoir un constructeur par défaut sans paramètre. La raison en est que, pour un type de valeur, les compilateurs par défaut ne génèrent ni un constructeur par défaut, ni un appel au constructeur par défaut. Donc, même si vous arrivé à définir un constructeur par défaut, il ne sera pas appelé, et qui ne vous confondre.

pour éviter de tels problèmes, le compilateur C # refuse la définition d'un constructeur par défaut par 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 les types de valeur sont initialisés par une valeur par défaut et le constructeur est utilisé pour l'initialisation.

vous n'avez pas à instancier votre struct avec le mot-clé new . Il fonctionne à la place comme un int; vous pouvez y accéder directement.

les structures ne peuvent pas contiennent des constructeurs explicites sans paramètres. Les membres de Struct sont automatiquement initialisés à leurs valeurs par défaut.

un constructeur par défaut (sans paramètre) pour une struct pourrait définir des valeurs différentes de celles de l'état entièrement zéro qui serait un comportement inattendu. L'exécution. net interdit donc les constructeurs par défaut pour une structure.

11
répondu Adiii 2015-10-22 07:08:58

juste au cas particulier. Si vous voyez un numérateur de 0 et un dénominateur de 0, faites comme si il avait les valeurs que vous voulez vraiment.

3
répondu Jonathan Allen 2008-12-03 07:59:06

vous ne pouvez pas définir un constructeur par défaut parce que vous utilisez C#.

Les structures

peuvent avoir des constructeurs par défaut dans .NET, même si Je ne connais aucun langage spécifique qui le supporte.

1
répondu Jonathan Allen 2008-12-03 19:53:48

voici ma solution au dilemme du constructeur sans 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.

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

ignorant le fait que j'ai une structure statique appelée null, (Note: C'est pour tous les quadrant positifs seulement), 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 insaisissable comme un solution pour certaines personnes sur cette réponse. C'est surtout pour ça que j'ajoute le mien. L'utilisation des fonctionnalités getter et setter dans C# vous permettra de contourner ce constructeur par défaut non-sense et de mettre un try catch autour de ce que vous n'avez pas initialisé. Pour moi cela fonctionne très bien, pour quelqu'un d'autre que vous pourriez vouloir ajouter quelques si déclarations. Donc, dans le cas où vous voudriez un numérateur/dénominateur, ce code pourrait aider. Je voudrais juste rappeler que cette solution n'a pas l'air sympa, fonctionne probablement pire encore du point de vue de l'efficacité, mais, pour quelqu'un venant d'une version plus ancienne de C#, en utilisant les 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;
    }
}
1
répondu G1xb17 2016-11-13 19:20:12

Je n'ai pas vu l'équivalent de la solution tardive que je vais donner, donc voilà.

utilise les offsets pour déplacer les valeurs par défaut 0 dans n'importe quelle valeur que vous aimez. ici, les propriétés doivent être utilisés au lieu d'accéder directement aux champs. (peut-être qu'avec la fonctionnalité c#7 possible, vous pouvez mieux définir les champs de propriété scoped de sorte qu'ils restent protégés contre l'accès direct dans le code.)

cette solution fonctionne pour des structures simples avec seulement des types de valeur (pas de type ref ou nullable struct).

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

c'est différent que cette réponse, cette approche n'est pas un coffrage spécial mais son utilisation offset 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 valeurs, seulement vous savez que si cela fonctionne dans votre cas ou non. juste examiner. mais vous avez l'idée générale.

1
répondu M.kazem Akhgary 2017-09-27 17:31:39
public struct Rational 
{
    private long numerator;
    private long denominator;

    public Rational(long num = 0, long denom = 1)   // This is allowed!!!
    {
        numerator   = num;
        denominator = denom;
    }
}
0
répondu eMeL 2018-08-17 18:23:23