Come leggere / interpretare correttamente una traccia di stack C # raw?

Sto leggendo alcuni rapporti di arresto anomalo da un’applicazione UWP (C #, compilato con .NET Native) e ho difficoltà a capire la syntax esatta / il formato utilizzato nelle tracce dello stack. Ho provato a cercare alcune guide su internet ma non ho trovato nulla di utile.

Ecco alcuni esempi:

1)

MyProject.ViewModels.SomeViewModel.d__69.MoveNext() 
  • OnLogin è il nome di un metodo in SomeViewModel , quindi perché è all’interno di parentesi angolari? È il "ClassName"...." il solito modo di indicare un metodo di istanza?
  • Capisco che il compilatore C # trasforma ogni pezzo di codice tra le chiamate d__69 in metodi anonimi e le pianifica usando le continuazioni, quindi suppongo che d__69 indichi una continuazione asincrona all’interno del metodo corrente.
    • Cosa significa “d”?
    • Quei numeri sono casuali? Voglio dire, il metodo non ha 69 await chiamate, quindi immagino che quei numeri non siano sequenziali. È ansible trovare la parte esatta nel metodo originale da quel numero nella traccia dello stack?
  • Qual è il metodo MoveNext() alla fine? Che tipo di tipo è chiamato?

2)

 MyProject.UserControls.SomeControl.b__0_0 
  • So che .ctor sta per il costruttore di oggetti, e guardando il codice ho scoperto che b__0_0 sta per un gestore di eventi anonimi aggiunto all’interno del costruttore, come questo: SomeEvent += (s, e) => Foo(); .
    • Cosa significa “b”?
    • Perché ci sono due numeri con un trattino basso? Quale di questi si riferisce all’indice del metodo anonimo? Voglio dire, è il primo (quindi il suo indice è 0) ma ci sono due 0 qui. Se fosse il secondo, avrei avuto 0_1 , 1_0 o qualcos’altro?

3) Ho questo metodo:

 // Returns a Task that immediately throws when awaited, as soon as the token is cancelled public static Task GetWatchedTask(this Task awaitableTask, CancellationToken token) { return awaitableTask.ContinueWith(task => task.GetAwaiter().GetResult(), token); } 

E ho questa traccia dello stack:

 MyProject.Helpers.Extensions.TasksExtensions.c__3$1.b__3_0($Task$1 task) 
  • Perché il secondo parametro (il token) non compare nella firma?
  • Perché il tipo $Task$1 scritto con il carattere ‘$’? È come una sorta di segnaposto / indicatore di terra (come in una regex) in modo che possa essere usato anche in altri posti? (Voglio dire, vedo che $1 in quello che immagino sia il tipo di ritorno del metodo)
    • Se la prima parte è il tipo restituito dal metodo, perché non è lì per tutti i metodi che hanno un tipo restituito? Ho molte tracce dello stack con metodo che restituiscono un valore, ma non hanno la stessa firma.
  • Che cosa significa tutto questo .c__3$1 significa? Da $1 e su suppongo che sarebbe il tipo di ritorno Task , ma che cos’è quella parte b__3_0 , dal momento che non ho chiamate asincrone o gestori di eventi? Significa qualcosa di diverso in questo caso?

4)

 Windows.UI.Xaml.Media.SolidColorBrush..ctor($Color color) 
  • Perché il tipo di parametro inizia con il carattere ‘$’? Cosa significa?

5) Ho questo altro metodo:

 public static async Task<ObservableCollection> LoadItemGroups(String parentId) 

E questa traccia dello stack:

 MyProject.SQLiteDatabase.SQLiteManager.c__DisplayClass142_3.b__3() 
  • Che cos’è c__DisplayClass142_3 ? È un modo per indicare il tipo di ritorno con un singolo tipo piuttosto che con tre classi separate (Task, ObservableCollection, SomeCustomClass)?
  • Ancora una volta, perché ho b__3 qui, dove in altre tracce dello stack ha usato il formato d_xxx per indicare un chunk di codice asincrono?

Ci scusiamo per le molte domande, spero che questo post aiuti anche altri programmatori di C # UWP.

Grazie in anticipo per il vostro aiuto!

