Les structures peuvent contenir des champs de types de référence

Les structures peuvent-elles contenir des champs de types de référence? Et s’ils le peuvent, est-ce une mauvaise pratique?

Oui, ils peuvent. Est-ce que c’est une bonne idée? Eh bien, cela dépend de la situation. Personnellement, je crée rarement mes propres structures en premier lieu … Je traiterais toute nouvelle structure définie par l’utilisateur avec un certain degré de scepticisme. Je ne dis pas que c’est toujours la mauvaise option, mais simplement qu’il faut plus d’un argument clair qu’une classe.

Ce serait une mauvaise idée pour une structure d’avoir une référence à un object mutable si … sinon vous pouvez avoir deux valeurs qui semblent indépendantes mais ne sont pas:

MyValueType foo = ...; MyValueType bar = foo; // Value type, hence copy... foo.List.Add("x"); // Eek, bar's list has now changed too! 

Les structures modulables sont mauvaises. Les structures immuables avec des références aux types mutables sont maléfiques de différentes manières.

Bien sûr et ce n’est pas une mauvaise pratique de le faire.

 struct Example { public readonly ssortingng Field1; } 

Le readonly n’est pas nécessaire mais c’est une bonne pratique de rendre les structures immuables.

Oui, c’est possible, et oui, c’est généralement une mauvaise pratique.

Si vous regardez le framework .NET lui-même, vous verrez que pratiquement toutes les structures contiennent des types de valeurs primitifs.

Oui, ils peuvent.

Ça dépend.

Beaucoup soutiennent qu’une structure doit être immuable et, dans ce cas, tenir une référence à un object peut signifier que ce n’est pas le cas.

Mais cela dépend de la situation.

La raison pour laquelle vous ne pouvez pas avoir de structures mutables est due au comportement des types de référence. Lisez cet article: http://www.yoda.arachsys.com/csharp/parameters.html

Lorsque vous avez une structure qui contient un object (tout ce qui n’est pas une primitive comme int ou double) et que vous copiez une instance de la structure, l’object Object n’est pas copié en profondeur, car il s’agit simplement d’une référence ) à un emplacement de mémoire contenant la classe réelle. Donc, si vous copiez une structure mutable contenant des instances de classe, la copie référencera les mêmes instances que l’original (la liste de la barre étant modifiée ci-dessus).

Si vous devez absolument faire en sorte que la structure soit mutable, créez des instances de classe en lecture seule, ou – c’est une mauvaise pratique – essayez de vous assurer que vous ne faites jamais de copie de la structure.

Étant donné que cela a reçu une défaite, j’essaie de réécrire un peu pour voir si cela peut devenir plus clair. Cette question est ancienne mais bon! J’ai aussi récemment rencontré quelques liens à ce sujet.

Le point que j’essaie d’append est que si vous déclarez des champs de référence, vous devez être capable de raisonner en dehors de votre propre bloc: quand quelqu’un utilise votre structure. Le point spécifique que j’ai ajouté concernait uniquement la déclaration d’un champ en lecture seule d’une structure; mais dans ce cas, les champs que vous avez dans votre structure peuvent modifier leurs résultats; et c’est difficile à comprendre.

Je suis tombé sur ce lien, où le programmeur a déclaré une classe contenant un champ struct readonly . Le champ dans sa classe est une struct — c’est un LinkedList.Enumerator – et il s’est cassé parce que le champ est en readonly – ses propres méthodes de classe obtiennent une copie de la structure enumerator et l’état est copié et non dynamic.

Mais si vous continuez à corriger son code en supprimant simplement le readonly du champ struct (qui fonctionne ); et cependant , si vous décidez ensuite de struct votre propre classe, un consommateur de votre struct ne peut pas encore l’utiliser comme champ en lecture seule, sinon il sera mordu par le même problème. (Et si cela semble artificiel parce que vous n’avez pas d’énumérateur en lecture seule, vous pouvez réellement le faire s’il prend en charge Reset!)

Donc, si ce n’est pas l’exemple le plus clair, ce que je veux dire, c’est que vous pouvez raisonner sur votre propre implémentation, mais si vous êtes une structure, vous devez également raisonner sur les consommateurs qui copient votre valeur et ce qu’ils obtiendront.

