泛型、Lambda 和反射问题 [复杂]

发布于 2024-11-04 04:21:12 字数 7855 浏览 0 评论 0原文

在开始解释代码之前,我将首先给出我的用例,以便您可以了解发生了什么以及为什么发生。

先决条件:

  • 要有一台服务器(客户端服务器术语中的队列调度程序/缓冲区)
  • 要有一个或多个管理客户端(客户端服务器术语中的生产者)
  • 要有客户端(客户端服务器术语中的消费者)

工作流程:

  • 管理客户端编写一个 C#脚本,发送到服务器
  • 脚本由 C# CodeDomProvider 编译,
  • 脚本可以返回 CallQueue 结果,或者
  • 如果发生服务器缓存 CallQueue,则
  • 在服务器上执行某些操作,同时其他管理客户端可以发送新的脚本,这些脚本是已处理
  • 一些客户端连接到服务器并请求 CallQueue
  • 客户端获取 CallQueue 并在其中指定的时间执行它

到目前为止一切正常,并且工作完美。

现在是技术部分:

CallQueue 是一个类,它使用 lambda 表达式作为通用方法的输入,并存储在客户端队列中执行调用所需的反射数据。

为什么所有这些复杂性.. lambda 泛型等?类型安全。 管理客户端是愚蠢的,只需要知道一些脚本方法就可以编写,而不是真正的程序员。因此,发送整数而不是字符串或用拼写错误命名属性可能很常见。这就是脚本使用 lambda 和泛型来限制某人可以输入的内容的原因。

这将在服务器上进行编译,如果错误则被拒绝。

这是一个管理客户端将编写的符号脚本:

    CallQueue cc = new CallQueue(new DateTime(2012,12,21,10,0,0));

    // set property Firstname to "test person"
    cc.AddPropertySet<Person, string>(x => x.FirstName, "test person");

    // call method ChangeDescription with parameter "test order"
    cc.AddVoidMethodCall<Order, string>(x => x.ChangeDescription, "test order");

    // call method Utility.CreateGuid and send result to Person.PersonId
    cc.AddFunctionCallWithDestinationPropertySet<Utility, Guid, Person>(src => src.CreateGuid, dst => dst.PersonId);

客户端将获得一个 CallQueue 实例并像这样执行它:

    Order order = new Order();
    Person person = new Person();
    Utility util = new Utility();

    CallQueue cc = /* already got from server */;

    // when you call this execute the call queue will do the work
    // on object instances sent inside the execute method
    cc.Execute(new List<object> { order, person, util });

到目前为止,一切都很好且类型安全,但有一些含义:

  • 客户端确切地知道必须将哪些对象发送到执行方法,由设计硬编码
  • 管理客户端可以编写脚本,该脚本对不会发送到但仍然在服务器上编译的对象进行操作,因为类型存在

举个例子:

 cc.AddFunctionCall<Int32, string>(x => x.ToString);

这将编译,但当客户端执行它时会失败,因为它不存在将 Int32 发送到执行方法中。

好吧,巴拉巴拉…… 所以问题是:

如何用一组允许的类型来限制这些泛型方法 - 不是通过定义继承:

 where T : something

而是更像

 where listOftypes.Contains(T)

或任何限制可以进入其中的内容的等效解决方案...... 我没有找到对此的通用约束...

