Je lis certains rapports d'erreur d'une application UWP (C #, compilés avec .NET Native) et j'ai du mal à comprendre la syntaxe/le format exact utilisé dans les traces de pile. J'ai essayé de chercher des guides sur Internet mais je n'ai rien trouvé d'utile.
Voici quelques exemples:
1)
MyProject.ViewModels.SomeViewModel.<OnLogin>d__69.MoveNext()
OnLogin
est le nom d'une méthode dans SomeViewModel
, alors pourquoi est-elle à l'intérieur angular crochets? Le "ClassName".<"MethodName>..."
est-il la manière habituelle d'indiquer une méthode d'instance?await
en méthodes anonymes et les planifie à l'aide de continuations, donc je suppose que d__69
Indique une continuation asynchrone à l'intérieur de la méthode actuelle. await
appels, donc je suppose que ces chiffres ne sont pas séquentiels. Est-il possible de trouver la partie exacte de la méthode d'origine à partir de ce numéro dans la trace de la pile?MoveNext()
à la fin? De quel type est-il appelé?2)
MyProject.UserControls.SomeControl.<.ctor>b__0_0
.ctor
Représente le constructeur d'objet, et en regardant le code, j'ai découvert que b__0_0
Représente un gestionnaire d'événements anonyme ajouté à l'intérieur du constructeur, comme ceci: SomeEvent += (s, e) => Foo();
. 0_1
, 1_0
Ou autre chose?3) J'ai cette méthode:
// Returns a Task that immediately throws when awaited, as soon as the token is cancelled
public static Task<T> GetWatchedTask<T>(this Task<T> awaitableTask, CancellationToken token)
{
return awaitableTask.ContinueWith(task => task.GetAwaiter().GetResult(), token);
}
Et j'ai cette trace de pile:
MyProject.Helpers.Extensions.TasksExtensions.<>c__3$1<System.__Canon>.<GetWatchedTask>b__3_0($Task$1<__Canon> task)
$Task$1
Est-il écrit avec le caractère '$'? Est-ce comme une sorte d'indicateur d'espace réservé/au sol (comme dans une expression régulière) afin qu'il puisse également être utilisé dans d'autres endroits? (Je veux dire, je vois que $1<System.__Canon>
Dans ce que je suppose être le type de retour de méthode) .<>c__3$1<System.__Canon>
? À partir de $1
Et ainsi de suite, je suppose que ce serait le type de retour Task<T>
, Mais quelle est cette partie b__3_0
, Car je n'ai pas d'appels asynchrones ni de gestionnaires d'événements? Est-ce que cela signifie quelque chose de différent dans ce cas?4)
Windows.UI.Xaml.Media.SolidColorBrush..ctor($Color color)
5) J'ai cette autre méthode:
public static async Task<ObservableCollection<SomeCustomClass>> LoadItemGroups(String parentId)
Et cette trace de pile:
MyProject.SQLiteDatabase.SQLiteManager.<>c__DisplayClass142_3.<LoadGroups>b__3()
c__DisplayClass142_3
? Est-ce une façon d'indiquer le type de retour avec un seul type plutôt que les trois classes distinctes (Task, ObservableCollection, SomeCustomClass)?b__3
Ici, alors que dans d'autres traces de pile, il a utilisé le format d_xxx
Pour indiquer un bloc de code asynchrone?Désolé pour les nombreuses questions, j'espère que ce message aidera également d'autres programmeurs UWP C #.
Merci d'avance pour votre aide!
[~ # ~] modifier [~ # ~] : cette question devrait pas être considérée comme un doublon de - ces autres questions car:
Je parie qu'Eric Lippert viendra plus tard et donnera une meilleure réponse, mais au cas où cela ne se produirait pas - voici mon point de vue, car je me suis également intéressé à cela. La signification de "d", "c" et des symboles similaires que j'ai obtenus de this réponse par Eric Lippert.
1) MyProject.ViewModels.SomeViewModel.<OnLogin>d__69.MoveNext()
Celui-ci est relativement simple. OnLogin
est une méthode asynchrone, et ces méthodes sont réécrites par le compilateur dans une machine à états. Cette machine d'état implémente l'interface IAsyncStateMachine
qui a la méthode MoveNext
. Ainsi, votre méthode asynchrone devient fondamentalement une séquence d'invocations MoveNext
de cette machine d'état. C'est pourquoi vous voyez MoveNext()
dans la trace de la pile.
MyProject.ViewModels.SomeViewModel.<OnLogin>d__69
Est le nom de la classe de machine d'état générée. Étant donné que cette machine d'état est liée à la méthode OnLogin
- elle fait partie du nom de type. d
est "classe itérateur" par le lien ci-dessus. Notez que les informations du lien ci-dessus datent de 7 ans et sont avant async\attendent l'implémentation, mais je suppose que la machine d'état est similaire à l'itérateur (la même méthode MoveNext
, même principe) - donc "classe itérateur" semble bien . 69 est un certain nombre\compteur unique. Je suppose que c'est juste contre, parce que si je compile une DLL avec seulement deux méthodes asynchrones - leurs machines d'état seraient d__0
Et d__1
. Il n'est pas possible de déduire quelle partie de la méthode async a été lancée sur la base de ces informations.
2) b
est une "méthode anonyme" (lien ci-dessus). J'ai fait quelques expériences et je pense que le premier index est lié à la méthode dans laquelle la méthode anonyme a été utilisée, et le deuxième index semble être lié à l'index de la méthode anonyme à l'intérieur de la méthode dans laquelle ils sont utilisés. Par exemple, supposons que vous utilisez 2 méthodes anonymes dans le constructeur et 2 méthodes anonymes dans la méthode Foo
dans la même classe. Ensuite:
public Test() {
Handler += (s, e) => Foo(); // this will be `b__0_0` because it's first in this method
Handler += (s, e) => Bar(); // this will be `b__0_1` because it's second
}
static void Foo() {
Action a = () => Console.WriteLine("test"); // this is `b__1_0`, 1 refers to it being in another method, not in constructor.
// if we use anonymous method in `Bar()` - it will have this index 2
a();
Action b = () => Console.WriteLine("test2"); // this is `b__1_1`
b();
}
3) Cela semble assez compliqué. Vous demandez d'abord "Pourquoi le deuxième paramètre (le jeton) n'apparaît-il pas dans la signature". C'est simple - parce que la méthode en question représente la méthode anonyme task => task.GetAwaiter().GetResult()
, pas votre méthode GetWatchedTask
. Maintenant, je ne pouvais pas reproduire votre trace de pile avec celui-ci, mais encore quelques informations. Tout d'abord, System.__Canon
Est:
Table de méthode interne utilisée pour instancier la table de méthode "canonique" pour les instanciations génériques. Le nom "__Canon" ne sera jamais vu par les utilisateurs mais il apparaîtra beaucoup dans les traces de pile de débogueur impliquant des génériques, il est donc délibérément court pour éviter d'être une nuisance.
Cela me semble énigmatique, mais je suppose que cela représente en quelque sorte votre T
lors de l'exécution. Ensuite, <>c__3$1<System.__Canon>
Est <>c__3$1<T>
Et est un nom de classe générée par le compilateur, où "c" est "classe de fermeture de méthode anonyme" (à partir du lien ci-dessus). Une telle classe est générée par le compilateur lorsque vous créez une fermeture, donc capturez un état externe dans votre méthode anonyme. Ce qui a été capturé doit être stocké quelque part, et il est stocké dans une telle classe.
Pour aller plus loin, <GetWatchedTask>b__3_0
Est une méthode de la classe anonyme ci-dessus. Il représente votre méthode task => task.GetAwaiter().GetResult()
. Tout à partir du point 2 s'applique également ici.
Je ne connais pas la signification de $
, Peut-être qu'il représente le nombre de paramètres de type. Donc peut-être que Task$1<System.__Canon>
Signifie Task<T>
Et quelque chose comme Tuple$2<System.__Canon
Signifierait Tuple<T1, T2>
.
4) que je ne sais malheureusement pas et que je n'ai pas pu reproduire.
5) c__DisplayClass142_3
Est à nouveau une classe de fermeture (voir point 3). <LoadGroups>b__3()
est une méthode anonyme que vous avez utilisée dans la méthode LoadGroups
. Cela indique donc une méthode anonyme qui est la fermeture (état externe capturé) et qui a été appelée dans la méthode LoadGroups
.