如何检测异步方法主体中的源代码更改
How to detect source code changes in async method body
我试图在运行时检测 class 方法的源代码是否已更改。基本上我检索方法主体 (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 方法添加一些代码时,哈希值都不会改变。有谁知道如何检测异步方法的方法体是否发生变化?
异步方法与迭代器方法一样,大多被编译成表示状态机的嵌套助手 class。整个助手 class(使用带停用选项的 ILSpy 反编译异步方法以查看示例的结果)将仅用于该异步方法。该方法的更改可能会发生在该助手的生成方法中 class 而不是原始方法。
关于你的第二个问题,没有使用 Cecil(因为我没有):
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
}
如果查看生成的代码,您会清楚地看到它需要编译器生成的 "hidden" 类型的局部变量。我们使用这个 :-) 请注意,这与 yield
和 async
兼容
感谢@xanatos 和@Wormbo 为我指明了正确的方向,我找到了解决方案。
对于异步方法,C# 编译器会生成一个包含方法主体的帮助程序 class。这些助手 classes 可以在主类型的 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));
我试图在运行时检测 class 方法的源代码是否已更改。基本上我检索方法主体 (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 方法添加一些代码时,哈希值都不会改变。有谁知道如何检测异步方法的方法体是否发生变化?
异步方法与迭代器方法一样,大多被编译成表示状态机的嵌套助手 class。整个助手 class(使用带停用选项的 ILSpy 反编译异步方法以查看示例的结果)将仅用于该异步方法。该方法的更改可能会发生在该助手的生成方法中 class 而不是原始方法。
关于你的第二个问题,没有使用 Cecil(因为我没有):
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
}
如果查看生成的代码,您会清楚地看到它需要编译器生成的 "hidden" 类型的局部变量。我们使用这个 :-) 请注意,这与 yield
和 async
感谢@xanatos 和@Wormbo 为我指明了正确的方向,我找到了解决方案。
对于异步方法,C# 编译器会生成一个包含方法主体的帮助程序 class。这些助手 classes 可以在主类型的 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));