将 Func 转换为 Func

huangapple go评论70阅读模式
英文:

Convert Func<T1, T2, ..., TResult> to Func<object[], object>

问题

以下是您要翻译的代码部分:

// 简化 API 并允许 GameAction 方法的任何参数组合,我想创建通用的 Register 方法,它们接受表达式并将其转换为一个通用签名,该签名可以存储在集合中,然后可以直接调用,而无需使用反射/动态调用。这是否可能?如果可能,如何实现?

请告诉我如果还有其他需要翻译的部分。

英文:

The short question:

Is it possible to convert various Func<T1, T2, ..., TResult> delegates into a common Func<object[], object> signature where the arguments are combined into a parameter array. And then still be able to invoke the methods directly without reflection/dynamics?

Something along these kind of lines:

public static void Register&lt;T, TArg1, TArg2&gt;(Expression&lt;Func&lt;TArg1, TArg2, T&gt;&gt; action)
{
    // Convert the function using some Expression methods... 
    Expression&lt;Func&lt;object[], object&gt;&gt; exp = Expression.Lambda&lt;Func&lt;object[], object&gt;&gt;(action.Body, action.Parameters);
    
    // Compile the code ready to invoke whenever it is needed...
}

Background:

I'm trying to build an P2P game library for sending and receiving/actioning RPCs. RPC methods are registered with a controller class. And when a matching RPC is received the method should be invoked with the arguments.

I would like to avoid reflection ideally, as the methods will all be strongly typed as they are registered, so it seems that generics, lambdas and expressions should be able to achieve my goals.

My best attempt so far involves wrapping the methods in a class which records the types, but I'm not happy with it because it requires creating an argument object instead of using the usual comma separated params:

public abstract class GameActionBase
{
    public Func&lt;object, object&gt; Action { get; protected set; }
    public Type ArgsType { get; protected set; }
    public Type ReturnType { get; protected set; }
}

public class GameAction&lt;TArgs, TReturn&gt; : GameActionBase
{
    public GameAction(Func&lt;TArgs, TReturn&gt; gameAction)
    {
        ArgsType = typeof(TArgs);
        ReturnType = typeof(TReturn);
        Action = args =&gt; gameAction((TArgs)args);
    }
}

public static class GameActions
{
    private static Dictionary&lt;string, GameActionBase&gt; registered;

    static GameActions()
    {
        registered = new Dictionary&lt;string, GameActionBase&gt;();
        SubscribeActionEvents();
    }

    public static void Register(string actionName, GameActionBase actionInfo)
    {
        registered.Add(actionName, actionInfo);
    }

    public static void Unregister(string actionName)
    {
        registered.Remove(actionName);
    }

    private static void SubscribeActionEvents()
    {
        Events.ActionReceivedEvent += (sender, action) =&gt;
        {
            ProcessActionReceived(action);
        };
    }

    private static void ProcessActionReceived(ActionEventArgs action)
    {
        GameActionBase info;
        if (registered.TryGetValue(action.Name, out info))
        {
            object args = JsonUtility.FromJson(action.Args, info.ArgsType);
            object res = info.Action(args);
            // etc...
        }
    }
}

Example RPC method:

public class TestActionClass
{
    public TestActionReceivedState TestActionReceived(TestActionReceivedArgs args)
    {   
        // Process game action

        // Return result
        return new TestActionReceivedState
        {
            state1 = args.firstArg,
            state2 = args.secondArg
        };        
    }
}

[System.Serializable]
public class TestActionReceivedState
{
    public string state1;
    public string state2;
}

[System.Serializable]
public class TestActionReceivedArgs
{
    public string firstArg;
    public string secondArg;
    public string thirdArg;
    public string fourthArg;
    public string fifthArg;
}

Registering the method:

TestActionClass testClass = new TestActionClass();
var testAction = new GameAction&lt;TestActionReceivedArgs, TestActionReceivedState&gt;(testClass.TestActionReceived);

GameActions.Register(&quot;TestActionReceived&quot;, testAction);

To simplify the API and allow any combination of params for GameAction methods, I would like to create generic Register methods which take expressions and transform them into a common signature which can be stored in a collection and then be directly invoked without reflection/dynamic invocation. Is this possible? If so, how is this achieved?

答案1

得分: 1

以下是您要翻译的内容:

你可以将 Func&lt;Foo, Bar, Baz&gt; 重写为一个接受一个元组参数的函数,Func&lt;(Foo, Bar), Baz&gt;。它们多少是等价的,而且元组的性能很高。元组在内部实现如下:

