Question

Il semble que je me souvienne d'avoir lu quelque chose sur le fait qu'il est mauvais pour les structures d'implémenter des interfaces dans le CLR via C #, mais je n'arrive pas à trouver quoi que ce soit à ce sujet. Est-il mauvais? Y at-il des conséquences inattendues de le faire?

public interface Foo { Bar GetBar(); }
public struct Fubar : Foo { public Bar GetBar() { return new Bar(); } }
Était-ce utile?

La solution

Il y a plusieurs choses qui se passent dans cette question ...

Il est possible pour une structure d'implémenter une interface, mais des problèmes de casting, de mutabilité et de performance se posent. Consultez ce message pour plus de détails: http: //blogs.msdn. com / abhinaba / archive / 2005/10/05 / 477238.aspx

En général, les structures doivent être utilisées pour les objets ayant une sémantique de type valeur. En implémentant une interface sur une structure, vous pouvez rencontrer des problèmes de boxe car la structure est convertie en va-et-vient entre la structure et l'interface. À la suite de la mise en boîte, les opérations qui modifient l’état interne de la structure peuvent ne pas se comporter correctement.

Autres conseils

Étant donné que personne d'autre n'a explicitement fourni cette réponse, j'ajouterai ce qui suit:

La mise en œuvre d'une interface sur une structure n'a aucune conséquence négative.

Toute variable du type d'interface utilisé pour contenir une structure entraînera l'utilisation d'une valeur encadrée de cette structure. Si la structure est immuable (une bonne chose), il s’agit au pire d’un problème de performances à moins que vous ne soyez:

  • utiliser l'objet résultant à des fins de verrouillage (une très mauvaise idée de toute façon)
  • utilise la sémantique d'égalité de référence et s'attend à ce qu'elle fonctionne pour deux valeurs encadrées de la même structure.

Ces deux options sont peu probables. Vous devrez probablement effectuer l'une des opérations suivantes:

Génériques

Peut-être que les structures d'implémentation d'interfaces ont une raison raisonnable de pouvoir les utiliser dans un contexte générique avec contraintes . Lorsqu'elle est utilisée de cette manière, la variable ressemble à ceci:

class Foo<T> : IEquatable<Foo<T>> where T : IEquatable<T>
{
    private readonly T a;

    public bool Equals(Foo<T> other)
    {
         return this.a.Equals(other.a);
    }
}
  1. Activer l'utilisation de la structure comme paramètre de type
    • tant qu'aucune autre contrainte comme new() ou class n'est utilisée.
  2. Permet d'éviter la boxe sur les structures utilisées de cette manière.

Alors this.a n'est PAS une référence d'interface, donc il ne provoque pas une boîte de ce qui y est placé. De plus, lorsque le compilateur c # compile les classes génériques et doit insérer des invocations des méthodes d’instance définies sur les occurrences du paramètre Type, il peut utiliser contraint : l'opcode:

  

Si thisType est un type valeur et que thisType implémente une méthode, ptr est transmis sans modification en tant que pointeur 'this' vers une instruction de méthode d'appel pour l'implémentation de méthode par thisType.

Ceci évite la boxe et puisque le type de valeur implémentant l'interface est obligée d'implémenter la méthode, aucune boxe ne se produira. Dans l'exemple ci-dessus, l'invocation Equals() est effectuée sans zone. This 1 .

API à faible frottement

La plupart des structures doivent avoir une sémantique de type primitive où les valeurs identiques au niveau du bit sont considérées comme égales 2 . Le runtime fournira un tel comportement dans l'implicite IEquatable<T> mais cela peut être lent. De plus, cette égalité implicite n'est pas exposée en tant qu'implémentation de T et empêche donc les structures d'être utilisées facilement en tant que clés pour les dictionnaires, à moins qu'elles ne l'implémentent explicitement elles-mêmes. Il est donc courant que de nombreux types de structures publiques déclarent qu'ils implémentent IComparable (où IConvertible sont eux-mêmes) pour rendre cela plus facile et plus performant, tout en restant cohérent avec le comportement de nombreux types de valeur existants dans la CLL BCL.

Toutes les primitives de la BCL implémentent au minimum:

  • IComparable<T>
  • IEquatable
  • IFormattable
  • ICollection (et donc Add())

Beaucoup implémentent également IDisposable, de plus, de nombreux types de valeur définis par le système, tels que DateTime, TimeSpan et Guid, implémentent également beaucoup d’entre eux. Si vous implémentez un type "très utile" similaire, comme une structure de nombre complexe ou des valeurs textuelles à largeur fixe, l'implémentation de plusieurs de ces interfaces communes (correctement) rendra votre structure plus utile et utilisable.

Exclusions