这是 CallQueue 类:

   [Serializable]
   public class CallQueue : List<CallQueue.Call>
    {
        [Serializable]
        public struct Call {
            public MethodInfo Method;
            public MethodInfo DestinationProperty;
            public object[] Parameters;
            public Call(MethodInfo m, MethodInfo d, object[] p) {
                Method = m;
                Parameters = p;
                DestinationProperty = d;
            }
        }

        public CallQueue(DateTime when) {
            ScheduledTime = when;
        }

        public DateTime ScheduledTime
        {
            get;
            set;
        }

        public void AddFunctionCall<TSrcClass, TResult>(Expression<Func<TSrcClass, Func<TResult>>> expr)
        {
            MethodResolver((LambdaExpression)expr, null, new object[] {});
        }

        public void AddFunctionCallWithDestinationPropertySet<TSrcClass, TResult, TDest>(Expression<Func<TSrcClass, Func<TResult>>> expr, Expression<Func<TDest, TResult>> dest)
        {
            MethodResolver((LambdaExpression)expr, dest,  new object[] { });
        }

        public void AddFunctionCallWithDestinationPropertySet<TSrcClass, TParam1, TResult, TDest>(Expression<Func<TSrcClass, Func<TParam1, TResult>>> expr, TParam1 param, Expression<Func<TDest, TResult>> dest)
        {
            MethodResolver((LambdaExpression)expr, dest, new object[] { param });
        }

        public void AddFunctionCall<TSrcClass, TParam1, TResult>(Expression<Func<TSrcClass, Func<TParam1, TResult>>> expr, TParam1 param)
        {
            MethodResolver((LambdaExpression)expr, null, new object[] {param});
        }

        public void AddFunctionCallWithDestinationPropertySet<TSrcClass, TParam1, TParam2, TResult, TDest>(Expression<Func<TSrcClass, Func<TParam1, TParam2, TResult>>> expr, TParam1 param, TParam2 param2, Expression<Func<TDest, TResult>> dest)
        {
            MethodResolver((LambdaExpression)expr, dest, new object[] { param, param2 });
        }

        public void AddFunctionCall<TSrcClass, TParam1, TParam2, TResult>(Expression<Func<TSrcClass, Func<TParam1, TParam2, TResult>>> expr, TParam1 param, TParam2 param2)
        {
            MethodResolver((LambdaExpression)expr, null, new object[] { param, param2 });
        }

        public void AddVoidMethodCall<TSrcClass, TParam>(Expression<Func<TSrcClass, Action<TParam>>> expr, TParam param)
        {
            MethodResolver((LambdaExpression)expr, null, new object[] { param });
        }

        public void AddVoidMethodCall<TSrcClass, TParam1, TParam2>(Expression<Func<TSrcClass, Action<TParam1, TParam2>>> expr, TParam1 param, TParam2 param2)
        {
            MethodResolver((LambdaExpression)expr, null, new object[] { param, param2 });
        }

        public void AddVoidMethodCall<TSrcClass, TParam1, TParam2, TParam3>(Expression<Func<TSrcClass, Action<TParam1, TParam2, TParam3>>> expr, TParam1 param, TParam2 param2, TParam3 param3)
        {
            MethodResolver((LambdaExpression)expr, null, new object[] { param, param2, param3 });
        }

        public void AddPropertySet<TSrcClass, TParam1>(Expression<Func<TSrcClass, TParam1>> expr, TParam1 param)
        {
            PropertyResolver((LambdaExpression)expr, new object[] {param});
        }

        public void Execute(List<object> instances) {
            foreach (var call in this) {
                var owner = instances.Find(o => o.GetType() == call.Method.DeclaringType);
                if (call.DestinationProperty != null)
                {
                    // execute method get result and set to destination property
                    object res = call.Method.Invoke(owner, call.Parameters);
                    var destOwner = instances.Find(o => o.GetType() == call.DestinationProperty.DeclaringType);
                    call.DestinationProperty.Invoke(destOwner, new object[] {res});
                }
                else 
                {
                    // just execute method
                    call.Method.Invoke(owner, call.Parameters);
                }
            }
        }

        private void MethodResolver(LambdaExpression expr, LambdaExpression dest, object[] param)
        {
            var body = (UnaryExpression)expr.Body;
            var methodCall = (MethodCallExpression)body.Operand;
            var constant = (ConstantExpression)methodCall.Arguments[2];
            var method = (MethodInfo)constant.Value;

            MethodInfo dmethod = null;

            if (dest != null)
            {
                var prop = (MemberExpression)dest.Body;
                var propMember = (PropertyInfo)prop.Member;
                dmethod = propMember.GetSetMethod();
            }

            this.Add(new Call(method, dmethod, param));
            Console.WriteLine(method.Name);
        }

        private void PropertyResolver(LambdaExpression expr, object[] param)
        {
            var prop = (MemberExpression)expr.Body;
            var propMember = (PropertyInfo)prop.Member;
            var method = propMember.GetSetMethod();
            this.Add(new Call(method, null, param));
            Console.WriteLine(method.Name);
        }
    }

非常感谢。 干杯!

Before I start explaining the code I will first give my use case so you can understand what and why is going on.

Prerequisites:

  • let there be a server (Queue dispatcher / Buffer in client server terminology)
  • let there be one or more managing clients (Producer in client server terminology)
  • let there be clients (Consumers in client server terminology)

Workflow:

  • managing client writes a C# script, sends to server
  • script gets compiled by C# CodeDomProvider
  • script can give back a CallQueue result, or just execute something on server
  • if occurs server caches the CallQueue
  • in the meanwhile other managing clients can send new scripts that are processed
  • some client connects to server and asks for a CallQueue
  • client gets the CallQueue and executes it at the time specified in it

Everything is ok up to this point, and works flawlessly.

Now the technical part:

The CallQueue is a class that uses lambda expressions as input on generic methods, and stores reflection data required to execute the calls in the queue on client side.

Why all this complexity.. lambda generics etc? Type Safety.
Managing clients are dumb and need to know only a few script methods to write, not real programmers. So sending an integer instead of a string or naming a property with a typo could be often. This is why the script uses lambdas and generics to restrict what someone can type.

