2016-06-30 6 views
0

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

я называю метод, как:

new TestClass<Class1, Class2>.IsAnyMemberNull(x => x.Property1.List1, object1); 

и метод выглядит следующим образом:

public bool IsAnyMemberNull(Expression<Func<T, IList<TM>>> listExpression, T entityDb) 
{ 
    var expressionsToCheck = new List<MemberExpression>(); 
    var expression = listExpression.Body as MemberExpression; 
    while (expression != null) 
    { 
     expressionsToCheck.Add(expression); 
     expression = expression.Expression as MemberExpression; 
    } 

    for (var i = expressionsToCheck.Count - 1; i >= 0; i--) 
    { 
     var objectMember = Expression.Convert(expressionsToCheck[i], typeof (object)); 
     var lambda = Expression.Lambda<Func<T, object>>(objectMember); 
     var value = lambda.Compile().Invoke(entityDb); 
     if (value == null) 
      return true; 
    } 

    return false; 
} 

При выполнении, я получаю исключение:

неправильное количество параметров поставляемого для лямбда-декларации

Любые идеи, что я сделал неправильно?

ответ

0

У вас возникла проблема в создании выражения лямбда - это проще, чем вы думали. Вы должны построить lambda для каждого expressionToCheck с оригинальным параметром выражения:

for (var i = expressionsToCheck.Count - 1; i >= 0; i--) 
{  
    var lambda = Expression.Lambda<Func<T, object>>(expressionsToCheck[i], listExpression.Parameters); 
    var value = lambda.Compile().Invoke(entityDb); 
    if (value == null) 
     return true; 
} 
1

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

Как правило, использование lambdas так случайно (скомпилирование лямбда для каждого свойства в цепочке и одном объекте) сопровождается заметным поражением производительности. Я запустил несколько тестов, и на моем компьютере, выполняющем этот метод 1000 раз для данного объекта, было задано время ~ 300-700 мс (в зависимости от количества свойств в цепочке). Dunno, сколько организаций вы имеете дело, но это не хороший знак, и лучшие замены доступны. Пожалуйста, прочтите далее ...


Вопрос в том, для чего вы это используете? Этот ваш метод напоминает мне null-conditional operators довольно много. В общем, если вы:

  • просто хотите проверить, если какое-либо имущество в цепи собственности является нулевой
  • использования C# 6
  • имеет все параметры цепь лямбда (как x => x.Property1.List1) известно во время выполнения

Тогда вы можете ломом весь этот метод IsAnyMemberNull вообще в пользу чего-то вроде:

object1.Property1.List1 == null 

Гораздо более кратким и не требуются дополнительные методы. Я побежал его 1 миллион раз, и он все еще находился в пределах ~ 23 мс. Это означает, что это на десятки тысяч быстрее, чем создание всех этих лямбдов.


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

Я взял на себя смелость удалить всю эту оболочку общего класса в пользу универсального метода.Из вашего примера использования, казалось, единственная цель универсального класса заключалась в доступе к определенному методу с параметрами generic типа класса. Это означает, что для каждого варианта метода необходимо будет создать и сохранить каждый новый класс без видимых причин, если я не ошибаюсь, для остальной части приложения. Общие методы в конкретных классах обычно предпочтительнее конкретных методов в родовых классах, в таких случаях.

Кроме того, я удалил IList, потому что я не вижу причин, по которым требуется, чтобы последний параметр был типа IList, служит для функции; это ограничивает его применимость без видимого усиления.

В целом, результат состоит в следующем:

public bool IsAnyMemberNull<TEntity, TMember>(Expression<Func<TEntity, TMember>> paramChain, TEntity entityDb) 
{ 
    var expressionsToCheck = new List<MemberExpression>(); 
    var expression = paramChain.Body as MemberExpression; 
    while (expression != null) 
    { 
     expressionsToCheck.Add(expression); 
     expression = expression.Expression as MemberExpression; 
    } 

    object value = entityDb; 
    for (var i = expressionsToCheck.Count - 1; i >= 0; i--) 
    { 
     var member = expressionsToCheck[i].Member; 
     if (member is PropertyInfo) value = (member as PropertyInfo).GetValue(value); 
     else if (member is FieldInfo) value = (member as FieldInfo).GetValue(value); 
     else throw new Exception(); // member generally should be a property or field, shouldn't it? 

     if (value == null) 
      return true; 
    } 

    return false; 
} 

После запуска этого ~ 1000 раз, потребовалось около 4-6ms; В 50-100 раз лучше, чем лямбда, хотя до сих пор господствует нулевое распространение.

Вызывается следующим образом (предполагается, что он все еще находится в TestClass, что это не нужно):

new TestClass().IsAnyMemberNull<Class1,Class2>(x => x.Property1.List1, object1); 

(Class1 и Class2 не могут быть необходимыми, благодаря типу умозаключения)


Надеюсь, что это поможет. Это не совсем то, о чем вы просили, но я боюсь, что со всем этим лямбда-нерестом вы столкнетесь с серьезными проблемами производительности; особенно если этот код должен использоваться много раз за запрос.