web-dev-qa-db-fra.com

ref et out arguments dans la méthode async

Est-ce que quelqu'un sait pourquoi les méthodes async ne sont pas autorisées à avoir des arguments ref et out? J'ai fait un peu ou fait des recherches dessus, mais la seule chose que j'ai pu trouver, c'est que cela a à voir avec le déroulement de la pile.

49
Ned Stoyanov

Est-ce que quelqu'un sait pourquoi les méthodes asynchrones ne sont pas autorisées à avoir des arguments ref et out?

Sûr. Pensez-y - une méthode asynchrone habituellement retourne presque immédiatement, bien avant que la majeure partie de la logique réelle ne soit exécutée ... cela se fait de manière asynchrone. Ainsi, tous les paramètres out devraient être attribués avant la première expression await, et il devrait très probablement y avoir une restriction sur les paramètres ref pour les empêcher d'être utilisés. après la première expression await de toute façon, car après cela, elles peuvent même ne pas être valides.

Pensez à appeler une méthode asynchrone avec les paramètres out et ref, en utilisant des variables locales pour les arguments:

int x;
int y = 10;
FooAsync(out x, ref y);

Après le retour de FooAsync, la méthode elle-même pourrait retourner - de sorte que ces variables locales n'existeraient plus logiquement ... mais la méthode async serait toujours en mesure de les utiliser dans ses suites. Gros problèmes. Le compilateur pourrait créer une nouvelle classe pour capturer la variable de la même manière que pour les expressions lambda, mais cela causerait d'autres problèmes ... à part quoi que ce soit d'autre, vous pourriez avoir une variable locale changeante à des points arbitraires via une méthode, lorsque les continuations s'exécutent sur un thread différent. Assez etrange pour dire.

Fondamentalement, il n'est pas logique d'utiliser les paramètres out et ref pour les méthodes async, en raison du timing impliqué. Utilisez plutôt un type de retour qui inclut toutes les données qui vous intéressent.

Si vous êtes uniquement intéressé par les paramètres out et ref changeant avant la première expression await, vous pouvez toujours diviser la méthode en deux:

public Task<string> FooAsync(out int x, ref int y)
{
    // Assign a value to x here, maybe change y
    return FooAsyncImpl(x, y);
}

private async Task<string> FooAsyncImpl(int x, int y) // Not ref or out!
{
}

EDIT: Il serait possible d'avoir des paramètres out en utilisant Task<T> et attribuez la valeur directement dans la méthode, tout comme les valeurs de retour. Ce serait un peu étrange cependant, et cela ne fonctionnerait pas pour les paramètres ref.

90
Jon Skeet

C # est compilé en CIL et CIL ne le prend pas en charge.

CIL n'a pas async en natif. async les méthodes sont compilées dans une classe, et tous les paramètres (utilisés) et les variables locales sont stockés dans les champs de classe, de sorte que lorsqu'une méthode spécifique de cette classe est appelée, le code sait où continuer à exécuter et quelles valeurs les variables ont.

Les paramètres ref et out sont implémentés à l'aide de pointeurs gérés, et les champs de classe de type pointeur géré ne sont pas autorisés, de sorte que le compilateur ne peut pas conserver la référence transmise.

Cette restriction sur les pointeurs gérés dans les champs de classe empêche certains codes absurdes, comme expliqué dans la réponse de Jon Skeet, car un pointeur géré dans un champ de classe peut faire référence à une variable locale d'une fonction qui est déjà retournée. Cependant, cette restriction est si stricte que même une utilisation sûre et correcte est rejetée. Les champs ref/outpourraient fonctionner, s'ils faisaient référence à un autre champ de classe, et le compilateur s'assurait de toujours envelopper les variables locales passées avec ref/out dans une classe (comme elle sait déjà le faire).

Ainsi, C # n'a tout simplement aucun moyen de contourner les restrictions imposées par CIL. Même si les concepteurs C # veulent le permettre (je ne dis pas qu'ils le font), ils ne le peuvent pas.

12
user743382