This gets compiled on server and rejected if wrong.

This is a symbolic script that a managing client would write:

    CallQueue cc = new CallQueue(new DateTime(2012,12,21,10,0,0));

    // set property Firstname to "test person"
    cc.AddPropertySet<Person, string>(x => x.FirstName, "test person");

    // call method ChangeDescription with parameter "test order"
    cc.AddVoidMethodCall<Order, string>(x => x.ChangeDescription, "test order");

    // call method Utility.CreateGuid and send result to Person.PersonId
    cc.AddFunctionCallWithDestinationPropertySet<Utility, Guid, Person>(src => src.CreateGuid, dst => dst.PersonId);

What a client would get is a CallQueue instance and execute it like this:

    Order order = new Order();
    Person person = new Person();
    Utility util = new Utility();

    CallQueue cc = /* already got from server */;

    // when you call this execute the call queue will do the work
    // on object instances sent inside the execute method
    cc.Execute(new List<object> { order, person, util });

Up to here everything is fine and typesafe, but there are implications:

  • client exactly know which objects must be sent to the execute method, hardcoded by design
  • managing client can write script which operates on objects that won't be sent into but still compile on server because Types exist

Take for example:

 cc.AddFunctionCall<Int32, string>(x => x.ToString);

This will compile but will fail when the client executes it because it does not send an Int32 into the execute method.

Ok, bla bla bla....
So the question is:

How to restrict those generic methods with a set of allowed Types - not by defining inheritance:

 where T : something

but more like

 where listOftypes.Contains(T)

Or any equivalent solution that restricts what can go into it...
I did not find a generic contraint for this...

Here is the CallQueue class:

   [Serializable]
   public class CallQueue : List<CallQueue.Call>
    {
        [Serializable]
        public struct Call {
            public MethodInfo Method;
            public MethodInfo DestinationProperty;
            public object[] Parameters;
            public Call(MethodInfo m, MethodInfo d, object[] p) {
                Method = m;
                Parameters = p;
                DestinationProperty = d;
            }
        }

        public CallQueue(DateTime when) {
            ScheduledTime = when;
        }

        public DateTime ScheduledTime
        {
            get;
            set;
        }

        public void AddFunctionCall<TSrcClass, TResult>(Expression<Func<TSrcClass, Func<TResult>>> expr)
        {
            MethodResolver((LambdaExpression)expr, null, new object[] {});
        }

        public void AddFunctionCallWithDestinationPropertySet<TSrcClass, TResult, TDest>(Expression<Func<TSrcClass, Func<TResult>>> expr, Expression<Func<TDest, TResult>> dest)
        {
            MethodResolver((LambdaExpression)expr, dest,  new object[] { });
        }

        public void AddFunctionCallWithDestinationPropertySet<TSrcClass, TParam1, TResult, TDest>(Expression<Func<TSrcClass, Func<TParam1, TResult>>> expr, TParam1 param, Expression<Func<TDest, TResult>> dest)
        {
            MethodResolver((LambdaExpression)expr, dest, new object[] { param });
        }

        public void AddFunctionCall<TSrcClass, TParam1, TResult>(Expression<Func<TSrcClass, Func<TParam1, TResult>>> expr, TParam1 param)
        {
            MethodResolver((LambdaExpression)expr, null, new object[] {param});
        }

        public void AddFunctionCallWithDestinationPropertySet<TSrcClass, TParam1, TParam2, TResult, TDest>(Expression<Func<TSrcClass, Func<TParam1, TParam2, TResult>>> expr, TParam1 param, TParam2 param2, Expression<Func<TDest, TResult>> dest)
        {
            MethodResolver((LambdaExpression)expr, dest, new object[] { param, param2 });
        }

        public void AddFunctionCall<TSrcClass, TParam1, TParam2, TResult>(Expression<Func<TSrcClass, Func<TParam1, TParam2, TResult>>> expr, TParam1 param, TParam2 param2)
        {
            MethodResolver((LambdaExpression)expr, null, new object[] { param, param2 });
        }

        public void AddVoidMethodCall<TSrcClass, TParam>(Expression<Func<TSrcClass, Action<TParam>>> expr, TParam param)
        {
            MethodResolver((LambdaExpression)expr, null, new object[] { param });
        }

        public void AddVoidMethodCall<TSrcClass, TParam1, TParam2>(Expression<Func<TSrcClass, Action<TParam1, TParam2>>> expr, TParam1 param, TParam2 param2)
        {
            MethodResolver((LambdaExpression)expr, null, new object[] { param, param2 });
        }

        public void AddVoidMethodCall<TSrcClass, TParam1, TParam2, TParam3>(Expression<Func<TSrcClass, Action<TParam1, TParam2, TParam3>>> expr, TParam1 param, TParam2 param2, TParam3 param3)
        {
            MethodResolver((LambdaExpression)expr, null, new object[] { param, param2, param3 });
        }

        public void AddPropertySet<TSrcClass, TParam1>(Expression<Func<TSrcClass, TParam1>> expr, TParam1 param)
        {
            PropertyResolver((LambdaExpression)expr, new object[] {param});
        }

        public void Execute(List<object> instances) {
            foreach (var call in this) {
                var owner = instances.Find(o => o.GetType() == call.Method.DeclaringType);
                if (call.DestinationProperty != null)
                {
                    // execute method get result and set to destination property
                    object res = call.Method.Invoke(owner, call.Parameters);
                    var destOwner = instances.Find(o => o.GetType() == call.DestinationProperty.DeclaringType);
                    call.DestinationProperty.Invoke(destOwner, new object[] {res});
                }
                else 
                {
                    // just execute method
                    call.Method.Invoke(owner, call.Parameters);
                }
            }
        }

        private void MethodResolver(LambdaExpression expr, LambdaExpression dest, object[] param)
        {
            var body = (UnaryExpression)expr.Body;
            var methodCall = (MethodCallExpression)body.Operand;
            var constant = (ConstantExpression)methodCall.Arguments[2];
            var method = (MethodInfo)constant.Value;

            MethodInfo dmethod = null;

            if (dest != null)
            {
                var prop = (MemberExpression)dest.Body;
                var propMember = (PropertyInfo)prop.Member;
                dmethod = propMember.GetSetMethod();
            }

            this.Add(new Call(method, dmethod, param));
            Console.WriteLine(method.Name);
        }

        private void PropertyResolver(LambdaExpression expr, object[] param)
        {
            var prop = (MemberExpression)expr.Body;
            var propMember = (PropertyInfo)prop.Member;
            var method = propMember.GetSetMethod();
            this.Add(new Call(method, null, param));
            Console.WriteLine(method.Name);
        }
    }

