【问题标题】:C# Pass any method as parameterC#将任何方法作为参数传递
【发布时间】:2020-08-12 15:22:57
【问题描述】:

我正在制作一个网络应用程序,我想在其中实现强类型 RPC。因此,无论参数如何,我都希望能够传递方法,这样我就可以获取它们并将它们存储在字典中,这样我就可以正确构造请求参数,一旦数据包到达,我就可以使用参数读取它使用相同的远程方法

我想要这样的东西:

Register(Enum key, [method with unknown parameters])

【问题讨论】:

  • 第一段的表述非常令人困惑(至少对我而言)。您能否再添加几句话来描述您正在尝试做的事情? “网络应用程序”部分是否相关?
  • 实际上并不能说明我是从哪里来的,本质上我只是想要一个方法来获取您传递给它的任何方法的参数类型,类似于 MyMethod(Action action)但是 Action 可以有任何种类和数量的参数
  • 可能会显示不起作用的代码,以便我们可以帮助解决特定问题
  • 你是故意想要推出你自己的品种还是仅仅使用像 gRPC 这样的东西对你有好处?

标签: c# networking reflection rpc


【解决方案1】:

在使用泛型和委托约束的 C# 8.0 中,您可以执行以下操作:

using System;
using System.Collections.Generic;

namespace ConsoleApp
{

    public class Program
    {
        public static void Main(params string[] args)
        {
            var app = new NetworkingApplication();
            app.Register<Action<int, string>>(PacketType.Type1, Method1, 1, "string1 argument");
            app.Register<Func<string, string>>(PacketType.Type2, Method2, "string2 argument");
            app.OnPacketReceived(PacketType.Type1);
            app.OnPacketReceived(PacketType.Type2);
        }

        public static void Method1(int arg1, string arg2)
        {
            Console.WriteLine($"Method1 Invoked with args: {arg1}, {arg2}");
        }

        public static string Method2(string arg1)
        {
            Console.WriteLine($"Method2 Invoked with args: {arg1}");
            return "Foo";
        }
    }

    public class NetworkingApplication
    {
        private readonly IDictionary<PacketType, DelegateInvoker> _registrations;

        public NetworkingApplication()
        {
            _registrations = new Dictionary<PacketType, DelegateInvoker>();
        }

        public void Register<TDelegate>(PacketType packetType, TDelegate @delegate, params object[] args)
            where TDelegate : Delegate
        {
            _registrations[packetType] = new DelegateInvoker(@delegate, args);
        }

        //invoke this when the packet is received
        public void OnPacketReceived(PacketType type)
        {
            if (_registrations.TryGetValue(type, out var invoker))
            {
                invoker.Invoke();
            }
        }

        private class DelegateInvoker
        {
            public DelegateInvoker(Delegate @delegate, object[] args)
            {
                Delegate = @delegate;
                Arguments = args;
            }

            private Delegate Delegate { get; }
            private object[] Arguments { get; }

            public void Invoke()
            {
                Delegate.Method.Invoke(Delegate.Target, Arguments);
            }
        }
    }





    public enum PacketType
    {
        Type1,
        Type2,
        Type3
    }
}

【讨论】:

    【解决方案2】:

    如上所述,您可以使用MethodInfo,它属于System.Reflection 命名空间。为此,首先获取对象的Type 类型,如下所示:

    var type = obj.GetType()

    之后您可以使用var methods = type.GetMethods()。这将为您提供 MethodInfo[]。使用您最喜欢的方法搜索元素。如Linq:

    var method = methods.Where(it => it.Name == __yourName__).LastOrDefault();
    

    *其中 yourName 是您的方法的名称。

    现在您有了您正在寻找的方法。使用

    获取parameters
    var parameters = method.getParameters();
    

    还有ParameterInfo[]的参数。 从那里您可以使用 parameter.ParameterType 属性获取每个参数的类型。

    这就是说要非常小心反射,它非常非常强大,但是当过度使用时会严重降低性能。

    看看System.Reflection命名空间here

    您现在可以将该方法添加到集合中,例如字典:

    var dictionary = new Dictionary<int,MethodInfo>();
    dictionary.Add(1, method);
    

    然后像这样检索它:

    var method = dictionary[1];
    

    要调用该函数,您可以使用method.Invoke() 并根据需要传入参数。

    编辑: 如果您想通过网络发送参数和函数。您可以创建一个用作 DTO 数据传输对象的新类。这个类可以有一个参数数组 (ParameterInfo[])、MethodInfo 和任何你想要的东西作为属性。

    然后您可以序列化对象(可能是 json)并将其发送到另一个系统,然后该系统可以反序列化它,并调用 MethodInfo obj

    【讨论】:

    • 我现在明白了,我向我的方法传递了一个字符串“MethodName”,它将使用反射来查找“this”中的所有方法,你提到性能是好事,因为我需要运行这就像一开始的 100 次,我不确定这会及时翻译多少,但我不知道我是否宁愿等待 1-5 分钟而不是手动编写我的数据包结构......遗憾的是 C# 没有我有“功能”类型
    • 我会说,如果只是在初始化时就可以了。此外,对于大多数应用程序,您可以在这里和那里使用它。如果您有一个需要保持响应的 UI 或可能需要从您的服务中提供服务的其他客户端,那就太糟糕了。我认为反射是最后的恢复方式。有些事情是无法通过其他方式完成的,例如“分析”源代码。
    【解决方案3】:
    using System;
    using System.Collections.Generic;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace ConsoleApp
    {
    
        public class Program
        {
            public static void Main(params string[] args)
            {
                var app = new NetworkingApplication();
                app.Register(PacketType.Type1, () =>
                {
                    Console.WriteLine("Type1 Packet is received!");
                });
            }
        }
    
        public class NetworkingApplication
        {
            private readonly IDictionary<PacketType, Action> _registrations;
    
            public NetworkingApplication()
            {
                _registrations = new Dictionary<PacketType, Action>();
            }
    
            public void Register(PacketType packetType, Action method)
            {
                _registrations[packetType] = method;
            }
    
            //invoke this when the packet is received
            public void OnPacketReceived(PacketType type)
            {
                if (_registrations.TryGetValue(type, out var action))
                {
                    action?.Invoke();
                }
            }
        }
    
    
    
        public enum PacketType
        {
            Type1,Type2,Type3
        }
    }
    

    【讨论】:

    • 是的,这是我目前所拥有的,但我需要能够传递任何类型的方法(按参数)而不是 Action
    • 你可以将任何你喜欢的方法包裹在动作中
    • 您将如何进行带参数的操作?你可以抓住他们,好的。但是 OP 正在谈论 RPC。您将如何跨流程甚至机器边界传输参数?
    猜你喜欢
    • 2018-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-16
    • 2011-10-14
    相关资源
    最近更新 更多