Évidemment, si l'interface implique fortement la mutabilité (telle que Dispose()), sa mise en oeuvre est une mauvaise idée car cela voudrait dire que vous devez soit rendre la structure mutable (conduisant au type d'erreur décrit). où les modifications se produisent déjà sur la valeur encadrée plutôt que sur l’original) ou vous confondez les utilisateurs en ignorant les implications de méthodes telles que <=> ou en levant des exceptions.

De nombreuses interfaces n'impliquent PAS de mutabilité (telle que <=>) et servent de moyen idiomatique d'exposer certaines fonctionnalités dansune mode cohérente. Souvent, l'utilisateur de la structure ne se soucie pas de la surcharge de la boxe pour un tel comportement.

Résumé

Lorsque cela est fait judicieusement, sur des types de valeurs immuables, la mise en oeuvre d'interfaces utiles est une bonne idée

Notes:

1: Notez que le compilateur peut utiliser cela lors de l'appel de méthodes virtuelles sur des variables connues d'un type de structure spécifique, mais dans lesquelles il est nécessaire d'appeler une méthode virtuelle. Par exemple:

List<int> l = new List<int>();
foreach(var x in l)
    ;//no-op

L'énumérateur renvoyé par la liste est une structure, une optimisation pour éviter une allocation lors de l'énumération de la liste (avec des informations intéressantes conséquences ). Cependant, la sémantique de foreach spécifie que si l'énumérateur implémente <=>, alors <=> sera appelé une fois l'itération terminée. Évidemment, cela se produirait via un appel encadré, ce qui éliminerait tout avantage que l’énumérateur soit une structure (en fait, ce serait pire). Pire, si l'appel dispose modifie l'état de l'énumérateur d'une manière ou d'une autre, cela se produirait dans l'instance encadrée et de nombreux bugs subtils pourraient être introduits dans des cas complexes. Par conséquent, l’IL émis dans ce genre de situation est:

IL_0001:  newobj      System.Collections.Generic.List..ctor
IL_0006:  stloc.0     
IL_0007:  nop         
IL_0008:  ldloc.0     
IL_0009:  callvirt    System.Collections.Generic.List.GetEnumerator
IL_000E:  stloc.2     
IL_000F:  br.s        IL_0019
IL_0011:  ldloca.s    02 
IL_0013:  call        System.Collections.Generic.List.get_Current
IL_0018:  stloc.1     
IL_0019:  ldloca.s    02 
IL_001B:  call        System.Collections.Generic.List.MoveNext
IL_0020:  stloc.3     
IL_0021:  ldloc.3     
IL_0022:  brtrue.s    IL_0011
IL_0024:  leave.s     IL_0035
IL_0026:  ldloca.s    02 
IL_0028:  constrained. System.Collections.Generic.List.Enumerator
IL_002E:  callvirt    System.IDisposable.Dispose
IL_0033:  nop         
IL_0034:  endfinally  

Ainsi, la mise en oeuvre d’IDisposable ne pose aucun problème de performances et l’aspect mutable (regrettable) de l’énumérateur est préservé si la méthode Dispose agit réellement!

2: double et float sont des exceptions à cette règle où les valeurs de NaN ne sont pas considérées comme égales.

Dans certains cas, il peut être judicieux qu'une structure implémente une interface (si elle n'avait jamais été utile, il est peu probable que les créateurs de .net l'aient prévu). Si une structure implémente une interface en lecture seule telle que IEquatable<T>, le stockage de la structure dans un emplacement de stockage (variable, paramètre, élément de tableau, etc.) de type CompareTwoThings<T>(T thing1, T thing2) where T:IComparable<T> nécessitera qu’elle soit encadrée (chaque type de structure en définit deux). types de choses: un type d'emplacement de stockage qui se comporte comme un type de valeur et un type d'objet tas qui se comporte comme un type de classe; le premier est implicitement convertible au second - & "; boxing &"; - et le second peut être converti au premier via une conversion explicite - & "; unboxing &"; Il est possible d’exploiter la structure d’une interface sans boxing, en utilisant ce que l’on appelle des génériques sous contraintes.

Par exemple, si on utilisait une méthode thing1.Compare(thing2), une telle méthode pourrait appeler thing1 sans avoir à encadrer thing2 ou Int32. Si CompareTwoThings<Int32>(Int32 thing1, Int32 thing2) se trouve être, par exemple, un Object, l'exécution le saura lorsqu'elle générera le code pour ValueType. Puisqu'il connaîtra le type exact de la chose hébergeant la méthode et de la chose transmise en tant que paramètre, il ne sera pas nécessaire de les encadrer.

Le plus gros problème avec les structures qui implémentent des interfaces est qu'une structure qui est stockée dans un emplacement de type interface, IEnumerator<T> ou enumerator1 (par opposition à un emplacement de son propre type) se comportera comme un objet de classe. . Cela n’est généralement pas un problème pour les interfaces en lecture seule, mais pour une interface en mutation telle que enumerator2, cela peut donner une sémantique étrange.

