Posibles trampas del uso de esta taquigrafía (basada en el método de extensión)

C # 6 Actualización

En C # 6 ?. ahora es una función de idioma :

 // C#1-5 propertyValue1 = myObject != null ? myObject.StringProperty : null; // C#6 propertyValue1 = myObject?.StringProperty; 

La pregunta a continuación todavía se aplica a las versiones anteriores, pero si desarrolla una nueva aplicación utilizando la nueva ?. el operador es una práctica mucho mejor.

Pregunta Original:

Regularmente quiero acceder a propiedades en objetos posiblemente nulos:

 string propertyValue1 = null; if( myObject1 != null ) propertyValue1 = myObject1.StringProperty; int propertyValue2 = 0; if( myObject2 != null ) propertyValue2 = myObject2.IntProperty; 

Y así…

Lo uso tan a menudo que tengo un fragmento para ello.

Puede acortar esto hasta cierto punto con un en línea si:

 propertyValue1 = myObject != null ? myObject.StringProperty : null; 

Sin embargo, esto es un poco torpe, especialmente si establece muchas propiedades o si más de un nivel puede ser nulo, por ejemplo:

 propertyValue1 = myObject != null ? (myObject.ObjectProp != null ? myObject.ObjectProp.StringProperty) : null : null; 

Lo que realmente quiero es ?? syntax de estilo, que funciona muy bien para tipos directamente nulos:

 int? i = SomeFunctionWhichMightReturnNull(); propertyValue2 = i ?? 0; 

Así que se me ocurrió lo siguiente:

 public static TResult IfNotNull( this T input, Func action, TResult valueIfNull ) where T : class { if ( input != null ) return action( input ); else return valueIfNull; } //lets us have a null default if the type is nullable public static TResult IfNotNull( this T input, Func action ) where T : class where TResult : class { return input.IfNotNull( action, null ); } 

Esto me permite esta syntax:

 propertyValue1 = myObject1.IfNotNull( x => x.StringProperty ); propertyValue2 = myObject2.IfNotNull( x => x.IntProperty, 0); //or one with multiple levels propertyValue1 = myObject.IfNotNull( o => o.ObjectProp.IfNotNull( p => p.StringProperty ) ); 

Esto simplifica estas llamadas, pero no estoy seguro acerca de cómo comprobar este tipo de método de extensión: hace que el código sea un poco más fácil de leer, pero a costa de extender el objeto. Esto aparecería en todo, aunque podría ponerlo en un espacio de nombres específicamente referenciado.

Este ejemplo es bastante simple, un poco más complejo sería comparar dos propiedades de objeto que aceptan nulos:

 if( ( obj1 == null && obj2 == null ) || ( obj1 != null && obj2 != null && obj1.Property == obj2.Property ) ) ... //becomes if( obj1.NullCompare( obj2, (x,y) => x.Property == y.Property ) ... 

¿Cuáles son los peligros de usar extensiones de esta manera? ¿Es probable que otros codificadores estén confundidos? ¿Esto solo es abuso de extensiones?


Creo que lo que realmente quiero aquí es una extensión de comstackción / lenguaje:

 propertyValue1 = myObject != null ? myObject.StringProperty : null; //becomes propertyValue1 = myObject?StringProperty; 

Esto haría que el caso complejo sea mucho más fácil:

 propertyValue1 = myObject != null ? (myObject.ObjectProp != null ? myObject.ObjectProp.StringProperty) : null //becomes propertyValue1 = myObject?ObjectProp?StringProperty; 

Esto solo funcionaría para los tipos de valor, pero podría devolver equivalentes que aceptan nulos:

 int? propertyValue2 = myObject?ObjectProp?IntProperty; //or int propertyValue3 = myObject?ObjectProp?IntProperty ?? 0; 

De forma independiente se nos ocurrió el mismo nombre e implementación del método de extensión: método de extensión de propagación nula . Entonces, no creemos que sea confuso o un abuso de los métodos de extensión.

Escribiría su ejemplo de “niveles múltiples” con el encadenamiento de la siguiente manera:

 propertyValue1 = myObject.IfNotNull(o => o.ObjectProp).IfNotNull(p => p.StringProperty); 

Hay un error ahora cerrado en Microsoft Connect que propone “?”. como un nuevo operador de C # que realizaría esta propagación nula. Mads Torgersen (del equipo de lenguaje C #) explicó brevemente por qué no lo implementarán.

