2015-05-27 6 views
6

Wenn ich das deserialisierte XML-Ergebnis in den xsd-generierten Baum der Objekte bekomme und ein tiefes Objekt in diesem Baum a.b.c.d.e.f verwenden möchte, gibt es eine Ausnahme, wenn ein Knoten in diesem Abfragepfad fehlt.Wie verwende ich Expression Tree, um sicher auf den Pfad von nullfähigen Objekten zuzugreifen?

if(a.b.c.d.e.f != null) 
    Console.Write("ok"); 

Ich möchte für null Kontrolle zu vermeiden, für jede Ebene wie folgt aus:

if(a != null) 
if(a.b != null) 
if(a.b.c != null) 
if(a.b.c.d != null) 
if(a.b.c.d.e != null) 
if(a.b.c.d.e.f != null) 
    Console.Write("ok"); 

Erste Lösung ist Erweiterung Methode zu implementieren erhalten, die dies ermöglicht:

if(a.Get(o=>o.b).Get(o=>o.c).Get(o=>o.d).Get(o=>o.e).Get(o=>o.f) != null) 
    Console.Write("ok"); 

Zweite Lösung ist Implementieren Sie die Methode "Get (string) extension" und verwenden Sie Reflection, um ein Ergebnis zu erhalten, das wie folgt aussieht:

if(a.Get("b.c.d.e.f") != null) 
    Console.Write("ok"); 

dritte Lösung könnte sein ExpandoObject zu implementieren und dynamische Art verwenden, um zur Folge haben wie folgt aussehen zu erhalten:

dynamic da = new SafeExpando(a); 
if(da.b.c.d.e.f != null) 
    Console.Write("ok"); 

Aber letzten 2 Lösungen nicht Vorteile der starke Typisierung und IntelliSense geben.

ich denke, die beste vierte Lösung sein könnte, die mit Expression Trees umgesetzt werden können:

if(Get(a.b.c.d.e.f) != null) 
    Console.Write("ok"); 

oder

if(a.Get(a=>a.b.c.d.e.f) != null) 
    Console.Write("ok"); 

ich bereits umgesetzt 1. und 2. Lösungen.

Hier ist, wie erste Lösung sieht so aus:

[DebuggerStepThrough] 
public static To Get<From,To>(this From @this, Func<From,To> get) 
{ 
    var ret = default(To); 
    if(@this != null && [email protected](default(From))) 
     ret = get(@this); 

    if(ret == null && typeof(To).IsArray) 
     ret = (To)Activator.CreateInstance(typeof(To), 0); 

    return ret; 
} 

Wie 4. Lösung, wenn möglich, implementieren?

Auch wäre es interessant zu sehen, wie die dritte Lösung zu implementieren, wenn möglich.

+0

