2009-12-24 20 views
5

Tengo que comprobar mediante programación si un resultado de la propiedad/función anidada en una expresión lambda es nulo o no. El problema es que el nulo podría estar en cualquiera de las subpropiedades anidadas.¿Cómo sé cuando una expresión lambda es nulo

Ejemplo. La función es:

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

Uso:

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) 

En cualquiera de los ejemplos o addressdetails la calle, o invoicelist o el producto o el nombre podría ser null.The código producirá una excepción si intento invocar la función y alguna propiedad anidada es nula.

Importante: No quiero usar un intento de captura para esto porque es desastrosa para el desempeño de depuración.

La razón de este enfoque es para comprobar rápidamente para valores Mientras que no quiero olvidar ningún nulos y así causar excepciones. Esto es útil para informar soluciones y grillas donde un nulo en el informe puede mostrarse vacío y no tiene reglas comerciales adicionales.

entrada relacionada: Don't stop debugger at THAT exception when it's thrown and caught

+0

obtener un reflejo de las propiedades y valores del objeto y forEach en que el uso del método de extensión? Si obtienes un nulo, tráelo como quieras. Puedes cambiar el objeto tanto como quieras y siempre funcionará. No se recomienda el código pero la reflexión para la producción ... a menos que sea absolutamente necesario. Para el uso ligero, está bien, pero si va a reflejar miles y miles de veces, debe replantear sus modelos. – ppumkin

Respuesta

1

Usted tendría que tomar la expresión separados y evaluar cada bit, a su vez, parando cuando se tiene un resultado nulo. Esto no sería imposible de ninguna manera, pero sería mucho trabajo.

¿Estás seguro de que esto es menos trabajo que acaba de poner guardias nulos explícitos en el código?

+0

sí, esperaba que el código ya haya sido escrito por alguien. – MichaelD

0

¿Por qué no puedes hacer lo siguiente?

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; 

Creo que estas expresiones booleanas simples sería el mejor camino a seguir, que funcionan debido a la evaluación condicional ... Cuando anding (& &) términos juntos, la primera falsa término devolverá falso y dejan el resto de ser evaluado, por lo que no obtendría ninguna excepción al hacer esto.

+0

Porque es más propenso a errores y más lento de escribir. Me gusta el código limpio O tiene un nulo, o no. en .net también hay un getValueOrDefault para recuperar un valor de un int nullable para un código de limpiador más rápido – MichaelD

0

Aunque no es la respuesta a esta cuestión exacta, la manera más fácil que conozco para lograr el mismo comportamiento es pasar pathes a propiedades como enumerables de nombres de propiedad en lugar de cadenas de llamadas.

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

Luego analiza esos enumerables en un círculo o recursivamente mediante la reflexión. Existen algunas desventajas, como perder el sentido intelectual y la comprobación estática de los nombres, pero son muy fáciles de implementar, lo que puede sobrecargarlos en algunos casos.

En lugar de escribir

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

que tendrá que escribir

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

Es posible, pero no estoy seguro de que lo recomiendo ella. Aquí hay algo que puede serle útil: no devuelve un valor booleano, sino el valor de la expresión si es posible (sin referencia nula).

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

Puede usarlo de esta manera:

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

Advertencia: esto no se ha probado completamente, se debe trabajo con métodos y propiedades, pero probablemente no con los métodos de extensión dentro de la expresión.

Editar: Ok, no puede manejar los métodos de extensión por ahora (por ejemplo, FirstOrDefault), pero aún es posible ajustar la solución.

+0

Guau, si realmente funciona, debe ser genial, pero personalmente no me gustaría depurar este método;) –

+0

solo lo probé, funciona, pero como dices no con los métodos de extensión. pero es un buen comienzo – MichaelD

1

Definitivamente necesitamos un operador de desreferenciación seguro de nulo en C#, pero hasta entonces mira this question, que proporciona una solución ligeramente diferente pero también clara para el mismo problema.