Как правильно читать / интерпретировать необработанную трассировку стека C#?

я читаю некоторые отчеты о сбоях из приложения UWP (C#, скомпилированного с .NET Native), и мне трудно понять точный синтаксис/формат, используемый в трассировках стека. Я попытался найти некоторые руководства в интернете, но я не придумал ничего полезного.

вот несколько примеров:

1)

MyProject.ViewModels.SomeViewModel.<OnLogin>d__69.MoveNext()
  • OnLogin - это имя метода в SomeViewModel, так почему же он внутри угловых скобок? Это "ClassName".<"MethodName>..." обычный способ указать метод экземпляра?
  • я понимаю, что компилятор C# поворачивает каждый кусок кода между await вызывает анонимные методы и планирует их с помощью продолжения, поэтому я думаю, что d__69 указывает на асинхронное продолжение внутри текущего метода.
    • что означает "d"?
    • эти числа случайны? Я имею в виду, что метод не имеет 69 await звонки, поэтому я думаю, что эти номера не являются последовательными. Есть можно ли узнать точную часть исходного метода из этого числа в трассировке стека?
  • что это MoveNext() метод в конце? Что это за тип?

2)

MyProject.UserControls.SomeControl.<.ctor>b__0_0
  • я знаю, что .ctor означает конструктор объектов, и, глядя на код, я узнал, что b__0_0 означает анонимный обработчик событий, добавленный внутри конструктора, например это: SomeEvent += (s, e) => Foo();.
    • что означает буква "Б"?
    • почему есть два числа с подчеркиванием? Какой из них относится к индексу анонимного метода? Я имею в виду, что это первый (поэтому его индекс равен 0), но здесь есть два 0. Если бы это было второе,я бы ... --17-->, 1_0 или что-то еще?

3) у меня есть этот метод:

// 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);
}

и у меня есть этот стек Трейс:

MyProject.Helpers.Extensions.TasksExtensions.<>c__3<System.__Canon>.<GetWatchedTask>b__3_0($Task<__Canon> task)
  • почему второй параметр (токен) не отображается в подписи?
  • почему типа $Task написано с символом"$"? Это как какой-то индикатор заполнителя/земли (например, в регулярном выражении), чтобы его можно было использовать и в других местах? (Я имею в виду, я вижу это <System.__Canon> в том, что я думаю, это тип возврата метода)
    • если эта первая часть является типом возврата метода, почему она не существует для всех методов, которые имеют тип возврата? У меня есть много трассировок стека с методом, которые возвращают значение, но у них нет той же подписи.
  • что все это .<>c__3<System.__Canon> в смысле? От и я думаю, что он будет Task<T> тип возврата, но что это b__3_0 часть, так как у меня нет асинхронных вызовов или обработчиков событий? Означает ли это что-то другое в данном случае?

4)

Windows.UI.Xaml.Media.SolidColorBrush..ctor($Color color)
  • почему начинается ли тип параметра с символа"$"? Что это значит?

5) у меня есть другой способ:

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

и эта трассировка стека:

MyProject.SQLiteDatabase.SQLiteManager.<>c__DisplayClass142_3.<LoadGroups>b__3()
  • что это c__DisplayClass142_3? Это способ указать возвращаемый тип с одним типом, а не с тремя отдельными классами (Task, ObservableCollection, SomeCustomClass)?
  • опять же, почему у меня b__3 здесь, где в другой стек трассировки он использовал формат d_xxx указать фрагмент асинхронного кода?

извините за многие вопросы, я надеюсь, что этот пост поможет другим программистам UWP c#.

заранее спасибо за вашу помощь!

редактировать: этот вопрос надо не считается дубликатом этот и другие вопросы потому что:

  • он представляет различные случаи (методы конструктора, общие типы синтаксис и т. д..) вместо того, чтобы просто спрашивать о значении некоторых ключевых слов/символов по умолчанию, связанных с определенным типом переменных
  • он конкретно спрашивает, как сравнить данную трассировку стека с оригинальной сигнатурой метода и шаги, которые нужно сделать для достижения этого
  • он представляет различные примеры в разных контекстах, а не просто задает общий вопрос
  • и, кстати, как "магические имена VS debugger" даже можно считать правильным вопросом титул? Как другой пользователь должен был найти этот вопрос при поиске символов трассировки стека C#?