[Diese Frage] (http://stackoverflow.com/questions/3897249/how-to-avoid-multiple-if-null-checks) hat zwei Links zu Fragen mit Antworten. Einer hängt vom Roslyn-Compiler ab; der andere ist ein einfacher Codeschnipsel, der den Trick macht. –

+0

Ich lese die eigentliche Frage und die Antworten oben adressieren nicht direkt Ihre Frage, aber sind relevant und interessant –

+0

Ich experimentierte mit diesem vor kurzem und vielleicht würde meine Lösung interessierend zu Ihnen sein. Ich habe es [hier] (http://codereview.stackexchange.com/questions/116798/improved-nullguard-v3-that-supports-property-chains-methods-and-ignores-value-t) bei Code Review gepostet. Ich habe meine eigene Implementierung geschrieben, weil ich etwas mehr wollte, das einfach gegen Null geht ;-) Ihre Frage und Servys Antwort haben mich inspiriert, etwas anderes zu versuchen. – t3chb0t

Antwort

12

Also der Ausgangspunkt ist ein Ausdruck Besucher. Auf diese Weise können wir alle Mitgliedzugriffe innerhalb eines bestimmten Ausdrucks finden. Dies hinterlässt uns die Frage, was wir für jeden Mitgliederzugriff tun müssen.

Also die erste Sache ist, rekursiv auf den Ausdruck zu besuchen, auf dem auf das Mitglied zugegriffen wird. Von dort können wir Expression.Condition verwenden, um einen bedingten Block zu erstellen, der den bearbeiteten zugrundeliegenden Ausdruck mit null vergleicht, und null zurückgibt, wenn der ursprüngliche Ausgangsausdruck wahr ist, wenn dies nicht der Fall ist.

Beachten Sie, dass wir Implementierungen sowohl für Member- als auch Methodenaufrufe bereitstellen müssen, aber der Prozess für beide ist im Wesentlichen identisch.

Wir fügen auch eine Überprüfung hinzu, so dass der zugrundeliegende Ausdruck null ist (das heißt, es gibt keine Instanz und es ist ein statisches Mitglied) oder wenn es einen nicht nullfähigen Typ ist, verwenden wir einfach das Basisverhalten stattdessen.

public class MemberNullPropogationVisitor : ExpressionVisitor 
{ 
    protected override Expression VisitMember(MemberExpression node) 
    { 
     if (node.Expression == null || !IsNullable(node.Expression.Type)) 
      return base.VisitMember(node); 

     var expression = base.Visit(node.Expression); 
     var nullBaseExpression = Expression.Constant(null, expression.Type); 
     var test = Expression.Equal(expression, nullBaseExpression); 
     var memberAccess = Expression.MakeMemberAccess(expression, node.Member); 
     var nullMemberExpression = Expression.Constant(null, node.Type); 
     return Expression.Condition(test, nullMemberExpression, node); 
    } 

    protected override Expression VisitMethodCall(MethodCallExpression node) 
    { 
     if (node.Object == null || !IsNullable(node.Object.Type)) 
      return base.VisitMethodCall(node); 

     var expression = base.Visit(node.Object); 
     var nullBaseExpression = Expression.Constant(null, expression.Type); 
     var test = Expression.Equal(expression, nullBaseExpression); 
     var memberAccess = Expression.Call(expression, node.Method); 
     var nullMemberExpression = Expression.Constant(null, MakeNullable(node.Type)); 
     return Expression.Condition(test, nullMemberExpression, node); 
    } 

    private static Type MakeNullable(Type type) 
    { 
     if (IsNullable(type)) 
      return type; 

     return typeof(Nullable<>).MakeGenericType(type); 
    } 

    private static bool IsNullable(Type type) 
    { 
     if (type.IsClass) 
      return true; 
     return type.IsGenericType && 
      type.GetGenericTypeDefinition() == typeof(Nullable<>); 
    } 
} 

Wir können dann machen eine Verlängerung Methode erstellen, um es Aufruf einfacher:

public static Expression PropogateNull(this Expression expression) 
{ 
    return new MemberNullPropogationVisitor().Visit(expression); 
} 

Neben einer, der eine Lambda, anstatt jeden Ausdruck akzeptiert und kann eine kompilierte Delegierten zurück:

Hinweis: Um Fälle zu unterstützen, in denen das aufgerufene Member in einen nicht nullbaren Wert aufgelöst wird, ändern wir den Typ dieser Ausdrücke so, dass sie nullbar sind, indemverwendet wird. Dies ist ein Problem mit diesem letzten Ausdruck, da es ein Func<T> sein muss, und es wird nicht übereinstimmen, wenn T nicht auch aufgehoben wird. Obwohl es nicht ideal ist (im Idealfall würden Sie diese Methode niemals mit einem Nullwertzeichen T aufrufen, aber es gibt keine gute Möglichkeit, dies in C# zu unterstützen), koaleszieren wir den endgültigen Wert mit dem Standardwert für diesen Typ if notwendig.

(Sie können trivialerweise ändern dies eine Lambda akzeptieren einen Parameter zu akzeptieren, und in einem Wert übergeben, aber Sie können genauso einfach dicht über diesen Parameter statt, so dass ich sehe keinen wirklichen Grund.)


Es ist auch erwähnenswert, dass in C# 6.0, wenn es tatsächlich veröffentlicht wird, wir eine tatsächliche Null propogation Operator (?.) haben, was all dies sehr unnötig macht. Sie können schreiben:

if(a?.b?.c?.d?.e?.f != null) 
    Console.Write("ok"); 

und haben genau die Semantik, die Sie suchen.

+0

Expression.Constant (null, node.Type) für Werttyp Ausnahme auslösen, ersetzen Sie Null durch den Standardwert des Typs (http://StackOverflow.com/a/2490274/440030) behobenen Problem. –

+0

@RezaArabQaeni Das ist nicht wirklich die richtige Semantik für einen Null-Propagierungsoperator. Es sollte das Ergebnis auf einen NULL-fähigen Typ heben, aber die Einschränkungen der Sprache erlauben dies nicht wirklich in allen Fällen, nur bestimmte könnten unterstützt werden. Insbesondere wenn der letzte Ausdruck aufgehoben wird, stimmt er nicht mehr mit der Signatur des Delegaten überein, und diese Änderung ist wirklich jenseits dessen, was C# effektiv unterstützen kann. Den Standardwert in diesem einen Fall zu verwenden, ist zumindest besser als immer, denke ich, also habe ich das jetzt gemacht. – Servy

+0

Ok, Sie haben Recht, also was ist Ihre Vorstellung davon, wenn der Knotentyp Werttyp ist, dann anstelle von Expression.Condition (um zu überprüfen, ob die Eigenschaft null ist) einen einfachen Ausdruck ohne Nullbedingung zurückgeben, obwohl ich nicht weiß, was die Implementierung ist Dieser einfache Ausdruck. –

Verwandte Themen