public struct ValueTuple&lt;T1&gt; { public T1 Item1; ... }
public struct ValueTuple&lt;T1, T2&gt; { ... }
public struct ValueTuple&lt;T1, T2, T3&gt; { ... }
// 在8个元素处停止

(请参阅 Source.Dot.Net 获取示例)

ValueTuple 为灵感,我们可以解决你的问题。你将不得不编写一堆重载,但并不太糟糕。我使用了 Action&lt;&gt;(本质上是 Func&lt;void&gt;)而不是 Func&lt;&gt;Expression&lt;Func&lt;&gt;&gt;,以保持解决方案更小,但应该很容易适应它们。请记住,Expression 不支持所有功能,这可能会严重限制你的代码(例如,() =&gt; {} 不允许,因为不支持块主体)。我还移除了 object,因为泛型更安全,速度更快,提供更好的错误消息,并且在这里同样有效。

Register 接受 Action&lt;T1, T2, ...&gt; 的变体并将它们封装在 Action&lt;(T1, T2, ...)&gt; 中。我们在 Dictionary&lt;string, (Type, Delegate)&gt; 中保存了 Action&lt;(...)&gt;typeof((T1, T2,...))

ProcessAction 做同样的事情。它接受 T1, T2, ... 的重载并将它们转换为 (T1, T2, ...),然后调用存储的动作。

public class Dispatcher
{
    public enum ProcessResult
    {
        Success,
        NotRegistered,
        ArgumentTypesNotSupported,
    }

    private readonly Dictionary&lt;string, (Type, Delegate)&gt; registered = new();

    public void Register&lt;T&gt;(string name, Action&lt;T&gt; action)
    {
        registered.Add(name, (typeof(T), action));
    }
    public void Register&lt;T0, T1&gt;(string name, Action&lt;T0, T1&gt; action)
    {
        registered.Add(name, (typeof((T0, T1)), ((T0 a, T1 b) v) =&gt; action(v.a, v.b)));
    }
    public void Register&lt;T0, T1, T2&gt;(string name, Action&lt;T0, T1, T2&gt; action)
    {
        registered.Add(name, (typeof((T0, T1, T2)), ((T0 a, T1 b, T2 c) v) =&gt; action(v.a, v.b, v.c)));
    }

    public void Unregister(string name)
    {
        registered.Remove(name);
    }

    public ProcessResult ProcessAction&lt;T&gt;(string name, T args)
    {
        if (registered.TryGetValue(name, out var typeAndAction))
        {
            var (type, action) = typeAndAction;

            if (typeof(T) != type)
            {
                // 在调试中抛出异常,因为这在开发时很难弄清楚。
                // 在发布中不要抛出异常,因为你不希望游戏崩溃。
                #if DEBUG
                throw new NotSupportedException($"类型不匹配:{name} 期望参数类型 {type},但提供的是 {typeof(T)}");
                #else
                return ProcessResult.ArgumentTypesNotSupported;
                #endif
            }

            ((Action&lt;T&gt;)action)(args);
            return ProcessResult.Success;
        }

        return ProcessResult.NotRegistered;
    }

    public ProcessResult ProcessAction&lt;T0, T1&gt;(string name, T0 args0, T1 args1) => ProcessAction&lt;(T0, T1)&gt;(name, (args0, args1));
    public ProcessResult ProcessAction&lt;T0, T1, T2&gt;(string name, T0 args0, T1 args1, T2 args2) => ProcessAction&lt;(T0, T1, T2)&gt;(name, (args0, args1, args2));
}
英文:

What you can do is rewrite Func&lt;Foo, Bar, Baz> as a function which takes one tuple argument instead, Func&lt;(Foo, Bar), Baz&gt;. They are more or less equivalent and tuples are fast. Tuple is implemented like this internally:

public struct ValueTuple&lt;T1&gt; { public T1 Item1; ... }
public struct ValueTuple&lt;T1, T2&gt; { ... }
public struct ValueTuple&lt;T1, T2, T3&gt; { ... }
// stops at 8 elements

(See Source.Dot.Net for examples)