Aquí hay otra solución para miembros encadenados, incluidos los métodos de extensión:

 public static U PropagateNulls ( this T obj ,Expression> expr) { if (obj==null) return default(U); //uses a stack to reverse Member1(Member2(obj)) to obj.Member1.Member2 var members = new Stack(); bool searchingForMembers = true; Expression currentExpression = expr.Body; while (searchingForMembers) switch (currentExpression.NodeType) { case ExpressionType.Parameter: searchingForMembers = false; break; case ExpressionType.MemberAccess: { var ma= (MemberExpression) currentExpression; members.Push(ma.Member); currentExpression = ma.Expression; } break; case ExpressionType.Call: { var mc = (MethodCallExpression) currentExpression; members.Push(mc.Method); //only supports 1-arg static methods and 0-arg instance methods if ( (mc.Method.IsStatic && mc.Arguments.Count == 1) || (mc.Arguments.Count == 0)) { currentExpression = mc.Method.IsStatic ? mc.Arguments[0] : mc.Object; break; } throw new NotSupportedException(mc.Method+" is not supported"); } default: throw new NotSupportedException (currentExpression.GetType()+" not supported"); } object currValue = obj; while(members.Count > 0) { var m = members.Pop(); switch(m.MemberType) { case MemberTypes.Field: currValue = ((FieldInfo) m).GetValue(currValue); break; case MemberTypes.Method: var method = (MethodBase) m; currValue = method.IsStatic ? method.Invoke(null,new[]{currValue}) : method.Invoke(currValue,null); break; case MemberTypes.Property: var method = ((PropertyInfo) m).GetGetMethod(true); currValue = method.Invoke(currValue,null); break; } if (currValue==null) return default(U); } return (U) currValue; } 

Entonces puedes hacer esto donde cualquiera puede ser nulo, o ninguno:

 foo.PropagateNulls(x => x.ExtensionMethod().Property.Field.Method()); 

Si tiene que comprobar muy a menudo si una referencia a un objeto es nula, puede ser que deba utilizar el Patrón de Objeto Nulo . En este patrón, en lugar de usar null para tratar el caso en el que no tiene un objeto, implementa una nueva clase con la misma interfaz pero con métodos y propiedades que devuelven los valores predeterminados adecuados.

Como es

 propertyValue1 = myObject.IfNotNull(o => o.ObjectProp.IfNotNull( p => p.StringProperty ) ); 

más fácil de leer y escribir que

 if(myObject != null && myObject.ObjectProp != null) propertyValue1 = myObject.ObjectProp.StringProperty; 

Jafar Husain publicó una muestra de uso de Expression Trees para comprobar nulo en una cadena, macros de Runtime en C # 3 .

Sin embargo, esto obviamente tiene implicaciones de rendimiento. Ahora si solo tuviéramos una forma de hacerlo en tiempo de comstackción.

¡Solo tengo que decir que me encanta este truco!

No me había dado cuenta de que los métodos de extensión no implican una comprobación nula, pero tiene sentido. Como señaló James, la llamada al método de extensión en sí no es más cara que un método normal, sin embargo, si está haciendo una tonelada de esto, entonces tiene sentido seguir el Patrón de Objeto Nulo, sugirió ljorquera. O para usar un objeto nulo y ?? juntos.

 class Class1 { public static readonly Class1 Empty = new Class1(); . . x = (obj1 ?? Class1.Empty).X; 

hace que el código sea un poco más fácil de leer, pero a costa de extender el objeto. Esto aparecería en todo,

Tenga en cuenta que en realidad no está ampliando nada (excepto teóricamente).

 propertyValue2 = myObject2.IfNotNull( x => x.IntProperty, 0); 

generará el código IL exactamente como si estuviera escrito:

 ExtentionClass::IfNotNull(myObject2, x => x.IntProperty, 0); 

No se agrega “sobrecarga” a los objetos para respaldar esto.

Para el lector que no sabe, parece que está llamando a un método en una referencia nula. Si quieres esto, te sugiero ponerlo en una clase de utilidad en lugar de utilizar un método de extensión:

 propertyValue1 = Util.IfNotNull(myObject1, x => x.StringProperty ); propertyValue2 = Util.IfNotNull(myObject2, x => x.IntProperty, 0); 

El “Util” rejillas, pero es IMO el mal sintáctico menor.

Además, si desarrolla esto como parte de un equipo, luego pregunte suavemente qué piensan y qué hacen los demás. La consistencia a través de una base de código para patrones frecuentemente usados ​​es importante.

Si bien los métodos de extensión generalmente causan malentendidos cuando se invocan desde instancias nulas, creo que la intención es bastante directa en este caso.

 string x = null; int len = x.IfNotNull(y => y.Length, 0); 

Me gustaría estar seguro de que este método estático funciona en Value Types que pueden ser nulos, como int?

Editar: el comstackdor dice que ninguno de estos son válidos:

  public void Test() { int? x = null; int a = x.IfNotNull(z => z.Value + 1, 3); int b = x.IfNotNull(z => z.Value + 1); } 

Aparte de eso, ve por ello.

No es una respuesta a la pregunta exacta, pero hay Operador Null-Conditional en C # 6.0 . Puedo argumentar que será una mala elección usar la opción en OP desde C # 6.0 🙂

Entonces tu expresión es más simple

 string propertyValue = myObject?.StringProperty; 

En caso de que myObject sea ​​nulo, devuelve null. En caso de que la propiedad sea de un tipo de valor, debe usar un tipo de valor nulo equivalente, como

 int? propertyValue = myObject?.IntProperty; 

O bien, puede fusionarse con un operador coalescente nulo para dar un valor predeterminado en caso de nulo. Por ejemplo,

 int propertyValue = myObject?.IntProperty ?? 0; 

?. no es la única syntax disponible. Para propiedades indexadas, puede usar ?[..] . Por ejemplo,

 string propertyValue = myObject?[index]; //returns null in case myObject is null 

Un comportamiento sorprendente del ?. El operador es que puede omitir de manera inteligente las .Member llamadas .Member si el objeto resulta ser nulo. Un ejemplo de esto se da en el enlace:

 var result = value?.Substring(0, Math.Min(value.Length, length)).PadRight(length); 

En este caso, el result es nulo si el value es nulo y value.Length expression no daría como resultado NullReferenceException .

Personalmente, incluso después de toda su explicación, no puedo recordar cómo diablos funciona esto:

 if( obj1.NullCompare( obj2, (x,y) => x.Property == y.Property ) 

Esto podría deberse a que no tengo experiencia en C #; sin embargo, pude leer y comprender todo lo demás en tu código. Prefiero mantener el lenguaje de códigos agnóstico (especialmente para cosas triviales) para que mañana, otro desarrollador pueda cambiarlo a un idioma completamente nuevo sin demasiada información sobre el lenguaje existente.

Aquí hay otra solución que usa myObject.NullSafe (x => x.SomeProperty.NullSafe (x => x.SomeMethod)), explicada en http://www.epitka.blogspot.com/