2009-12-24 7 views
5

Ich muss programmatisch überprüfen, ob eine verschachtelte Eigenschaft/Funktion Ergebnis in einem Lambda-Ausdruck ist null oder nicht. Das Problem besteht darin, dass sich der Nullwert in einer der geschachtelten Untereigenschaften befinden kann.Woher weiß ich, wenn ein Lambda-Ausdruck null ist

Beispiel. Die Funktion ist:

public static bool HasNull<T, Y>(this T someType, Expression<Func<T, Y>> input) 
    { 
     //Determine if expression has a null property 
    } 

Verwendung:

person.HasNull(d=>d.addressdetails.Street) 
person.HasNull(d=>d.addressdetails[1].Street) 
person.HasNull(d=>d.addressdetails.FirstOrDefault().Street) 
person.HasNull(d=>d.InvoiceList.FirstOrDefault().Product.Name) 

In jedem der Beispiele addressdetails oder Straße oder invoicelist oder das Produkt oder der Name null.The Code könnte eine Exception aus, wenn ich versuche, Rufen Sie die Funktion auf und eine verschachtelte Eigenschaft ist null.

Wichtig: Ich möchte keinen try catch verwenden, da dies für die Debugging-Leistung desaströs ist.

Der Grund für diesen Ansatz ist, schnell nach Werten zu suchen, während ich Nullen nicht vergessen möchte und so Ausnahmen verursachen. Dies ist praktisch für das Melden von Lösungen und Rastern, bei denen eine Null im Bericht nur leer angezeigt werden kann und keine weiteren Geschäftsregeln enthält.

verwandter Beitrag: Don't stop debugger at THAT exception when it's thrown and caught

+0

eine Reflexion von Eigenschaften und Werten des Objekts erhalten und foreach auf, dass die Erweiterung Methode? Wenn Sie eine Null erhalten, behandeln Sie es wie Sie wollen. Sie können das Objekt so oft ändern, wie Sie möchten, und es wird immer funktionieren. Es wird nicht empfohlen, Code, sondern Reflektionen zu verwenden. Es sei denn, es ist absolut notwendig. Für den Lichtgebrauch ist es in Ordnung, aber wenn es sich tausendfach widerspiegelt, müssen Sie Ihre Modelle überdenken. – ppumkin

Antwort

1

Sie würden den Ausdruck auseinander nehmen und jedes Bit wiederum zu bewerten, zu stoppen, wenn Sie ein Nullergebnis bekommen. Das wäre nicht unmöglich, aber es wäre eine Menge Arbeit.

Sind Sie sicher, dass dies weniger Arbeit ist, als nur explizite Null-Wächter in den Code zu setzen?

+0

Ja, ich hatte gehofft, dass der Code schon von jemandem geschrieben wurde. – MichaelD

0

Jeder Grund, warum Sie nicht einfach folgendes tun könnten?

bool result; 
result = addressdetails != null && addressdetails.Street != null; 
result = addressdetails != null && addressdetails.Count > 1 && addressdetails[1].Street != null; 
result = addressdetails != null && addressdetails.FirstOrDefault() != null && addressdetails.FirstOrDefault().Street != null; 
result = addressdetails != null && addressdetails.FirstOrDefault() != null && addressdetails.FirstOrDefault().Product != null && addressdetails.FirstOrDefault().Product.Name != null; 

Ich denke, dieses einfachen Booleschen Ausdrücken der beste Weg zu gehen, würde sie wegen der bedingten Auswertung arbeiten ... Wenn anding (& &) Begriffe zusammen, der erste falsche Begriff falsch zurück und den Rest stoppen von der Auswertung, so dass Sie keine Ausnahmen davon bekommen würden.

+0

Weil dies fehleranfälliger und langsamer zu schreiben ist. Ich mag sauberen Code. Oder es hat eine Null oder nicht. in .net gibt es auch einen getValueOrDefault, um einen Wert von einem nullbaren int für schnellere en sauberer Code – MichaelD

0

Obwohl es nicht die Antwort auf diese genaue Frage ist, ist der einfachste Weg, den ich kenne, um das gleiche Verhalten zu erreichen, Pfade an Eigenschaften als Aufzählungen von Eigenschaftsnamen anstelle von Aufrufketten zu übergeben.

public static bool HasNull<T, Y>(this T someType, IEnumerable<string> propertyNames) 

Dann analysieren Sie diese Aufzählungen in einem Kreis oder rekursiv mit Reflektion. Es gibt einige Nachteile, wie den Verlust des intelligenten Sinns und die statische Namenskontrolle, aber sehr einfach zu implementieren, was sie in manchen Fällen überlasten kann.

Statt zu schreiben

person.HasNull(d=>d.addressdetails.FirstOrDefault().Street) 

Sie

person.HasNull(new string[] { "addressdetails", "0", "Street" }) 
3

Es ist möglich, schreiben, aber ich bin mir nicht sicher, ob ich es empfehlen würde. Hier ist etwas, das Sie nützlich finden können: Es gibt keinen booleschen Wert zurück, sondern stattdessen den Blattwert des Ausdrucks (keine Nullreferenz).

