在 .NET Core 中反编译匿名方法 IL

Decompiling Anonymous Method IL in .NET Core

我有这个方法

public void Add(Action<Cursor> action)
{
    // Decompile `action` to C#
}

方法内部我想将action中的匿名方法反编译为C#代码,或C#代码的AST表示。

我尝试使用 Mono.CecilICSharpCode.Decompiler(来自 ILSpy 存储库),但我找不到反编译匿名方法的方法,因为这两个库都需要您加载从物理位置获取程序集并通过类型图搜索方法,这看起来非常复杂,尤其是在寻找匿名方法时。

有没有办法使用该方法的 IL 字节数组,并将其反编译为 C#?

public void Add(Action<Cursor> action)
{
    var il = action.Method.GetMethodBody().GetILAsByteArray();
    var ast = decompiler.DecompileIL(il); // Does such decompiler exist?
}

使用 ICSharpCode.Decompiler 库(来自 ILSpy 存储库),我能够反编译 lambda 方法。

像这样

public void Add(Action<Cursor> action)
{
    // Get the assembly in which the lambda resides
    var asm = Assembly.GetCallingAssembly();
    var file = asm.Location;

    // Create a resolver (a callback for resolving referenced assemblies during decompilation)
    var resolver = new CustomAssemblyResolver(asm);
    // Create an instance of decompiler
    var decompiler = new CSharpDecompiler(file, resolver, new DecompilerSettings());

    // Get an "EntityHandle" instance for the lambda's underlying method
    var method = MetadataTokenHelpers.TryAsEntityHandle(action.Method.MetadataToken); 

    var ast = decompiler.Decompile(new List<EntityHandle>() { method.Value });
}

正如我提到的,您为 CSharpDecompiler 实例提供了一个解析器 (IAssemblyResolver),其工作是在反编译过程中加载引用的程序集。

ICSharpCode.Decompiler 有一个 UniversalAssemblyResolver (source) 它通过在文件系统中搜索公共文件夹来完成这项工作(它接收一个框架版本以知道在哪里搜索)。我不喜欢它,因为它假设太多并且需要太多信息(我并不总是知道框架或运行时的其他部分),所以我创建了我自己的解析器来遍历程序集图(从调用程序集)。

class CustomAssemblyResolver : IAssemblyResolver
{
    private Dictionary<string, Assembly> _references;

    public CustomAssemblyResolver(Assembly asm)
    {
        _references = new Dictionary<string, Assembly>();
        var stack = new Stack<Assembly>();
        stack.Push(asm);

        while (stack.Count > 0)
        {
            var top = stack.Pop();
            if (_references.ContainsKey(top.FullName)) continue;

            _references[top.FullName] = top;

            var refs = top.GetReferencedAssemblies();
            if (refs != null && refs.Length > 0)
            {
                foreach (var r in refs)
                {
                    stack.Push(Assembly.Load(r));
                }
            }
        }

        ;
    }

    public PEFile Resolve(IAssemblyReference reference)
    {
        var asm = _references[reference.FullName];
        var file = asm.Location;
        return new PEFile(file, new FileStream(file, FileMode.Open, FileAccess.Read), PEStreamOptions.Default, MetadataReaderOptions.Default);
    }

    public PEFile ResolveModule(PEFile mainModule, string moduleName)
    {
        var baseDir = Path.GetDirectoryName(mainModule.FileName);
        var moduleFileName = Path.Combine(baseDir, moduleName);
        if (!File.Exists(moduleFileName))
        {
            throw new Exception($"Module {moduleName} could not be found");
        }
        return new PEFile(moduleFileName, new FileStream(moduleFileName, FileMode.Open, FileAccess.Read), PEStreamOptions.Default, MetadataReaderOptions.Default);
    }
}

我不知道它是否是所有用例的最佳方法,但它对我来说非常有用,至少到目前为止是这样。