Issu d'un background C # et Java, je suis habitué à ce que mes listes soient homogènes, et cela a du sens pour moi. Quand j'ai commencé à prendre Lisp, j'ai remarqué que les listes peuvent être hétérogènes. Quand j'ai commencé à déconner avec le dynamic
mot - clé en C #, j'ai remarqué que, depuis C # 4.0, il peut aussi y avoir des listes hétérogènes:
List<dynamic> heterogeneousList
Ma question est quel est le point? Il semble qu'une liste hétérogène aura beaucoup plus de temps lors du traitement et que si vous devez stocker différents types en un seul endroit, vous aurez peut-être besoin d'une structure de données différente. Ma naïveté est-elle en train de se révéler laide ou y a-t-il vraiment des moments où il est utile d'avoir une liste hétérogène?
List<dynamic>
différent (pour votre question) de faire simplementList<object>
?Réponses:
Le document Strongly Typed Heterogenous Collections par Oleg Kiselyov, Ralf Lämmel et Keean Schupke contient non seulement une implémentation de listes hétérogènes dans Haskell, mais aussi un exemple motivant de quand, pourquoi et comment vous utiliseriez les HLists. En particulier, ils l'utilisent pour accéder à la base de données vérifiée lors de la compilation en toute sécurité. (Pensez LINQ, en fait, le document auquel ils font référence est le document Haskell d'Erik Meijer et al qui a conduit à LINQ.)
Citant le paragraphe introductif du document HLists:
Notez que les exemples que vous avez donnés dans votre question ne sont vraiment pas des listes hétérogènes dans le sens où le mot est couramment utilisé. Ce sont des listes faiblement typées ou non typées . En fait, ce sont en fait des listes homogènes , puisque tous les éléments sont du même type:
object
oudynamic
. Vous êtes ensuite obligé d'effectuer des transtypages ou desinstanceof
tests non vérifiés ou quelque chose comme ça, afin de pouvoir réellement travailler de manière significative avec les éléments, ce qui les rend faiblement typés.la source
Les conteneurs courts et hétérogènes à long terme échangent les performances d'exécution pour plus de flexibilité. Si vous voulez avoir une «liste de choses» sans tenir compte du type particulier de choses, l'hétérogénéité est la voie à suivre. Les lisps sont typiquement dynamiquement typés, et presque tout est une contre-liste de valeurs encadrées de toute façon, donc le plus petit succès de performance est attendu. Dans le monde Lisp, la productivité du programmeur est considérée comme plus importante que les performances d'exécution.
Dans un langage typé dynamiquement, les conteneurs homogènes auraient en fait un léger surcoût par rapport aux conteneurs hétérogènes, car tous les éléments ajoutés devraient être vérifiés par type.
Votre intuition sur le choix d'une meilleure structure de données est à propos. De manière générale, plus vous pouvez mettre en place de contrats sur votre code, plus vous en savez sur son fonctionnement, et plus fiable, maintenable, etc. il devient. Cependant, parfois, vous voulez vraiment un conteneur hétérogène, et vous devriez pouvoir en avoir un si vous en avez besoin.
la source
IUserSetting
et l'implémenter plusieurs fois, ou créer un génériqueUserSetting<T>
, mais l'un des problèmes du typage statique est que vous définissez une interface avant de savoir précisément comment elle va être utilisée. Les choses que vous faites avec les paramètres entiers sont probablement très différentes de celles que vous faites avec les paramètres de chaîne, alors quelles opérations sont logiques à mettre dans une interface commune? Jusqu'à ce que vous en soyez certain, il est préférable d'utiliser judicieusement la frappe dynamique et de la concrétiser plus tard.object
plutôt que d'undynamic
, alors bien sûr, utilisez le premier.Dans les langages fonctionnels (comme lisp), vous utilisez la correspondance de modèles pour déterminer ce qui arrive à un élément particulier dans une liste. L'équivalent en C # serait une chaîne d'instructions if ... elseif qui vérifient le type d'un élément et effectuent une opération basée sur cela. Il va sans dire que la mise en correspondance de modèles fonctionnels est plus efficace que la vérification du type d'exécution.
L'utilisation du polymorphisme serait une correspondance plus étroite avec la correspondance de motifs. C'est-à-dire que les objets d'une liste correspondent à une interface particulière et appellent une fonction sur cette interface pour chaque objet. Une autre alternative serait de fournir une série de méthodes surchargées qui prennent un type d'objet spécifique comme paramètre. La méthode par défaut prenant Object comme paramètre.
Cette approche fournit une approximation de la correspondance de motifs Lisp. Le modèle de visiteur (tel qu'implémenté ici, est un excellent exemple d'utilisation de listes hétérogènes). Un autre exemple serait pour la répartition des messages où, il y a des écouteurs pour certains messages dans une file d'attente prioritaire et en utilisant la chaîne de responsabilité, le répartiteur transmet le message et le premier gestionnaire qui correspond au message le gère.
Le revers est d'avertir tous ceux qui s'inscrivent pour un message (par exemple, le modèle d'agrégateur d'événements couramment utilisé pour le couplage lâche des ViewModels dans le modèle MVVM). J'utilise la construction suivante
La seule façon d'ajouter au dictionnaire est une fonction
(et l'objet est en fait une référence faible au gestionnaire passé dans). Donc, ici, je dois utiliser List <Object> car au moment de la compilation, je ne sais pas quel sera le type fermé. Au Runtime cependant, je peux imposer que ce sera ce type qui sera la clé du dictionnaire. Quand je veux déclencher l'événement que j'appelle
et encore je résous la liste. Il n'y a aucun avantage à utiliser List <dynamic> car je dois quand même le caster. Donc, comme vous le voyez, il y a des avantages aux deux approches. Si vous allez distribuer dynamiquement un objet à l'aide de la surcharge de méthode, dynamique est le moyen de le faire. Si vous êtes forcé de lancer quoi que ce soit, vous pourriez aussi bien utiliser Object.
la source
DoSomething(Object)
(au moins lors de l' utilisationobject
dans laforeach
boucle,dynamic
est tout autre chose).Vous avez raison de dire que l'hétérogénéité entraîne une surcharge d'exécution, mais plus important encore, elle affaiblit les garanties de compilation fournies par le vérificateur de typage. Néanmoins, il existe certains problèmes où les alternatives sont encore plus coûteuses.
D'après mon expérience, en traitant des octets bruts via des fichiers, des sockets réseau, etc., vous rencontrez souvent de tels problèmes.
Pour donner un exemple réel, considérons un système de calcul distribué utilisant des futures . Un travailleur sur un nœud individuel peut générer du travail de tout type sérialisable, ce qui donne un avenir de ce type. Dans les coulisses, le système envoie le travail à un pair, puis enregistre un enregistrement associant cette unité de travail à l'avenir particulier qui doit être rempli une fois que la réponse à ce travail revient.
Où ces enregistrements peuvent-ils être conservés? Intuitivement, ce que vous voulez est quelque chose comme un
Dictionary<WorkId, Future<TValue>>
, mais cela vous limite à gérer un seul type de futures dans tout le système. Le type le plus approprié estDictionary<WorkId, Future<dynamic>>
, car l'ouvrier peut lancer le type approprié lorsqu'il force l'avenir.Remarque : Cet exemple vient du monde Haskell où nous n'avons pas de sous-typage. Je ne serais pas surpris s'il y ait une solution plus idiomatique pour cet exemple particulier en C #, mais j'espère qu'elle est toujours illustrative.
la source
ISTR que Lisp n'a pas de structures de données autres qu'une liste, donc si vous avez besoin de n'importe quel type d'objet de données agrégé, cela devra être une liste hétérogène. Comme d'autres l'ont souligné, ils sont également utiles pour sérialiser des données pour la transmission ou le stockage. Une fonctionnalité intéressante est qu'ils sont également ouverts, vous pouvez donc les utiliser dans un système basé sur une analogie de tuyaux et de filtres et avoir des étapes de traitement successives pour augmenter ou corriger les données sans nécessiter un objet de données fixe ou une topologie de flux de travail .
la source