public static class Dereferencer 
{ 
    private static readonly MethodInfo safeDereferenceMethodInfo 
     = typeof (Dereferencer).GetMethod("SafeDereferenceHelper", BindingFlags.NonPublic| BindingFlags.Static); 


    private static TMember SafeDereferenceHelper<TTarget, TMember>(TTarget target, 
                  Func<TTarget, TMember> walker) 
    { 
     return target == null ? default(TMember) : walker(target); 
    } 

    public static TMember SafeDereference<TTarget, TMember>(this TTarget target, Expression<Func<TTarget, TMember>> expression) 
    { 
     var lambdaExpression = expression as LambdaExpression; 
     if (lambdaExpression == null) 
      return default(TMember); 

     var methodCalls = new Queue<MethodCallExpression>(); 
     VisitExpression(expression.Body, methodCalls); 
     var callChain = methodCalls.Count == 0 ? expression.Body : CombineMethodCalls(methodCalls); 
     var exp = Expression.Lambda(typeof (Func<TTarget, TMember>), callChain, lambdaExpression.Parameters); 
     var safeEvaluator = (Func<TTarget, TMember>) exp.Compile(); 

     return safeEvaluator(target); 
    } 

    private static Expression CombineMethodCalls(Queue<MethodCallExpression> methodCallExpressions) 
    { 
     var callChain = methodCallExpressions.Dequeue(); 
     if (methodCallExpressions.Count == 0) 
      return callChain; 

     return Expression.Call(callChain.Method, 
           CombineMethodCalls(methodCallExpressions), 
           callChain.Arguments[1]); 
    } 

    private static MethodCallExpression GenerateSafeDereferenceCall(Type targetType, 
                    Type memberType, 
                    Expression target, 
                    Func<ParameterExpression, Expression> bodyBuilder) 
    { 
     var methodInfo = safeDereferenceMethodInfo.MakeGenericMethod(targetType, memberType); 
     var lambdaType = typeof (Func<,>).MakeGenericType(targetType, memberType); 
     var lambdaParameterName = targetType.Name.ToLower(); 
     var lambdaParameter = Expression.Parameter(targetType, lambdaParameterName); 
     var lambda = Expression.Lambda(lambdaType, bodyBuilder(lambdaParameter), lambdaParameter); 
     return Expression.Call(methodInfo, target, lambda); 
    } 

    private static void VisitExpression(Expression expression, 
             Queue<MethodCallExpression> methodCallsQueue) 
    { 
     switch (expression.NodeType) 
     { 
      case ExpressionType.MemberAccess: 
       VisitMemberExpression((MemberExpression) expression, methodCallsQueue); 
       break; 
      case ExpressionType.Call: 
       VisitMethodCallExpression((MethodCallExpression) expression, methodCallsQueue); 
       break; 
     } 
    } 

    private static void VisitMemberExpression(MemberExpression expression, 
               Queue<MethodCallExpression> methodCallsQueue) 
    { 
     var call = GenerateSafeDereferenceCall(expression.Expression.Type, 
               expression.Type, 
               expression.Expression, 
               p => Expression.PropertyOrField(p, expression.Member.Name)); 

     methodCallsQueue.Enqueue(call); 
     VisitExpression(expression.Expression, methodCallsQueue); 
    } 

    private static void VisitMethodCallExpression(MethodCallExpression expression, 
                Queue<MethodCallExpression> methodCallsQueue) 
    { 
     var call = GenerateSafeDereferenceCall(expression.Object.Type, 
               expression.Type, 
               expression.Object, 
               p => Expression.Call(p, expression.Method, expression.Arguments)); 

     methodCallsQueue.Enqueue(call); 
     VisitExpression(expression.Object, methodCallsQueue); 
    } 
} 

Sie können es auf diese Weise verwenden:

var street = person.SafeDereference(d=>d.addressdetails.Street); 
street = person.SafeDereference(d=>d.addressdetails[1].Street); 
street = person.SafeDereference(d=>d.addressdetails.FirstOrDefault().Street); 
var name = person.SafeDereference(d=>d.InvoiceList.FirstOrDefault().Product.Name); 

Achtung: Dies ist nicht vollständig getestet, es sollte Arbeit mit Methoden und Eigenschaften, aber wahrscheinlich nicht mit Erweiterungsmethoden innerhalb des Ausdrucks.

Edit: Ok, es kann keine Erweiterung Methoden für jetzt (z. B.), aber es ist immer noch möglich, die Lösung anzupassen.

+0

Wow, wenn es wirklich funktioniert, das muss großartig sein, aber personnaly möchte ich nicht diese Methode debuggen;) –

+0

nur testete es, es funktioniert, aber wie du es nicht mit Erweiterungsmethoden sagst. aber es ist ein guter Anfang – MichaelD

1

Wir brauchen definitiv einen null-sicheren Dereferenzierungsoperator in C#, aber bis dahin schauen Sie sich this question an, was eine etwas andere, aber auch saubere Lösung für das gleiche Problem bietet.

Verwandte Themen