La règle générale à suivre est que les structures doivent être de petites collections simples (à un niveau) de propriétés connexes, immuables une fois créées; pour toute autre chose, utilisez une classe.
C # est bien en ce que les structures et les classes n'ont pas de différences explicites dans la déclaration autres que le mot-clé de définition; ainsi, si vous pensez que vous devez "mettre à niveau" une structure vers une classe, ou inversement, "rétrograder" une classe sur une structure, il suffit généralement de changer le mot clé (il existe quelques autres pièges; les structures ne peuvent pas dériver de toute autre classe ou type de structure, et ils ne peuvent pas définir explicitement un constructeur par défaut sans paramètre).
Je dis "principalement", car le plus important à savoir sur les struct est que, comme il s'agit de types de valeur, les traiter comme des classes (types de référence) peut s'avérer pénible. En particulier, rendre les propriétés d'une structure mutables peut entraîner un comportement inattendu.
Par exemple, supposons que vous ayez une classe SimpleClass avec deux propriétés, A et B. Vous instanciez une copie de cette classe, initialisez A et B, puis transmettez l'instance à une autre méthode. Cette méthode modifie davantage A et B. De retour dans la fonction appelante (celle qui a créé l'instance), A et B de votre instance auront les valeurs que leur donne la méthode appelée.
Maintenant, vous en faites une structure. Les propriétés sont toujours modifiables. Vous effectuez les mêmes opérations avec la même syntaxe qu'auparavant, mais maintenant, les nouvelles valeurs de A et B ne sont plus dans l'instance après l'appel de la méthode. Qu'est-il arrivé? Eh bien, votre classe est maintenant une structure, ce qui signifie que c'est un type valeur. Si vous transmettez un type de valeur à une méthode, la valeur par défaut (sans mot-clé out ou ref) est de passer "par valeur"; une copie superficielle de l'instance est créée pour être utilisée par la méthode, puis détruite lorsque la méthode est terminée en laissant l'instance initiale intacte.
Cela devient encore plus déroutant si vous aviez un type de référence en tant que membre de votre structure (non pas interdit, mais extrêmement mauvais dans pratiquement tous les cas); la classe ne serait pas clonée (uniquement la référence de la structure à celle-ci), ainsi les modifications apportées à la structure n'affecteront pas l'objet d'origine, mais les modifications apportées à la sous-classe de la structure affecteront l'instance à partir du code appelant. Cela peut très facilement placer des structures mutables dans des états très incohérents pouvant causer des erreurs très loin du problème réel.
Pour cette raison, pratiquement toutes les autorités de C # ont pour principe de toujours rendre vos structures immuables; permet au consommateur de spécifier les valeurs des propriétés uniquement lors de la construction d'un objet et ne fournit jamais aucun moyen de modifier les valeurs de cette instance. Les champs en lecture seule, ou les propriétés get-only, sont la règle. Si le consommateur souhaite modifier la valeur, il peut créer un nouvel objet en fonction des valeurs de l'ancien, avec les modifications souhaitées, ou appeler une méthode qui en fera de même. Cela les oblige à traiter une seule instance de votre structure comme une "valeur" conceptuelle, indivisible et distincte de toutes les autres (mais éventuellement équivalente). S'ils effectuent une opération sur une "valeur" stockée par votre type, ils obtiendront une nouvelle "valeur" différente de leur valeur initiale.
Pour un bon exemple, regardez le type DateTime. Vous ne pouvez affecter aucun des champs d'une instance DateTime directement; vous devez soit en créer une nouvelle, soit appeler une méthode sur la méthode existante qui produira une nouvelle instance. En effet, une date et une heure sont une "valeur", comme le nombre 5, et une modification du nombre 5 entraîne une nouvelle valeur qui n'est pas 5. Juste parce que 5 + 1 = 6 ne veut pas dire que 5 est maintenant 6 parce que vous y avez ajouté 1. DateTimes fonctionnent de la même manière; 12:00 ne "devient" pas 12:01 si vous ajoutez une minute, vous obtenez une nouvelle valeur 12:01 qui est différente de 12:00. S'il s'agit d'un état logique pour votre type (de bons exemples conceptuels qui ne sont pas intégrés à .NET sont Money, Distance, Weight, etc., où les opérations doivent prendre en compte toutes les parties de la valeur), utilisez ensuite une structure et concevez-la en conséquence. Dans la plupart des autres cas où les sous-éléments d'un objet doivent être modifiables indépendamment, utilisez une classe.