Faire un cycle dans la structure qui n’existe pas

Ceci est une version simplifiée de certains de mes codes:

public struct info { public float a, b; public info? c; public info(float a, float b, info? c = null) { this.a = a; this.b = b; this.c = c; } } 

Le problème est que l’erreur Struct member 'info' causes a cycle in the struct layout. Je suis après struct comme le comportement de type valeur. Je pourrais simuler ceci en utilisant une classe et une fonction membre clone, mais je ne vois pas pourquoi je devrais en avoir besoin.

Comment cette erreur est-elle vraie? La récursion pourrait peut-être causer la construction pour toujours dans des situations similaires, mais je ne vois pas comment cela pourrait arriver dans ce cas. Vous trouverez ci-dessous des exemples qui devraient convenir si le programme comstack.

 new info(1, 2); new info(1, 2, null); new info(1, 2, new info(3, 4)); 

modifier:

La solution que j’ai utilisée a été de faire “info” une classe au lieu d’une structure et de lui donner une fonction membre pour renvoyer une copie que j’ai utilisée lors de sa transmission. En effet, simuler le même comportement qu’une structure mais avec une classe.

J’ai également créé la question suivante en cherchant une réponse.

Définition de classe de type de valeur en C #?

Ce n’est pas légal d’avoir une structure qui se contient en tant que membre. En effet, une structure a une taille fixe et doit être au moins aussi grande que la sum des tailles de chacun de ses membres. Votre type doit avoir 8 octets pour les deux flottants, au moins un octet pour indiquer si les info sont non info ou non, plus la taille d’une autre info . Cela donne l’inégalité suivante:

  size of info >= 4 + 4 + 1 + size of info 

Ceci est évidemment impossible car cela exigerait que votre type soit infiniment grand.

Vous devez utiliser un type de référence (c.-à-d. Classe). Vous pouvez rendre votre classe immuable et remplacer Equals et GetHashCode pour donner un comportement semblable à la valeur, similaire à la classe Ssortingng .

La raison pour laquelle cela crée un cycle est que Nullable est lui-même une struct . Parce qu’il renvoie à info vous avez un cycle dans la présentation ( info a un champ Nullable et il a un champ info ). C’est essentiellement équivalent à ce qui suit

 public struct MyNullable { public T value; public bool hasValue; } struct info { public float a, b; public MyNullable next; } 

Le vrai problème est sur cette ligne:

 public info? c; 

Puisqu’il s’agit d’une struct , C # a besoin de connaître la présentation des info internes avant de pouvoir produire la présentation des info externes. Et les info internes incluent des info internes, qui à leur tour incluent des info internes, et ainsi de suite. Le compilateur ne peut pas produire de mise en page à cause de ce problème de référence circulaire.

Note: info? c info? c est un raccourci pour Nullable qui est lui-même une struct .

Il n’existe aucun moyen d’obtenir une sémantique de valeur variable d’éléments de taille variable (je pense que vous voulez que MyInfo1 = MyInfo2 génère une nouvelle liste chaînée qui est détachée de celle lancée par MyInfo2). On pourrait remplacer l’ info? avec un info[] (qui serait toujours soit nul, soit rempli avec un tableau à un seul élément), ou avec une classe de titulaire qui encapsule une instance d’ info , mais la sémantique ne serait probablement pas ce que vous recherchez. Après MyInfo1 = MyInfo2 , les modifications apscopes à MyInfo1.a n’affecteraient pas MyInfo2.a , ni les modifications apscopes à MyInfo1.c n’affecteraient pas MyInfo2.c , mais les modifications apscopes à MyInfo1.c[0].a auraient une incidence sur MyInfo2.c[0].a .

Ce serait bien si une version future de .net pouvait avoir un concept de “références de valeur”, de sorte que la copie d’une structure ne copie pas simplement tous ses champs. Le fait que .net présente une certaine valeur n’est pas compatible avec toutes les subtilités des constructeurs de copie C ++, mais il serait également utile de permettre aux emplacements de stockage de type ‘struct’ d’avoir une identité qui serait associée à l’emplacement de stockage plutôt qu’à son contenu.

Étant donné que .net ne supporte actuellement aucun de ces concepts, cependant, si vous voulez que les info soient mutables, vous devrez supporter soit la sémantique de référence mutable (y compris le clonage protecteur), soit la bizarre et délirante struct-class- sémantique hybride. Une suggestion que j’aurais si la performance est une préoccupation serait d’avoir une classe abstraite InfoBase avec les descendants MutableInfo et MutableInfo , et avec les membres suivants:

  1. AsNewFullyMutableAsNewFullyMutable publique – Retourne un nouvel object MutableInfo , avec les données copiées à partir de l’original, appelant AsNewFullyMutable sur toutes les références nestedes.

  2. AsNewMutableAsNewMutable publique – Retourne un nouvel object MutableInfo , avec des données copiées à partir de l’original, appelant AsImmutable sur toutes les références nestedes.

  3. AsNewImmutableAsNewImmutable protégée – Retourne un nouvel object ImmutableInfo , avec les données copiées à partir de l’origine, appelant AsImmutable (pas AsNewImmutable ) sur les références nestedes.

  4. AsImmutable – Public virtual – Pour un ImmutableInfo , retourne lui-même; pour un MutableInfo , appelez AsNewImmutable sur lui-même.

  5. AsMutable – Public virtuel – Pour un MutableInfo , renvoie lui-même; pour un ImmutableInfo , appelez AsNewMutable sur lui-même.

Lors du clonage d’un object, on peut appeler AsImmutable , AsNewFullyMutable ou AsNewMutable , selon que l’on s’attend à ce que l’object ou ses descendants soient à nouveau clonés avant d’être mutés. Dans les scénarios dans lesquels on s’attendrait à ce qu’un object soit cloné de manière défensive de manière répétée, l’object serait remplacé par une instance immuable qui ne devrait plus être clonée tant que personne ne souhaitait le muter.