Using ValueTuple as an inspiration, we can solve your question. You'll have to write a bunch of overloads but it's not too bad. I used Action&lt;&gt; (essentially Func&lt;void&gt;) instead of Func&lt;&gt; or Expression&lt;Func&lt;&gt;&gt; to keep the solution smaller but it should be trivial to retrofit them. Keep in mind that Expressions don't support everything, which might severely limit your code (for example, () =&gt; {} is not allowed because block bodies are not supported). I also removed object because generics are safer, a lot faster, give better error messages and work just as well here.

Register takes variants of Action&lt;T1, T2, ...&gt; and wraps them in a Action&lt;(T1, T2, ...)&gt;. We save the Action&lt;(...)&gt; and typeof((T1, T2,...)) in a Dictionary&lt;string, (Type, Delegate)&gt;. Delegate because it's the common parent class.

ProcessAction does the same thing. Takes overloads of T1, T2, ... and converts them into (T1, T2, ...), then calls the stored action.

public class Dispatcher
{
    public enum ProcessResult
    {
        Success,
        NotRegistered,
        ArgumentTypesNotSuppored,
    }

    private readonly Dictionary&lt;string, (Type, Delegate)&gt; registered = new();

    public void Register&lt;T&gt;(string name, Action&lt;T&gt; action)
    {
        registered.Add(name, (typeof(T), action));
    }
    public void Register&lt;T0, T1&gt;(string name, Action&lt;T0, T1&gt; action)
    {
        registered.Add(name, (typeof((T0, T1)), ((T0 a, T1 b) v) =&gt; action(v.a, v.b)));
    }
    public void Register&lt;T0, T1, T2&gt;(string name, Action&lt;T0, T1, T2&gt; action)
    {
        registered.Add(name, (typeof((T0, T1, T2)), ((T0 a, T1 b, T2 c) v) =&gt; action(v.a, v.b, v.c)));
    }

    public void Unregister(string name)
    {
        registered.Remove(name);
    }


    public ProcessResult ProcessAction&lt;T&gt;(string name, T args)
    {
        if (registered.TryGetValue(name, out var typeAndAction))
        {
            var (type, action) = typeAndAction;

            if (typeof(T) != type)
            {
                // throw in debug because this is a nightmare to figure out if you&#39;re still developing. 
                // Don&#39;t throw in release because you don&#39;t want your game to crash.
#if DEBUG
                throw new NotSupportedException($&quot;Type mismatch: {name} expects argument type {type} but was supplied with {typeof(T)}&quot;);
#else
                return ProcessResult.ArgumentTypesNotSuppored;
#endif
            }

            ((Action&lt;T&gt;)action)(args);
            return ProcessResult.Success;
        }

        return ProcessResult.NotRegistered;
    }

    public ProcessResult ProcessAction&lt;T0, T1&gt;(string name, T0 args0, T1 args1) =&gt; ProcessAction&lt;(T0, T1)&gt;(name, (args0, args1));
    public ProcessResult ProcessAction&lt;T0, T1, T2&gt;(string name, T0 args0, T1 args1, T2 args2) =&gt; ProcessAction&lt;(T0, T1, T2)&gt;(name, (args0, args1, args2));

}

答案2

得分: 1

以下是您提供的代码的中文翻译:

由于框架只定义了8个Func委托,使用扩展方法实现你想要的功能非常容易:

public static class Ext
{
    public static Func<object[], object> ToObjects<T1, R>(this Func<T1, R> func) => (object[] os) => (R)func((T1)os[0]);
    public static Func<object[], object> ToObjects<T1, T2, R>(this Func<T1, T2, R> func) => (object[] os) => (R)func((T1)os[0], (T2)os[1]);
    public static Func<object[], object> ToObjects<T1, T2, T3, R>(this Func<T1, T2, T3, R> func) => (object[] os) => (R)func((T1)os[0], (T2)os[1], (T3)os[2]);
    public static Func<object[], object> ToObjects<T1, T2, T3, T4, R>(this Func<T1, T2, T3, T4, R> func) => (object[] os) => (R)func((T1)os[0], (T2)os[1], (T3)os[2], (T4)os[3]);
    public static Func<object[], object> ToObjects<T1, T2, T3, T4, T5, R>(this Func<T1, T2, T3, T4, T5, R> func) => (object[] os) => (R)func((T1)os[0], (T2)os[1], (T3)os[2], (T4)os[3], (T5)os[4]);
    public static Func<object[], object> ToObjects<T1, T2, T3, T4, T5, T6, R>(this Func<T1, T2, T3, T4, T5, T6, R> func) => (object[] os) => (R)func((T1)os[0], (T2)os[1], (T3)os[2], (T4)os[3], (T5)os[4], (T6)os[5]);
    public static Func<object[], object> ToObjects<T1, T2, T3, T4, T5, T6, T7, R>(this Func<T1, T2, T3, T4, T5, T6, T7, R> func) => (object[] os) => (R)func((T1)os[0], (T2)os[1], (T3)os[2], (T4)os[3], (T5)os[4], (T6)os[5], (T7)os[6]);
    public static Func<object[], object> ToObjects<T1, T2, T3, T4, T5, T6, T7, T8, R>(this Func<T1, T2, T3, T4, T5, T6, T7, T8, R> func) => (object[] os) => (R)func((T1)os[0], (T2)os[1], (T3)os[2], (T4)os[3], (T5)os[4], (T6)os[5], (T7)os[6], (T8)os[7]);
}