Considérons, par exemple, le code suivant:

List<String> myList = [list containing a bunch of strings]
var enumerator1 = myList.GetEnumerator();  // Struct of type List<String>.IEnumerator
enumerator1.MoveNext(); // 1
var enumerator2 = enumerator1;
enumerator2.MoveNext(); // 2
IEnumerator<string> enumerator3 = enumerator2;
enumerator3.MoveNext(); // 3
IEnumerator<string> enumerator4 = enumerator3;
enumerator4.MoveNext(); // 4

L’énoncé marqué n ° 1 amorcera enumerator3 la lecture du premier élément. L’état de cet énumérateur sera copié dans enumerator4. La déclaration marquée n ° 2 avancera cette copie pour lire le deuxième élément, mais n’affectera pas List<String>.Enumerator. L’état de ce deuxième énumérateur sera alors copié dans IEnumerator<String>, qui sera avancé par l’instruction marquée n ° 3. Ensuite, étant donné que Equals et System.Enum sont les deux types de référence, un REFERENCE vers <=> sera alors copié dans <=>. L'instruction marquée fera donc progresser les deux <=> et <=>.

Certaines personnes prétendent que les types de valeur et les types de référence sont deux types de <=>, mais ce n'est pas vraiment vrai. Les types de valeur réelle sont convertibles en <=>, mais n'en sont pas des instances. Une instance de <=> qui est stockée dans un emplacement de ce type est un type de valeur et se comporte comme un type de valeur; le copier dans un emplacement de type <=> le convertira en un type de référence et il se comportera comme un type de référence . Ce dernier est une sorte de <=> mais le premier ne l’est pas.

BTW, encore quelques notes: (1) En général, les types de classe mutables doivent avoir leur <=> méthode d'égalité de référence de test, mais il n'y a pas de moyen décent pour une structure encadrée de le faire; (2) malgré son nom, <=> est un type de classe et non un type de valeur; Tous les types dérivés de <=> sont des types de valeur, de même que tous les types dérivés de <=> à l'exception de <=>, mais <=> et <=> sont tous deux des types de classe.

Les structures sont implémentées en tant que types de valeur et les classes sont des types de référence. Si vous avez une variable de type Foo et que vous stockez une instance de Fubar dans celle-ci, elle & "Box it &"; dans un type de référence, éliminant ainsi l'avantage d'utiliser une structure en premier lieu.

La seule raison pour laquelle je vois utiliser une structure au lieu d'une classe est parce que ce sera un type valeur et non un type référence, mais la structure ne peut pas hériter d'une classe. Si la structure hérite d'une interface et que vous transmettez des interfaces, vous perdez la nature du type de valeur de la structure. Autant en faire une classe si vous avez besoin d’interfaces.

(Vous n'avez rien de majeur à ajouter, mais vous n'avez pas encore de prouesse en matière d'édition, alors allez-y ..)
Parfaitement en sécurité. Rien d'illégal avec l'implémentation d'interfaces sur des structures. Cependant, vous devriez vous demander pourquoi vous voudriez le faire.

Cependant, l'obtention d'une référence d'interface vers une structure la BOX . Donc, pénalité de performance et ainsi de suite.

Le seul scénario valable auquel je puisse penser actuellement est illustrés dans mon post ici . Lorsque vous souhaitez modifier l'état d'une structure stockée dans une collection, vous devez le faire via une interface supplémentaire exposée sur la structure.

Je pense que le problème est qu’il provoque la boxe car les structs sont des types valeur, ce qui entraîne une légère pénalité de performances.

Ce lien suggère qu'il pourrait y avoir d'autres problèmes ...

http://blogs.msdn.com/abhinaba /archive/2005/10/05/477238.aspx

Il n'y a pas de conséquences pour une structure implémentant une interface. Par exemple, les structures système intégrées implémentent des interfaces telles que IComparable et IFormattable.

Il y a très peu de raisons pour qu'un type de valeur implémente une interface. Comme vous ne pouvez pas sous-classer un type de valeur, vous pouvez toujours le désigner comme son type concret.

À moins bien sûr que vous ayez plusieurs structures implémentant la même interface, cela pourrait être un avantage marginal, mais à ce stade, je vous recommanderais d'utiliser une classe et de le faire correctement.

Bien sûr, en mettant en place une interface, vous boxez la structure, elle est donc maintenant sur le tas, et vous ne pourrez plus la passer en valeur ... Cela renforce vraiment mon opinion que vous devriez simplement utiliser une classe dans cette situation.

Les structures ressemblent aux classes qui vivent dans la pile. Je ne vois aucune raison pour laquelle ils devraient être & "Dangereux"! ".

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top