visual studio microsoft español descargar community c# lambda path tree expression-trees

c# - microsoft - visual studio installer



¿Cómo usar Expression Tree para acceder de forma segura a la ruta de objetos que aceptan nulos? (1)

Cuando obtengo un resultado XML deserializado en el árbol de objetos generado por xsd y deseo utilizar algún objeto profundo dentro de ese árbol abcdef, me dará una excepción si falta algún nodo en esa ruta de consulta.

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

Quiero evitar buscar null para cada nivel como este:

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

La primera solución es implementar el método de extensión Get que permite esto:

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

La segunda solución es implementar el método de extensión Get (cadena) y usar la reflexión para obtener un resultado como este:

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

La tercera solución podría ser implementar el ExpandoObject y usar el tipo dinámico para obtener un resultado como este:

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

Pero las últimas 2 soluciones no brindan beneficios de tipado fuerte e IntelliSense.

Creo que lo mejor podría ser la cuarta solución que se puede implementar con Expression Trees:

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

o

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

Ya implementé la primera y segunda soluciones.

Así es cómo se ve la primera solución:

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

¿Cómo implementar la cuarta solución si es posible?

También sería interesante ver cómo implementar la tercera solución si es posible.


Entonces, el lugar de partida es crear un visitante de expresión. Esto nos permite encontrar todos los accesos de miembros dentro de una expresión particular. Esto nos deja con la pregunta de qué hacer para el acceso de cada miembro.

Entonces, lo primero es visitar recursivamente la expresión a la que se está accediendo al miembro. A partir de ahí, podemos usar Expression.Condition para crear un bloque condicional que compare esa expresión subyacente procesada con null , y devuelve null si es verdadera y la expresión de inicio original si no lo es.

Tenga en cuenta que necesitamos proporcionar implementaciones tanto para miembros como para llamadas a métodos, pero el proceso para cada uno es básicamente idéntico.

También agregaremos un cheque para que la expresión subyacente sea null (es decir, no hay ninguna instancia y es un miembro estático) o si es un tipo que no admite nulo, que en su lugar usamos el comportamiento base.

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

Luego podemos crear un método de extensión para hacer que llamar sea más fácil:

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

Además de uno que acepta una lambda, en lugar de cualquier expresión, y puede devolver un delegado compilado:

public static Func<T> PropogateNull<T>(this Expression<Func<T>> expression) { var defaultValue = Expression.Constant(default(T)); var body = expression.Body.PropogateNull(); if (body.Type != typeof(T)) body = Expression.Coalesce(body, defaultValue); return Expression.Lambda<Func<T>>(body, expression.Parameters) .Compile(); }

Tenga en cuenta que, para admitir casos en los que el miembro al que se ha accedido se resuelve como un valor que no admite valores NULL, estamos cambiando el tipo de esas expresiones para que puedan MakeNullable mediante MakeNullable . Este es un problema con esta expresión final, ya que necesita ser un Func<T> , y no coincidirá si T tampoco se levanta. Por lo tanto, aunque es muy poco ideal (idealmente nunca llamarías a este método con una T no nulable, pero no hay una buena manera de admitir esto en C #) unimos el valor final usando el valor predeterminado para ese tipo, si necesario.

(Puede modificar esto trivialmente para aceptar una lambda que acepte un parámetro, y pasarle un valor, pero en su lugar puede cerrarse fácilmente sobre ese parámetro, así que no veo ningún motivo real para hacerlo).

También vale la pena señalar que en C # 6.0, cuando se publique realmente, tendremos un operador de propagación nulo real ( ?. ), Haciendo que todo esto sea innecesario. Podrás escribir:

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

y tiene exactamente la semántica que estás buscando.