【问题标题】:How do I make anonymous class implementing an interface with std only?如何使匿名类仅使用 std 实现接口?
【发布时间】:2019-12-20 22:30:49
【问题描述】:

这是我尝试用 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 执行错误而失败。

【问题讨论】:

  • 我真的不确定你想在这里做什么。也许你解释一下你想要做什么,而不是给我们 Scala 代码?
  • 是的,一些解释会很棒。就目前情况而言,您可以使您的类非抽象(没有解释为什么它应该是抽象的),使您的构造函数公开 - 并将您的委托传递给构造函数 - 它会“工作”(即编译/运行)。
  • @DavidG 我想序列化一个 lambda 表达式。因为我不能传递它,所以我决定为它生成一个唯一类型,传递这个类型,然后将它用作唯一键以在线路的另一端执行 lambda。我无法访问序列化程序,所以我想用唯一的类型包装函数。
  • 序列化 lambda 表达式并不是这么简单。有第三方工具可以序列化表达式,也许它们可能有用。除此之外,你为什么还要这样做?
  • 哦,天哪,这听起来像是一种令人讨厌的做事方式。我可能会传递一个 URL,目的地可以 ping 回一个结果。您意识到通过 HTTP 传递一组可执行代码的内在安全风险吗?在我的任何系统中我都不喜欢这样。

标签: c# .net linq-expressions codegen


【解决方案1】:

这不是我想要的,但这是我提供的唯一解决方案

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"));

如你所见,这样函数回调可能会被序列化和反序列化。

【讨论】:

  • 我不明白你为什么要写这么多代码,为什么不直接写一个Dictionary&lt;string, Func&lt;T, TResult&gt;
  • 因为我是通过 HTTP 发送的。我可以发送一个整数,但我不能发送一个Func。如果我可以发送函数,我根本不必编写任何字典,我可以直接传递一个对象。所以我改为传递一个令牌。
  • 你没抓住我的意思,这段代码给你什么是普通字典没有的?
  • 与我的字典的唯一区别是您使用特定的Func(在我的情况下可以存储不同的Funcs)并且您使用一些string而不是metadataToken。我不知道我应该从哪里得到这个字符串。
  • 请注意反序列化和执行任意代码的安全问题。您如何验证或验证输入?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-04-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多