首页 > 解决方案 > 如何使匿名类仅使用 std 实现接口?

问题描述

这是我试图用 C# 重写的示例 Scala 代码:

trait FuncHolder[T, TResult] {
  def Value: Function1[T, TResult]
}

object FuncHolder {
  def GetFunctionHolder[T, TResult](func: Function1[T, TResult]) = new FuncHolder[T, TResult] {
    override def Value: Function1[T, TResult] = func
  }
}

这是我开始的地方:

public abstract class FuncHolder<T, TResult>
{
    public Func<T, TResult> Value { get; }

    protected FuncHolder(Func<T, TResult> value)
    {
        Value = value;
    }
}


public static class FuncHolder
{
    public static FuncHolder<T, TResult> GetFuncHolder<T, TResult>(Func<T, TResult> func)
    {
        var ilBody = func.Method.GetMethodBody().GetILAsByteArray();
    } 
}

但是后来我卡住了,因为我不确定我是否可以只复制那个字节数组il.emit并且它会起作用。所以我想也许在 2019 年有一些方法可以在没有肮脏魔法、后期锐化输出二进制文件或其他东西的情况下实现它。我正在寻找 BCL 和 Expressions/DynamicMethods 方面的纯解决方案。

最近的尝试如下:

public abstract class FuncHolder<T, TResult>
{
    public abstract TResult GetResult(T value);
}

public static class FuncHolder
{
    private static ModuleBuilder _builder = AssemblyBuilder
        .DefineDynamicAssembly(new AssemblyName("OmsCodegen"), AssemblyBuilderAccess.Run)
        .DefineDynamicModule("MainModule");

    /// <summary>
    /// Returns a holder that allows safely pass delegates through the network
    /// </summary>
    /// <param name="func">Func to hold. Note that it must be pure function, closure with external dependencies will fail</param>
    public static FuncHolder<T, TResult> GetFuncHolder<T, TResult>(Func<T, TResult> func)
    {
        var tb = _builder.DefineType($"<{func.Method.MetadataToken}>__FuncHolder", TypeAttributes.Class | TypeAttributes.Sealed);
        tb.SetParent(typeof(FuncHolder));
        var baseMethod = typeof(FuncHolder<,>).GetMethod("GetResult");
        Debug.Assert(baseMethod != null);
        var implementation = tb.DefineMethod(baseMethod.Name, MethodAttributes.Public | MethodAttributes.Virtual);
        CopyIl(implementation, func.Method);
        tb.DefineMethodOverride(implementation, baseMethod);
        tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName |
                                    MethodAttributes.RTSpecialName);
        return (FuncHolder<T, TResult>) Activator.CreateInstance(tb.CreateTypeInfo().AsType());
    }

    private static void CopyIl(MethodBuilder implementation, MethodInfo methodInfo)
    {
        var ilGenerator = implementation.GetILGenerator();
        var methodBody = methodInfo.GetMethodBody();
        var il = methodBody?.GetILAsByteArray() ?? throw new InvalidOperationException("Cannot get method body");

        foreach (var local in methodBody.LocalVariables)
            ilGenerator.DeclareLocal(local.LocalType);

        var opCodes = GetOpCodes(il);
        for (int i = 0; i < opCodes.Length; ++i)
        {
            if (!opCodes[i].code.HasValue)
                continue;
            OpCode opCode = opCodes[i].code.Value;
            if (opCode.OperandType == OperandType.InlineBrTarget)
            {
                ilGenerator.Emit(opCode, BitConverter.ToInt32(il, i + 1));
                i += 4;
                continue;
            }
            if (opCode.OperandType == OperandType.ShortInlineBrTarget)
            {
                ilGenerator.Emit(opCode, il[i + 1]);
                ++i;
                continue;
            }
            if (opCode.OperandType == OperandType.InlineType)
            {
                Type tp = methodInfo.Module.ResolveType(BitConverter.ToInt32(il, i + 1), methodInfo.DeclaringType.GetGenericArguments(), methodInfo.GetGenericArguments());
                ilGenerator.Emit(opCode, tp);
                i += 4;
                continue;
            }
            if (opCode.FlowControl == FlowControl.Call)
            {
                MethodInfo mi = methodInfo.Module.ResolveMethod(BitConverter.ToInt32(il, i + 1)) as MethodInfo;
                if (mi == methodInfo)
                    ilGenerator.Emit(opCode, implementation);
                else
                    ilGenerator.Emit(opCode, mi);
                i += 4;
                continue;
            }
            ilGenerator.Emit(opCode);
        }
    }

    static OpCodeContainer[] GetOpCodes(byte[] data)
    {
        List<OpCodeContainer> opCodes = new List<OpCodeContainer>();
        foreach (byte opCodeByte in data)
            opCodes.Add(new OpCodeContainer(opCodeByte));
        return opCodes.ToArray();
    }

    class OpCodeContainer
    {
        public OpCode? code;
        byte data;

        public OpCodeContainer(byte opCode)
        {
            data = opCode;
            try
            {
                code = (OpCode)typeof(OpCodes).GetFields().First(t => ((OpCode)(t.GetValue(null))).Value == opCode).GetValue(null);
            }
            catch
            {
                // if it throws an exception then code should remain null
            }
        }
    }
}

由于 CLR 执行错误而失败。

标签: c#.netlinq-expressionscodegen

解决方案


这不完全是我想要的,但这是我带来的唯一解决方案

public abstract class FuncHolder<T, TResult>
{
    [JsonIgnore]
    public abstract Func<T, TResult> Value { get; }
}

public static class FuncHolder
{
    private static Dictionary<int, object> _metadataTokenToMethodDictionary = new Dictionary<int, object>();

    private class FuncHolderImplementation<T, TResult> : FuncHolder<T, TResult>
    {
        public int MetadataToken { get; }

        public FuncHolderImplementation(int metadataToken)
        {
            MetadataToken = metadataToken;
        }

        public override Func<T, TResult> Value => (Func<T, TResult>)_metadataTokenToMethodDictionary[MetadataToken];
    }

    public static FuncHolder<T, TResult> GetFuncHolder<T, TResult>(Func<T, TResult> func)
    {
        if (!_metadataTokenToMethodDictionary.ContainsKey(func.Method.MetadataToken))
        {
            _metadataTokenToMethodDictionary[func.Method.MetadataToken] = func;
        }

        return new FuncHolderImplementation<T, TResult>(func.Method.MetadataToken);
    }
}

我没有将函数包装在类型中,而是传递了一个 metadataToken,然后我将其转换回来。


为什么需要这个可能如下所示:

var holder = FuncHolder.GetFuncHolder<string, int>(s => s.Length);
var settings = new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.All};
var json = JsonConvert.SerializeObject(holder, settings);
var holder2 = JsonConvert.DeserializeObject<FuncHolder<string, int>>(json, settings);
Console.WriteLine(holder2.Value("hello"));

如您所见,这种方式函数回调可能会被序列化和反序列化。


推荐阅读