Generics, лямбда и рефлексия вопрос [сложный] - PullRequest
1 голос
/ 29 апреля 2011

Прежде чем я начну объяснять код, я сначала приведу свой пример использования, чтобы вы могли понять, что и почему происходит.

Предварительные условия:

  • пусть будет сервер (Диспетчер очереди / Буфер в терминологии клиент-сервер)
  • пусть будет один или несколько управляющих клиентов (Производитель в терминологии клиент-сервер)
  • пусть будут клиенты (Потребители в терминологии клиент-сервер)

Рабочий процесс:

  • управляющий клиент пишет скрипт C #, отправляет на сервер
  • скрипт компилируется C # CodeDomProvider
  • script can возвращает результат CallQueue или просто выполняет что-то на сервере
  • , если происходит, сервер кэширует CallQueue
  • , в то время как другие управляющие клиенты могут отправлять новые сценарии, которые обрабатываются
  • какой-то клиент подключается к серверу и запрашивает CallQueue
  • клиент получает CallQueue и выполняет его в указанное в нем время

Все в порядке, то этот момент, и работает безупречно.

Теперь техническая часть:

CallQueue - это класс, который использует лямбда-выражения в качестве входных данных для универсальных методов и хранит данные отражения, необходимые для выполнения вызовов вочередь на стороне клиента.

Почему вся эта сложность .. лямбда-генерики и т. д.?Тип Безопасность.Управлять клиентами глупо, и им нужно знать лишь несколько методов сценариев, а не настоящих программистов.Таким образом, отправка целого числа вместо строки или присвоение имени свойству с помощью опечатки может происходить часто.Вот почему сценарий использует лямбда-выражения и обобщения для ограничения того, что кто-то может напечатать.

Это компилируется на сервере и отклоняется, если не так.

Это символический сценарий, который управляющий клиент написал бы:

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

До сих пор все хорошо и безопасно, но есть некоторые последствия:

  • клиент точно знает, какие объекты должны быть отправлены в метод execute, жестко запрограммированный в конструкции.
  • управляющий клиент может написать сценарий, который работает с объектами, которые не будут отправляться, но все же компилироваться на сервере, посколькуТипы существуют

Возьмем, к примеру:

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

Это скомпилируется, но произойдет сбой, когда клиент выполнит его, поскольку он не отправляет Int32 в метод execute.

Хорошо, бла бла бла .... Итак, вопрос в следующем:

Как ограничить эти универсальные методы набором разрешенных типов, а не путем определения наследования:

 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 ]

2 голосов
/ 29 апреля 2011

Итак, есть две вещи, которые вы могли бы сделать. Во-первых, убедитесь, что все ваши типы в listOftypes являются производными от одного базового класса или реализуют один и тот же интерфейс, и в этом случае вы можете просто использовать где.

Учитывая, что ваш вопрос, по-видимому, указывает на то, что это не то, что вам нужно, вы можете получить лучшие отчеты об ошибках во время выполнения, посмотрев на typeof(T) и посмотрев, содержится ли этот тип в listOfTypes. Не так хорошо, как вы хотите, но вы ограничены.

1 голос
/ 29 апреля 2011

Вы не можете делать то, что просите, например:

where listOftypes.Contains(T)

Но вы можете применять несколько типов, но вам нужно набирать их, а не поддерживать коллекцию, как вы предлагаете.

Существуют предопределенные способы применения ограничений :

, где T: struct

Аргумент типа должен быть значением тип. Любой тип значения, кроме Nullable можно указать Смотрите Использование Nullable Типы (Руководство по программированию в C #) информация.

где T: класс

Аргумент типа должен быть ссылкой тип; это относится и к любому классу, тип интерфейса, делегата или массива.

, где T: новый ()

Аргумент типа должен иметь открытый конструктор без параметров. Когда используется вместе с другими ограничениями ограничение new () должно быть указано последний.

где T:

Аргумент типа должен быть или получить из указанного базового класса.

где T:

Аргумент типа должен быть или реализован указанный интерфейс. множественный ограничения интерфейса могут быть указано. Ограничивающий интерфейс также может быть универсальным.

где T: U

Аргумент типа, указанный для T must быть или получить из аргумента поставляется для U.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...