现在可以轻松运行:

Func<int, string, string> f = (n, t) =>
    String.Join(" ", Enumerable.Range(1, n).Select(x => t));

Func<object[], object> g = f.ToObjects();

Console.WriteLine(g(new object[] { 5, "Hello" }));

这将产生:

> Hello Hello Hello Hello Hello

英文:

Since there are only 8 Func delegates defined by the framework, it's quite easy to make what you want with extension methods:

public static class Ext
{
	public static Func&lt;object[], object&gt; ToObjects&lt;T1, R&gt;(this Func&lt;T1, R&gt; func) =&gt; (object[] os) =&gt; (R)func((T1)os[0]);
	public static Func&lt;object[], object&gt; ToObjects&lt;T1, T2, R&gt;(this Func&lt;T1, T2, R&gt; func) =&gt; (object[] os) =&gt; (R)func((T1)os[0], (T2)os[1]);
	public static Func&lt;object[], object&gt; ToObjects&lt;T1, T2, T3, R&gt;(this Func&lt;T1, T2, T3, R&gt; func) =&gt; (object[] os) =&gt; (R)func((T1)os[0], (T2)os[1], (T3)os[2]);
	public static Func&lt;object[], object&gt; ToObjects&lt;T1, T2, T3, T4, R&gt;(this Func&lt;T1, T2, T3, T4, R&gt; func) =&gt; (object[] os) =&gt; (R)func((T1)os[0], (T2)os[1], (T3)os[2], (T4)os[3]);
	public static Func&lt;object[], object&gt; ToObjects&lt;T1, T2, T3, T4, T5, R&gt;(this Func&lt;T1, T2, T3, T4, T5, R&gt; func) =&gt; (object[] os) =&gt; (R)func((T1)os[0], (T2)os[1], (T3)os[2], (T4)os[3], (T5)os[4]);
	public static Func&lt;object[], object&gt; ToObjects&lt;T1, T2, T3, T4, T5, T6, R&gt;(this Func&lt;T1, T2, T3, T4, T5, T6, R&gt; func) =&gt; (object[] os) =&gt; (R)func((T1)os[0], (T2)os[1], (T3)os[2], (T4)os[3], (T5)os[4], (T6)os[5]);
	public static Func&lt;object[], object&gt; ToObjects&lt;T1, T2, T3, T4, T5, T6, T7, R&gt;(this Func&lt;T1, T2, T3, T4, T5, T6, T7, R&gt; func) =&gt; (object[] os) =&gt; (R)func((T1)os[0], (T2)os[1], (T3)os[2], (T4)os[3], (T5)os[4], (T6)os[5], (T7)os[6]);
	public static Func&lt;object[], object&gt; ToObjects&lt;T1, T2, T3, T4, T5, T6, T7, T8, R&gt;(this Func&lt;T1, T2, T3, T4, T5, T6, T7, T8, R&gt; func) =&gt; (object[] os) =&gt; (R)func((T1)os[0], (T2)os[1], (T3)os[2], (T4)os[3], (T5)os[4], (T6)os[5], (T7)os[6], (T8)os[7]);
}

Now it's easy to run:

Func&lt;int, string, string&gt; f = (n, t) =&gt;
	String.Join(&quot; &quot;, Enumerable.Range(1, n).Select(x =&gt; t));

Func&lt;object[], object&gt; g = f.ToObjects();

Console.WriteLine(g(new object[] { 5, &quot;Hello&quot; }));

That produces:

> Hello Hello Hello Hello Hello

huangapple
  • 本文由 发表于 2023年6月13日 07:53:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/76460918.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定