0

Here is a sample Scala code that I'm trying to rewrite in C#:

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
  }
}

Here is where I started:

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();
    } 
}

But then I stuck because I'm not sure if I could just copy that byte array in il.emit and it will work. So I thought maybe in 2019 there could be some ways in acheiving it without dirty magic, postsharping output binaries or something. I'm looking for a pure solution in terms of BCL and Expressions/DynamicMethods.

The most recent attempt is following:

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
            }
        }
    }
}

Which fails with CLR execution error.

Alex Zhukovskiy
  • 9,565
  • 11
  • 75
  • 151
  • 4
    I'm really not sure what you're trying to do here. Perhaps if you explain what you are trying to do, rather than giving us Scala code? – DavidG Aug 14 '19 at 11:06
  • Yes, some explanation would be great. As things stand, you could make your class non-abstract (no explanation given why it should be abstract), make your constructor public - and just pass you delegate to the constructor - and it would "work" (i.e. compile/run). – decPL Aug 14 '19 at 11:08
  • @DavidG I want to serialize a lambda expression. Since I can't pass it I decided to generate a unique type for it, pass this type and then use it as a unique key to execute the lambda on the other end of the wire. I have no access to the serializer, so I want to wrap the func with unique type which will work. – Alex Zhukovskiy Aug 14 '19 at 11:18
  • 1
    Serialising lambda expressions is not as simple as this. There are third-party tools to serialise Expressions, perhaps they might be useful. Other than that, why would you even want to do this? – DavidG Aug 14 '19 at 11:21
  • I have two ends, A and B. When `B` finishes some action it have to execute some callback. I can't pass a callback so I want to pass a type that "encodes" the function. I could just pass method's MetadataToken (because I only need to know which callback method I have to execute, I already have its IL) but I don't know how to get a delegate from it, so I decided to just a generate a new type and then call its method which just duplicates what this lambda does. – Alex Zhukovskiy Aug 14 '19 at 11:25
  • The purpose of IL is codegen wrappers instead of writing them manually – Alex Zhukovskiy Aug 14 '19 at 11:41
  • Another way is creating some static Dictionary MetadataToken -> Func and pass it, and then recover it. But in this case I loose all type checks. – Alex Zhukovskiy Aug 14 '19 at 11:46
  • Why can't you pass a callback? – DavidG Aug 14 '19 at 11:47
  • Because callback is not serializable. I can't pass it via HTTP. – Alex Zhukovskiy Aug 14 '19 at 11:49
  • 1
    Oh my, that sounds like a nasty way to do things. I would maybe pass a URL that the destination could ping back with a result. You realise the inherent security risks of passing a set of executable code over HTTP? I wouldn't like that in any of my systems. – DavidG Aug 14 '19 at 11:57

1 Answers1

0

It's not exactly what I wanted but it's the only solution I came with

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

Instead of wrapping a function in the type I merely pass a metadataToken which I then convert back.


Why is this needed may be shown as following:

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

As you can see, this way function callback may be serialized and deserialized back.

Alex Zhukovskiy
  • 9,565
  • 11
  • 75
  • 151
  • I don't understand why you wrote all this code, why not just have a `Dictionary`? – DavidG Aug 14 '19 at 12:11
  • Because I send it via HTTP. I can send an int, but I cannot send a `Func`. If I could send funcs I didn't have to write any dictionaries at all, I could just pass an object as it is. So I pass a token instead. – Alex Zhukovskiy Aug 14 '19 at 13:15
  • You miss my point, what does this code give you that a plain dictionary doesn't? – DavidG Aug 14 '19 at 13:16
  • The only difference with my dictionary is you are using specific `Func` (when in my case different `Func`s could be stored) and you use some `string` instead of metadataToken. I don't know where I'm supposed to get this string from. – Alex Zhukovskiy Aug 14 '19 at 15:04
  • Just be careful about the security issues of deserializing and executing arbitrary code. How do you validate or authenticate the input? – Rodney Richardson Aug 14 '19 at 16:14
  • Because you don't actually pass the executable code itself, you just pass some kind of identifier. The worst thing someone could do is execute one of pre-defined methods, that all are pure and cannot affect anything. – Alex Zhukovskiy Aug 15 '19 at 09:11