2016-06-30 27 views
0

Ich habe wirklich Mühe, eine generische Klasse zu schreiben, die überprüfen muss, ob alle Mitglieder eines übergebenen Ausdrucks nicht null sind, indem das tatsächliche Objekt aufgerufen wird.Iterieren eines Ausdrucksbaums, um nach Null zu suchen

Ich rufe die Methode wie:

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

und die Methode wie folgt aussieht:

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

Bei der Ausführung, erhalte ich die Ausnahme:

falsche Anzahl von Parametern geliefert für Lambda-Deklaration

Irgendwelche Ideen, was habe ich falsch gemacht?

Antwort

0

Sie haben ein Problem bei der Lambda-Expression-Erstellung - es ist einfacher als Sie dachten.

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

Während es möglich ist, Ihren Code zu bekommen arbeiten, so dass ein korrektes lambda gebaut und zusammengestellt, wiederholt zusammengestellt lambda mit null Kontrolle zu erreichen: Sie sollten expressionToCheck mit ursprünglichem Ausdruck Parametern lambda für jede bauen ist ein teurer Overkill.

Im Allgemeinen kommt die Verwendung von lambdas so beiläufig (ein Lambda für jede Eigenschaft in der Kette und ein einzelnes Objekt zu kompilieren) mit einem bemerkenswerten Leistungseinbruch. Ich habe einige Tests ausgeführt, und auf meinem Computer ergab diese Methode 1000 Mal für ein gegebenes Objekt eine Zeit von ~ 300-700 ms (abhängig von der Anzahl der Eigenschaften in der Kette). Weiß nicht, mit wie vielen Entitäten Sie es zu tun haben, aber es ist kein gutes Zeichen, und es stehen bessere Ersatzmöglichkeiten zur Verfügung. Bitte weiterlesen ...


Die Frage ist, wofür verwenden Sie das? Diese Methode erinnert mich sehr an null-conditional operators. Im Allgemeinen, wenn Sie:

  • will nur prüfen, ob eine Eigenschaft in der Eigenschaftenkette ein Null-
  • # Verwendung C 6
  • haben alle Parameterkette lambdas (wie x => x.Property1.List1) zur Laufzeit bekannt

Dann könnten Sie das ganze IsAnyMemberNull Methode Schrott zusammen für so etwas wie:

object1.Property1.List1 == null 

Viel genauer und keine zusätzlichen Methoden sind erforderlich. Ich lief es 1 Million Mal und es war immer noch in ~ 23ms Zeit. Es bedeutet, dass es Dutzende schneller ist, als all diese Lambdas zu erstellen.


Wenn Sie nicht null-koaleszierenden Betreiber für welchen Gründen auch immer (vor allem, wenn der Ausdruck gebaut wird dynamisch) verwenden können, können Sie stattdessen entscheiden Feld/Objekt Reflexion zu verwenden.

Ich habe mir die Freiheit genommen, all diese generische Klassenumhüllung zugunsten einer generischen Methode zu entfernen.In Ihrem Verwendungsbeispiel schien der einzige Zweck der generischen Klasse darin zu bestehen, auf eine spezifische Methode mit generischen Typparametern der Klasse zuzugreifen. Es bedeutet, dass für jede Variation der Methode eine neue Klasse erstellt und gespeichert werden muss, ohne erkennbaren Grund, wenn ich mich nicht irre, für die restliche Lebensdauer der Anwendung. Generische Methoden in bestimmten Klassen werden in Fällen wie diesen im Allgemeinen bestimmten Methoden in generischen Klassen vorgezogen.

Außerdem habe ich IList entfernt, weil ich keinen Grund sehe, wie die Verwendung des letzten Parameters vom Typ IList dem Zweck der Funktion dient; es beschränkt seine Anwendbarkeit nur ohne sichtbaren Gewinn.

Insgesamt ist das Ergebnis folgendes:

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

Nach dem Ausführen dieser ~ 1000 mal, es dauerte etwa 4-6ms; 50-100 Mal besser als Lambdas, obwohl die Null-Vermehrung noch immer vorherrscht.

Genannt wie folgt (vorausgesetzt, es befindet sich immer noch in Testclass, die es nicht braucht):

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

(Class1 und Class2 vielleicht nicht notwendig sein, durch Folgerung geben)


Hoffe, das hilft. Es ist nicht genau das, wonach du gefragt hast, aber ich fürchte, mit all dem Lambda-Laichen könntest du in ernsthafte Leistungsprobleme geraten; vor allem, wenn dieser Code mehrmals pro Anfrage verwendet werden sollte.