2010-11-17 27 views
3

J'ai un problème en utilisant une classe de structures.Les structures dynamiques échouent

est ici la définition de base:

using System; 

struct Real 
{ 
    public double real; 

    public Real(double real) 
    { 
     this.real = real; 
    } 
} 

class Record 
{ 
    public Real r; 
    public Record(double r) 
    { 
     this.r = new Real(r); 
    } 
    public void Test(double origval, double newval) 
    { 
     if (this.r.real == newval) 
      Console.WriteLine("r = newval-test passed\n"); 
     else if (this.r.real == origval) 
      Console.WriteLine("r = origval-test failed\n"); 
     else 
      Console.WriteLine("r = neither-test failed\n"); 
    } 
} 

Lorsque je crée une dynamique non-enregistrement, la définition des travaux réels (statique?).
Lorsque je crée un enregistrement dynamique, le réglage du réel ne fonctionne pas.
Lorsque je crée un enregistrement dynamique, je remplace les travaux réels.

Et voici le programme de test

class Program 
{ 
    static void Main(string[] args) 
    { 
     double origval = 8.0; 
     double newval = 5.0; 

     // THIS WORKS - create fixed type Record, print, change value, print 
     Record record1 = new Record(origval); 
     record1.r.real = newval;  // change value *** 
     record1.Test(origval, newval); 

     // THIS DOESN'T WORK. change value is not making any change! 
     dynamic dynrecord2 = new Record(origval); 
     dynrecord2.r.real = newval;  // change value 
     dynrecord2.Test(origval, newval); 

     // THIS WORKS - create dynamic type Record, print, change value, print 
     dynamic dynrecord3 = new Record(origval); 
     dynamic r = dynrecord3.r;  // copy out value 
     r.real = newval;    // change copy 
     dynrecord3.r = r;    // copy in modified value 
     dynrecord3.Test(origval, newval); 

    } 
} 

Et voici la sortie: r = newval test passé r = origval-test a échoué r = newval-test a réussi

Quand je change la struct Real à la classe Real, les trois cas fonctionnent.

Alors, que se passe-t-il?
Merci,
Max

+3

Règle de base; ** ne jamais faire une structure mutable **. Même si vous pensez que c'est sain d'esprit, ce n'est probablement pas le cas. –

+0

Je devrais aussi ajouter; Les champs publics sont aussi un problème: votre code se comportera différemment si vous passez aux propriétés, ce qui est regrettable et à éviter. –

+0

Ce code n'est évidemment pas destiné à la production. Les champs publics sont juste pour dépouiller la représentation la plus simple du problème. –

Répondre

1

dynamic est vraiment un mot pour object jusqu'à la CLI de base est concerné, de sorte que vous mutent une copie en boîte. C'est sujet à la folie. Mutation d'une structure en premier lieu est vraiment, vraiment sujette à l'erreur. Je voudrais simplement rendre la structure immuable - sinon vous allez obtenir ce encore et encore.

+0

J'ai vu cette explication mais je ne l'achète pas. Qu'est-ce qu'un struct mais un type POD? Ne pas être en mesure de définir une valeur dans un champ struct dans une classe est comme dire que vous ne pouvez pas définir un champ int dans une classe. Ce code est utilisé pour gérer une acquisition de données très rapide. La surcharge de l'allocation de tas de chaque champ dans le dossier est inacceptable. –

+0

@Max - avez-vous réellement * mesuré * cela? L'allocation de tas est également assez rapide (et si elles sont de courte durée, la collecte est également peu coûteuse). Les structures ne sont pas ** simplement ** POD; ils sont destinés à ** valeurs ** - et une * valeur * ne change jamais. Bien sûr, si vous laissez tomber le 'dynamic', cela fonctionnera, ** et sera plus rapide **, donc si * performance * est votre objectif, vous vous êtes déjà tiré dans le pied. Mais finalement, c'est ce point lorsque (via dynamic) la structure est encadrée (à '.r') qui provoque l'échec. –

+0

@Max - pour le rendre explicite: vous dites que vous ne voulez pas allouer sur le tas, mais finalement vous utilisez 'dynamic'; cela est ** allocation de tas **, puisque 'dynamic' fonctionne contre' object'. Cela le met en boîte, et vous avez perdu ce que vous visiez. L'utilisation d'une interface (sauf avec une contrainte générique/d'interface) entraînerait ** aussi ** la boxe. –

0

J'ai creusé un peu plus profondément dans ce problème. Voici une réponse de Mads Torgersen de Microsoft.


De Mads:

C'est un peu regrettable, mais par la conception. Dans

dynrecord2.r.real = newval; // change value 

La valeur de dynrecord2.r est encadrée, ce qui signifie qu'elle est copiée dans son propre objet segment. Cette copie est celle qui est modifiée, pas l'original que vous testez par la suite.

Ceci est une conséquence de la manière très "locale" dans laquelle C# dynamic fonctionne. Pensez à une déclaration comme ci-dessus - il y a deux façons fondamentales que nous pourrions attaquer que:

1) Rendez-vous compte au moment de la compilation que quelque chose de dynamique qui se passe, et déplacer essentiellement toute la déclaration à être lié à l'exécution

2) Lier des opérations individuelles à l'exécution lorsque leurs constituants sont dynamiques, en retournant quelque chose de dynamique qui peut à son tour faire que les choses soient liées à l'exécution

En C# nous sommes allés avec ce dernier, qui est joliment compositionnel, et le rend facile à décrire dynamique en termes de système de type, mais a quelques inconvénients - tels que la boxe des types de valeur résultants par exemple.

Ce que vous voyez est le résultat de ce choix de conception.


J'ai jeté un autre regard sur le MSIL. Il faut essentiellement

dynrecord2.r.real = newval; 

et il se transforme en:

Real temp = dynrecord2.r; 
temp.real = newval; 

Si dynrecord2.r est une classe, il se contente de recopier la poignée de sorte que le changement affecte le champ interne. Si dynrecord2.r est une structure, une copie est faite et la modification n'affecte pas l'original.

Je laisserai le soin au lecteur de décider s'il s'agit d'un bug ou d'une fonctionnalité.

Max

+0

Toute introduction décente le C# /. Net indique que la conversion d'un valuetype en 'object' le place dans une boîte. Un bon programmeur C# vous dit que vous ne devriez pas utiliser des structures mutables. Si vous faites une structure mutable, vous savez mieux ce que vous faites. – CodesInChaos

+0

J'ai lu ceci deux fois, et pour autant que je sache, il n'y a rien ici qui n'était pas déjà dans la réponse et les commentaires que j'ai donnés plus tôt. –

+0

C'est pourquoi je vous ai coche. –

0

Faites votre struct immuable et vous ne serez pas avoir des problèmes.

struct Real 
{ 
    private double real; 
    public double Real{get{return real;}} 

    public Real(double real) 
    { 
     this.real = real; 
    } 
} 

struct mutables peuvent être utiles dans Interop natif ou des scénarios de haute performance, mais vous feriez mieux de savoir ce que vous faites.

+0

C'est exactement le problème. J'ai une application d'acquisition de données à haut débit. Il se peut que je doive le coder en C++/CLI ou même en vider .Net. En ce qui concerne ce que je suis en train de faire, je vais y arriver. –