EDIT : questa domanda non dovrebbe essere considerata un duplicato di queste altre domande perché:

  • Presenta diversi casi (metodi di costruzione, syntax dei tipi generici, ecc.) Invece di chiedere semplicemente il significato di alcune parole chiave / simboli predefiniti relativi a un certo tipo di variabili
  • In particolare, chiede come confrontare una determinata traccia di stack con una firma di metodo originale e i passaggi da eseguire per ottenere ciò
  • Presenta diversi esempi in diversi contesti, invece di porre una domanda generale
  • E a proposito, in che modo “VS debugger magic names” può essere considerato un vero titolo di domanda? In che modo un altro utente avrebbe dovuto trovare quella domanda quando cercava i simboli delle tracce di stack C # che significava?

Scommetto che Eric Lippert arriverà più tardi e darà una risposta migliore, ma nel caso in cui ciò non accadrà – ecco la mia opinione, perché anch’io mi sono interessato a questo. Il significato di “d”, “c” e simboli simili ho ottenuto da questa risposta di Eric Lippert.

1) MyProject.ViewModels.SomeViewModel.d__69.MoveNext()

Questo è relativamente semplice. OnLogin è un metodo asincrono e tali metodi vengono riscritti dal compilatore in una macchina a stati. Questa macchina a stati implementa IAsyncStateMachine interfaccia IAsyncStateMachine che ha il metodo MoveNext . Quindi il tuo metodo asincrono diventa fondamentalmente una sequenza di invocazioni MoveNext di quella macchina a stati. Ecco perché vedi MoveNext() nella traccia dello stack.

MyProject.ViewModels.SomeViewModel.d__69 è il nome della class della macchina di stato generata. Poiché questa macchina di stato è correlata al metodo OnLogin , diventa parte del nome del tipo. d è “class iteratore” dal link sopra. Si noti che le informazioni dal link qui sopra hanno 7 anni e sono prima di asincrona \ attendono l’implementazione, ma suppongo che la macchina a stati sia simile a iterator (lo stesso metodo MoveNext , stesso principio) – quindi la “class iteratore” sembra soddisfacente. 69 è un numero unico \ contatore. Suppongo sia solo un contatore, perché se compilo dll con solo due metodi asincroni – le loro macchine di stato sarebbero d__0 e d__1 . Non è ansible dedurre quale parte del metodo asincrono è stata generata in base a queste informazioni.

2) b è “metodo anonimo” (link sopra). Ho fatto alcuni esperimenti e penso che il primo indice sia correlato al metodo in cui è stato utilizzato il metodo anonimo e il secondo indice sembra essere correlato all’indice del metodo anonimo all’interno del metodo in cui vengono utilizzati. Ad esempio, supponiamo di utilizzare 2 metodi anonimi in costruttore e 2 metodi anonimi in metodo Foo nella stessa class. Poi:

 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) Questo sembra abbastanza complicato. Per prima cosa chiedi “Perché il secondo parametro (il token) non compare nella firma”. È semplice – perché il metodo in questione rappresenta anonimo metodo task => task.GetAwaiter().GetResult() , non il metodo GetWatchedTask . Ora non ero in grado di riprodurre la traccia dello stack con questo, ma ancora alcune informazioni. Innanzitutto, System.__Canon è:

Metodo interno utilizzabile per istanziare la metodologia “canonica” per le istanze generiche. Il nome “__Canon” non sarà mai visto dagli utenti, ma apparirà molto nelle tracce dello stack del debugger che coinvolgono i generici, quindi è mantenuto deliberatamente breve per evitare di essere un fastidio.

Sembra criptico per me, ma immagino che rappresenti la tua T in runtime. Quindi, <>c__3$1 è <>c__3$1 ed è un nome della class generata dal compilatore, dove “c” è “class di chiusura del metodo anonimo” (dal link sopra). Tale class viene generata dal compilatore quando si crea una chiusura, quindi acquisire uno stato esterno nel metodo anonimo. Ciò che è stato catturato dovrebbe essere memorizzato da qualche parte, ed è memorizzato in tale class.

Andando oltre, b__3_0 è un metodo in quella class anonima qui sopra. Rappresenta il tuo task => task.GetAwaiter().GetResult() . Anche tutto dal punto 2 vale anche qui.

Non conosco il significato di $ , forse rappresenta il numero di parametri di tipo. Quindi forse Task$1 significa Task e qualcosa come Tuple$2 significherebbe Tuple .

4) Che sfortunatamente non conosco e non sono stato in grado di riprodurre.

5) c__DisplayClass142_3 è di nuovo una class di chiusura (vedi punto 3). b__3() è il metodo anonimo che hai usato nel metodo LoadGroups . Questo indica un metodo anonimo che è la chiusura (stato esterno catturato) e che è stato chiamato nel metodo LoadGroups .