Thank you very much.
Cheers!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

再见回来 2024-11-11 04:21:12

所以你可以做两件事。一是确保 listOftypes 中的所有类型都派生自相同的基类,或实现相同的接口,在这种情况下,您可以只使用 where。

鉴于您的问题似乎表明这不是您想要的,您可以通过查看 typeof(T) 并查看该类型是否包含在 中,从而在运行时获得更好的错误报告类型列表。没有你想要的那么好,但你是有限的。

So there are two things you could do. One is make sure all your types in listOftypes derive from the same baseclass, or implement the same interface, in which case you could just use where.

Given that your question seems to indicate this is not what you want, you might be able to get better error reporting at runtime by looking at typeof(T) and seeing if that type is contained within listOfTypes. Not as nice as what you want, but you are limited.

蓝海 2024-11-11 04:21:12

您无法执行您所要求的操作,例如:

where listOftypes.Contains(T)

但是您可以应用多种类型,但您确实需要将它们键入而不是按照您的建议维护集合。

有预定义的方法来应用约束

其中 T:结构

类型参数必须是一个值
类型。除 Nullable 之外的任何值类型
可以指定。请参阅使用可空值
类型(C# 编程指南)了解更多信息
信息。

其中 T :类

类型参数必须是引用
类型;这也适用于任何类别,
接口、委托或数组类型。

其中 T : new()

类型参数必须具有公共类型
无参数构造函数。使用时
与其他约束一起,
必须指定 new() 约束
最后。

其中 T :

类型参数必须是或派生
来自指定的基类。

其中 T :

类型参数必须是或实现
指定的接口。多种的
接口约束可以是
指定的。约束接口
也可以是通用的。

其中 T : U

为 T 提供的类型参数必须
是或源自论证
为美国供应。

You can't do what you're asking, such as:

where listOftypes.Contains(T)

But you can apply multiple types, but you do need to type them out rather than maintain a collection, as you suggest.

There are predefined ways to apply constraints:

where T: struct

The type argument must be a value
type. Any value type except Nullable
can be specified. See Using Nullable
Types (C# Programming Guide) for more
information.

where T : class

The type argument must be a reference
type; this applies also to any class,
interface, delegate, or array type.

where T : new()

The type argument must have a public
parameterless constructor. When used
together with other constraints, the
new() constraint must be specified
last.

where T :

The type argument must be or derive
from the specified base class.

where T :

The type argument must be or implement
the specified interface. Multiple
interface constraints can be
specified. The constraining interface
can also be generic.

where T : U

The type argument supplied for T must
be or derive from the argument
supplied for U.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文