Как обнаружить изменения исходного кода в теле асинхронного метода

Я пытаюсь определить во время выполнения, был ли изменен исходный код метода класса. В основном я извлекаю тело метода (IL), хеширую его с помощью md5 и сохраняю в базе данных. В следующий раз, когда я проверю метод, я могу сравнить хэши.

public class Changed
{
    public string SomeValue { get; set; }

    public string GetSomeValue()
    {
        return SomeValue + "add something";
    }

    public async Task<string> GetSomeValueAsync()
    {
        return await Task.FromResult(SomeValue + "add something");
    }
}

Я использую Mono.Cecil для извлечения тел методов:

var module = ModuleDefinition.ReadModule("MethodBodyChangeDetector.exe");

var typeDefinition = module.Types.First(t => t.FullName == typeof(Changed).FullName);

// Retrieve all method bodies (IL instructions as string)
var methodInstructions = typeDefinition.Methods
    .Where(m => m.HasBody)
    .SelectMany(x => x.Body.Instructions)
    .Select(i => i.ToString());

var hash = Md5(string.Join("", methodInstructions));

Это прекрасно работает, за исключением методов, помеченных как асинхронные. Всякий раз, когда я добавляю код в метод SomeValue, хеш изменяется. Всякий раз, когда я добавляю код в метод GetSomeValueAsync, хэш не меняется. Кто-нибудь знает, как определить, изменилось ли тело метода асинхронного метода?


person Robin van der Knaap    schedule 21.03.2015    source источник
comment
Я предлагаю вам взглянуть на приложение с помощью IlSpy, отключив асинхронные методы Display->Options->Decompile. В методах async (как и в методах yield) реализовано довольно большое моджо.   -  person xanatos    schedule 21.03.2015
comment
Асинхронные методы — это нечто другое. Компилятор преобразует его в конечный автомат. См. это для получения дополнительной информации. Также немного поиска в Google поможет понять, как асинхронные методы работают внутри.   -  person Sriram Sakthivel    schedule 21.03.2015
comment
Вам действительно не нужен Сесил для чего-то такого простого... Вы можете использовать GetMethodBody().GetILAsByteArray()/GetMethodBody().LocalVariables   -  person xanatos    schedule 21.03.2015
comment
@xanatos Это даст только локальные переменные, я хочу знать, изменилось ли тело метода, например, были ли добавлены или изменены строки кода.   -  person Robin van der Knaap    schedule 21.03.2015
comment
IL как массив байтов дает только коды операций, которые не включают, например, строки, используемые в методе.   -  person Robin van der Knaap    schedule 21.03.2015
comment
@RobinvanderKnaap Да ... Вы правы, потому что строки загружаются с ldstr из специальной таблицы   -  person xanatos    schedule 21.03.2015


Ответы (3)


Асинхронные методы, как и методы итераторов, в основном компилируются во вложенный вспомогательный класс, представляющий конечный автомат. Весь этот вспомогательный класс (используйте ILSpy с деактивированной опцией для декомпиляции асинхронных методов, чтобы увидеть результат для вашего примера) будет использоваться только для этого асинхронного метода. Изменения в методе, скорее всего, произойдут в сгенерированном методе этого вспомогательного класса, а не в исходном методе.

person Wormbo    schedule 21.03.2015
comment
Да, я тоже это читал. Знаете ли вы способ получить этот сгенерированный метод во время выполнения? Или вы предлагаете мне проверить исходный код ILSpy и посмотреть, как они это делают? - person Robin van der Knaap; 21.03.2015
comment
По крайней мере, компилятор Microsoft C#, похоже, включает имя метода в имя сгенерированного класса, и этот сгенерированный класс реализует IAsyncStateMachine. - person Wormbo; 21.03.2015

Я нашел решение благодаря @xanatos и @Wormbo, которые направили меня в правильном направлении.

В случае асинхронного метода компилятор C# создает вспомогательный класс, содержащий тело метода. Эти вспомогательные классы можно найти в свойстве NestedTypes основного типа. Итак, если мы включим тела методов вложенных типов, мы сможем создать правильный хэш:

var module = ModuleDefinition.ReadModule("MethodBodyChangeDetector.exe");

var typeDefinition = module.Types.First(t => t.FullName == typeof(Changed).FullName);

// Retrieve all method bodies (IL instructions as string)
var methodInstructions = typeDefinition.Methods
    .Where(m => m.HasBody)
    .SelectMany(x => x.Body.Instructions)
    .Select(i => i.ToString());

var nestedMethodInstructions = typeDefinition.NestedTypes
    .SelectMany(x=>x.Methods)
    .Where(m => m.HasBody)
    .SelectMany(x => x.Body.Instructions)
    .Select(i => i.ToString());


Md5(string.Join("", methodInstructions) + string.Join("", nestedMethodInstructions));
person Robin van der Knaap    schedule 21.03.2015
comment
Я только что обнаружил (внутри скомпилированных Razor Views), что некоторые методы можно разделить на несколько уровней, поэтому поиск только для NestedTypes первого уровня не работает. Это работало для меня для поиска ВСЕХ вложенных типов: var typesAndSubTypes = new List‹TypeDefinition›() { typeDefinition }; for(int i = 0; i‹typesAndSubTypes.Count(); i++) typesAndSubTypes.AddRange(typesAndSubTypes[i].NestedTypes); var allInstructions = typesAndSubTypes.SelectMany(x => x.Methods) .Where(m => m.HasBody) .SelectMany(x => x.Body.Instructions).ToList(); - person drizin; 23.09.2020

По вашему второму вопросу, без использования Сесила (потому что у меня его нет):

var method2 = typeof(Program).GetMethod("MyMethodX", BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
var body = method2.GetMethodBody();
Type[] compilerGeneratedVariables = body.LocalVariables.Select(x => x.LocalType).Where(x => x.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Length != 0).ToArray();
byte[] ilInstructions = body.GetILAsByteArray(); // You can hash these

if (compilerGeneratedVariables.Length != 0)
{
    // There are some local variables of types that are compiler generated
    // This is a good sign that the compiler has changed the code
}

Если вы посмотрите на сгенерированный код, вы увидите, что ему явно нужна локальная переменная "скрытого" типа, которая была сгенерирована компилятором. Мы используем это :-) Обратите внимание, что это совместимо как с yield, так и с async.

person xanatos    schedule 21.03.2015