1 ответов


держу пари, Эрик Липперт придет позже и даст лучший ответ, но в случае, если этого не произойдет - вот мой дубль, потому что я тоже заинтересовался этим. Значение "d", " c " и подобных символов я получил от этой ответ Эрика Липперта.

1) MyProject.ViewModels.SomeViewModel.<OnLogin>d__69.MoveNext()

это относительно просто. OnLogin является асинхронным методом, и такие методы перезаписываются компилятором в государственную машину. Эта государственная машина реализует IAsyncStateMachine интерфейс, который имеет MoveNext метод. Таким образом, ваш асинхронный метод в основном становится последовательностью MoveNext вызовы этой государственной машины. Вот почему вы видите MoveNext() в стек трассировки.

MyProject.ViewModels.SomeViewModel.<OnLogin>d__69 - имя сгенерированного класса машины состояния. Потому что эта машина состояния связана с OnLogin method-он становится частью имени типа. d это "итератор" по ссылке выше. Обратите внимание, что информация из ссылки выше 7 лет и находится до реализации async\await, но я думаю, что государственная машина похожие на iterator (то же самое MoveNext метод, тот же принцип) - так что "класс итератора" выглядит нормально. 69-это некоторое уникальное число \ счетчик. Я думаю, это просто счетчик, потому что если я скомпилирую dll только с двумя асинхронными методами - их государственные машины будут d__0 и d__1. Невозможно вывести, какая часть асинхронного метода была брошена на основе этой информации.

2) b является "анонимным методом" (ссылка выше). Я провел несколько экспериментов, и я думаю, что первый индекс связан с методом в котором использовался анонимный метод, а второй индекс, по-видимому, связан с индексом анонимного метода внутри того метода, в котором они используются. Например, предположим, что вы используете 2 анонимных метода в конструкторе и 2 анонимных метода в методе Foo в том же классе. Затем:

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) это выглядит довольно сложным. Сначала вы спрашиваете: "почему второй параметр (токен) не отображается в подписи". Это просто - потому что метод, о котором идет речь, представляет анонимный метод task => task.GetAwaiter().GetResult(), а не ваши GetWatchedTask метод. Теперь я не смог воспроизвести трассировку стека с помощью этого, но все же некоторую информацию. Во-первых, System.__Canon - это:

внутренняя methodtable используется для создания экземпляра " canonical" methodtable для общих экземпляров. Имя "_ _ Canon" никогда не будет замечено пользователями, но оно будет много отображаться в трассировках стека отладчика с участием дженериков, поэтому он намеренно короткий, чтобы избежать неприятностей.

выглядит загадочно для меня, но я думаю, что это своего рода представляет ваш T в runtime. Затем:<>c__3<System.__Canon> is <>c__3<T> и является именем класса, сгенерированного компилятором, где " c "- это" класс закрытия анонимного метода " (по ссылке выше). Такой класс генерируется компилятором при создании закрытия, поэтому захватите некоторое внешнее состояние в анонимном методе. То, что было захвачено, должно храниться где-то, и оно хранится в таком классе.

Идем дальше, <GetWatchedTask>b__3_0 является методом в этом анонимном класс выше. Он представляет ваш task => task.GetAwaiter().GetResult() метод. Все, что относится к пункту 2, применимо и здесь.

я не знаю смысла $, возможно, он представляет количество параметров типа. Так что, может быть Task<System.__Canon> означает Task<T> и что-то вроде Tuple<System.__Canon означает Tuple<T1, T2>.

4) что я, к сожалению, не знаю и не смог воспроизвести.

5) c__DisplayClass142_3 снова класс закрытия (см. пункт 3). <LoadGroups>b__3() анонимный метод используется в методе LoadGroups. Так что это указывает на какой-то анонимный метод, который является закрытием (захваченным внешним состоянием) и который был вызван в LoadGroups метод.