2009-05-14 8 views
1

Lorsque vous écrivez une fonction d'itérateur en C#, en utilisant la syntaxe yield, le compilateur traduit en interne votre fonction dans une classe. Donc, si je vous écris quelque chose comme ceci:Recherche du champ d'objet correspondant à un paramètre de référence en C#

IEnumerator<int> MyIterator() { 
    for (int i = 0; i < 10; i++) 
    yield return i; 
} 

Invoquer MyIterator() construit une classe avec un champ privé pour i, au lieu d'utiliser une variable de la pile.

Maintenant, pour la question: Je veux trouver un moyen de localiser le champ utilisé pour stocker la valeur de i. La raison est un peu en cause:

J'utilise des fonctions d'itération pour l'enfilage coopératif. J'écris un code simple comme celui-ci dans un itérateur, afin de suspendre l'exécution d'une fonction jusqu'à ce qu'une opération asynchrone soit terminée.

Future<string> f; 
yield return file.readLine(out f); 
string line = f.Result; 

Lorsque ces trois instructions sont exécutées, la fonction readLine est invoquée et stocke un avenir dans le champ « f ». La fonction d'itérateur est alors suspendue, et se réveille lorsque le 'f' Futur a eu un résultat stocké dedans, à quel point je peux récupérer le résultat.

Ce que je veux être en mesure de le faire, est la suivante:

string line; 
yield return file.readLine(out line); 

Dans des circonstances normales, cela ne serait pas possible dans .NET, car il n'y a aucun moyen de garantir qu'une variable reste de la pile en vie assez longtemps pour que je puisse écrire. Cependant, sachant que les fonctions d'itérateur stockent tous leurs locals à l'intérieur des champs, je peux théoriquement le faire en maintenant une référence à l'itérateur (en le gardant vivant) et au champ lui-même, de sorte que lorsque l'opération readLine se termine, peut stocker le résultat directement dans le champ.

Le problème est que le CLR ne semble pas me donner un moyen de le faire. Si j'ai un paramètre 'out' ou 'ref', je peux utiliser C# non sécurisé ou MSIL brut pour convertir ce paramètre en un pointeur brut, une référence ou un TypedReference. Malheureusement, il n'est pas légal de stocker l'un de ces trois types dans un champ - même si vous trompez le compilateur en vous laissant le faire, le vérificateur de bytecode ne vous laissera pas exécuter le code. Cela est probablement dû au fait que le stockage d'une référence à une variable de pile est intrinsèquement dangereux. Donc, ce que je cherche à faire est d'écrire une fonction qui prend un paramètre out/ref, et recherche les champs de l'itérateur appelant pour trouver un champ correspondant. Une fois qu'il a le champ, il stocke une référence à l'itérateur et au champ dans un type de wrapper et associe le wrapper avec une opération asynchrone. Étant donné le temps système impliqué dans la réflexion d'exécution dans .NET, je suppose que cela n'est pas possible sans prétraiter mon code source ou post-traiter mes assemblys après la compilation.

Cependant, j'aimerais entendre quelques suggestions de solutions alternatives. :) Des idées?

Répondre

0

J'ai finalement trouvé une solution qui répond ici à mes besoins, et que savez-vous - il implique LINQ.

Un article de blog sur .NET mentionnait comment vous pouvez convertir n'importe quelle expression lambda en un objet Expression <> si vous connaissez le type de résultat de l'expression. Compte tenu de cela, il se avère que vous pouvez prendre une expression comme ceci:

() => this.Foo.Bar

Et marcher à travers les nœuds pour se retrouver avec un MemberInfo (pour « bar ») et un objet 'this' (pour 'this.Foo'). Étant donné ces deux valeurs, vous pouvez lier directement à un champ ou une propriété spécifié lors de la compilation. Dans le cas d'une propriété, vous pouvez le faire très efficacement en récupérant les méthodes Get et Set de la propriété à partir de MemberInfo et en les convertissant en délégués fortement typés, ce qui rend le coût de lecture/écriture de cette propriété très faible.

Le meilleur de tous, cela fonctionne parfaitement avec le refactoring automatisé et les recherches de code source parce qu'il met le muscle du compilateur à utiliser pour vous, et ne nécessite aucune modification du code existant pour permettre la liaison. Le seul inconvénient est qu'il est très difficile de lier les champs/propriétés d'une structure, mais je ne considère pas cela comme un cas d'utilisation particulièrement pertinent.

Quiconque cherche à résoudre ce ou un problème similaire peut voir le code source ici:

http://code.google.com/p/fracture/source/browse/trunk/Squared/Util/Bind.cs

1

Mordre avec les champs de l'itérateur est de demander des ennuis. Ne serait-il pas plus simple d'utiliser un membre d'une classe que vous spécifiez dans la signature de l'itérateur? Soit en le faisant entrer ou sortir? Fondamentalement impliquant une sorte de classe de wrapper simple le long du chemin?

class MyWrapper { 
    public string Line {get;set;} 
} 

vous permettant de parler à Line tout à fait en toute sécurité, simplement par une référence d'objet MyWrapper?

Je ne comprenais pas tous la question, de sorte que est le meilleur que je peux dire ...

+0

Future dans l'exemple plus ou moins sert le but que vous décrivez. Avoir à dégrouper ma valeur d'un conteneur à chaque fois est une douleur, alors je voudrais sauter l'étape et écrire directement sur le terrain si je le peux. La sémantique de la modification des champs de l'itérateur me semble évidente - si vous regardez l'IL généré pour un itérateur, il est parfaitement sûr de modifier les locals aussi longtemps que vous le faites d'une manière saine. –