泛型,Lambda和反思问题[复杂]

时间:2011-04-29 01:27:26

标签: c# generics reflection lambda client-server

在我开始解释代码之前,我将首先提供我的用例,以便您了解正在发生的事情和原因。

先决条件:

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

工作流:

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

到目前为止,一切都很好,而且完美无瑕。

现在技术部分:

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

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

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

这是管理客户端写的符号脚本:

    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发送到execute方法。

好的,bla bla bla .... 所以问题是:

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

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

非常感谢你。 干杯!

2 个答案:

答案 0 :(得分:2)

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

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

答案 1 :(得分:1)

你不能做你所要求的,例如:

where listOftypes.Contains(T)

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

There are predefined ways to apply constraints

  

其中T:struct

     

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

     

其中T:class

     

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

     

其中T:new()

     

type参数必须是public   无参数构造函数。使用时   连同其他约束,   必须指定new()约束   最后。

     

其中T:

     

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

     

其中T:

     

type参数必须是或实现的   指定的接口。多   接口约束可以   指定。约束界面   也可以是通用的。

     

其中T:U

     

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