L’exemple que j’ai trouvé est lié ci-dessous.

Sa classe n’est pas une struct , mais contient le champ m_Enumerator (et le programmeur sait que c’est une struct ).

Il s’avère que les méthodes de cette classe obtiennent une copie de cette valeur et ne fonctionnent pas. — Vous pouvez effectivement examiner ce bloc très attentivement pour comprendre cela.

Vous pouvez résoudre ce problème en rendant le champ non readonly , ce qui readonly déjà de la confusion. Mais vous pouvez également résoudre le problème en déclarant le champ comme type d’ interfaceIEnumerator .

Mais si vous corrigez le problème en laissant le champ déclaré comme struct et ne le déclarez pas en readonly , puis choisissez de définir votre classe en tant que struct , alors si quelqu’un déclare une instance de votre struct tant que champ en readonly dans une classe, encore une fois ils perdent!

PAR EXEMPLE:

 public class Program { private struct EnumeratorWrapper : IEnumerator { // Fails always --- the local methods read the readonly struct and get a copy //private readonly LinkedList.Enumerator m_Enumerator; // Fixes one: --- locally, methods no longer get a copy; // BUT if a consumer of THIS struct makes a readonly field, then again they will // always get a copy of THIS, AND this contains a copy of this struct field! private LinkedList.Enumerator m_Enumerator; // Fixes both!! // Because this is not a value type, even a consumer of THIS struct making a // readonly copy, always reads the memory pointer and not a value //private IEnumerator m_Enumerator; public EnumeratorWrapper(LinkedList linkedList) => m_Enumerator = linkedList.GetEnumerator(); public int Current => m_Enumerator.Current; object System.Collections.IEnumerator.Current => Current; public bool MoveNext() => m_Enumerator.MoveNext(); public void Reset() => ((System.Collections.IEnumerator) m_Enumerator).Reset(); public void Dispose() => m_Enumerator.Dispose(); } private readonly LinkedList l = new LinkedList(); private readonly EnumeratorWrapper e; public Program() { for (int i = 0; i < 10; ++i) { l.AddLast(i); } e = new EnumeratorWrapper(l); } public static void Main() { Program p = new Program(); // This works --- a local copy every time EnumeratorWrapper e = new EnumeratorWrapper(pl); while (e.MoveNext()) { Console.WriteLine(e.Current); } // This fails if the struct cannot support living in a readonly field while (peMoveNext()) { Console.WriteLine(peCurrent); } Console.ReadKey(); } } 

Si vous déclarez une struct avec un champ d' interface , vous ne saurez pas ce que vous avez là-dedans, mais vous pouvez réellement en savoir plus sur ce que vous obtenez lorsque vous le référencez simplement! C'est très intéressant; mais seulement parce que le langage autorise autant de libertés avec une struct : il faut partir de quelque chose de très simple; et ajoutez seulement ce que vous pouvez concrètement raisonner!

Un autre point est que la référence indique également que vous devez définir les valeurs par défaut comme raisonnables; ce qui n'est pas possible avec un champ de référence! Si vous invoquez par inadvertance le constructeur par défaut, toujours possible avec une struct , vous obtenez une référence null.

Une dernière note aussi. De nombreuses personnes défendent des structures mutables et de grandes structures mutables. Mais si vous observez, vous découvrez généralement qu’ils ne font que définir ces objects d’une manière qui leur permet de raisonner de manière définitive sur les comportements, et que les structs ne s’écoulent pas dans les étendues où ces invariants pourraient changer.

... Trop de gens commencent à expliquer les structures comme "Tout comme une classe mais ... x, y, z, 1, 2, alpha, disco bêta". Cela doit être expliqué par quelques valeurs en lecture seule; période; sauf que maintenant que vous savez quelque chose, vous pouvez commencer à raisonner en ajoutant quelque chose!

L'exemple que je suis venu accros est ici:

https://www.red-gate.com/simple-talk/blogs/why-enumerator-structs-are-a-really-bad-idea/