trampas posibles de utilizar este (método de extensión basados) taquigrafía


15

C# 6 Update

En C#6 ?. is now a language feature:

// 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 el desarrollo de una nueva aplicación que utiliza el nuevo operador ?. es una práctica mucho mejor.

pregunta original:

Regularmente quiera acceder a las propiedades de los objetos posiblemente nulos:

string propertyValue1 = null; 
if(myObject1 != null) 
    propertyValue1 = myObject1.StringProperty; 

int propertyValue2 = 0; 
if(myObject2 != null) 
    propertyValue2 = myObject2.IntProperty; 

Y así sucesivamente ...

utilizo esto tan a menudo que tengo una fragmento para ello

se puede acortar esto en cierta medida con una línea si:

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

Sin embargo esto es un poco torpe, especialmente si el establecimiento de una gran cantidad de 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 la sintaxis ?? estilo, que funciona muy bien para este tipo directamente nulos:

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

Así que se le ocurrió la siguiente:

public static TResult IfNotNull<T, TResult>(this T input, Func<T, TResult> 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<T, TResult>(this T input, Func<T, TResult> action) 
    where T : class 
    where TResult : class 
{ return input.IfNotNull(action, null); } 

Esto me nos deja esta sintaxis:

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 sobre la comprobación de este tipo de método de extensión en - 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 una bastante simple, una un poco más complejo sería comparar dos propiedades del objeto anulables:

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 las 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 del compilador/idioma:

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

//becomes 
propertyValue1 = myObject?StringProperty; 

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

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

//becomes 
propertyValue1 = myObject?ObjectProp?StringProperty; 

Esto sólo funcionaría para el valor tipos, pero puede devolver equivalentes que aceptan nulos:

int? propertyValue2 = myObject?ObjectProp?IntProperty; 

//or 

int propertyValue3 = myObject?ObjectProp?IntProperty ?? 0; 
16

Nos encontramos de forma independiente con el exactamente el mismo nombre e implementación del método de extensión: Null-propagating extension method. Entonces, no creemos que sea confuso o un abuso de los métodos de extensión.

que iba a escribir tu ejemplo "múltiples niveles" con el encadenamiento de la siguiente manera:

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

Hay una now-closed bug on Microsoft Connect que propuso "?". 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.

  0

Sí, le pregunté a Mads directamente en TechEd: básicamente, esta característica sigue perdiendo el corte, aún pueden agregarla una futura versión de C#. 28 mar. 112011-03-28 13:20:02

  0

@Keith ¡Gracias por la actualización! (No puede hacer daño seguir recordando al equipo de C# que los clientes lo encontrarán útil). 29 mar. 112011-03-29 02:33:02

  0

Ahora lo están considerando seriamente: http://blogs.msdn.com/b/jerrynixon/archive/2014/02/26/ at-last-c-is-getting-sometimes-called-the-safe-navigation-operator.aspx 28 feb. 142014-02-28 15:32:57

+1

El estado de la función "Propagación nula" es "Hecho". Consulte el [Estado de implementación de características de idioma] de Roslyn (https://roslyn.codeplex.com/wikipage?title=Language%20Feature%20Status) 25 jul. 142014-07-25 17:01:25

  0

'? .' se implementa en C# 6 - He actualizado la pregunta para reflejar que . Las respuestas aquí todavía se aplican a C# 1-5 15 jun. 152015-06-15 06:41:02


0

En lo personal, incluso después de todo su explicación, no puedo recordar cómo diablos funciona esto:

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

Esto podría ser porque 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.


11

Si tiene que comprobar muy a menudo si la referencia a un objeto es nula, puede ser que deba utilizar el Null Object Pattern. 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.


1

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á extendiendo nada (excepto teóricamente).

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

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

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

No hay "gastos generales", agregó a los objetos para apoyar esto.


5

Cómo 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 registró una muestra de la utilización de árboles de expresión para comprobar nulo en una cadena, Runtime macros in C# 3.

Esto obviamente tiene implicaciones de rendimiento. Ahora si solo tuviéramos una forma de hacerlo en tiempo de compilación.

  0

Gracias, ese es un gran enlace. 24 sep. 082008-09-24 08:02:29

+1

Estoy de acuerdo con tu primer comentario. El problema que me parece es que no es inmediatamente obvio por el código lo que está haciendo. Es menos desordenado, pero solo es más fácil de entender _una vez que sabes lo que hace_. 21 nov. 082008-11-21 10:34:51


5

¡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; 
  0

Los métodos de extensión en sí mismos no son más caros, sin embargo, este método de extensión particular es más caro, ya que requiere un método lambda/anónimo. Lambdas se compila en asignaciones de clase en segundo plano. Por lo tanto, es más costoso porque requiere una asignación. 29 ene. 092009-01-29 23:06:37

+1

@JudahHimango Lamdas solo se cumplen las asignaciones de clase si capturan una variable (convirtiéndose en un cierre). Si no lo hacen, se convierten en un método estático generado por el compilador ... Puede verificar esto mirando un archivo DLL compilado con algo como dotPeek (http://www.jetbrains.com/decompiler/) 24 feb. 142014-02-24 17:50:49


1

Al lector no le parece que está llamando a un método en una referencia nula. Si desea que esta, me gustaría sugerir 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, entonces pregúntele 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.


1

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 tipos de valor que pueden ser nula, como int?

Editar: compilador dice que ninguno de estos son válidas:

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, ir a por ello.

  0

Es por eso que hay dos sobrecargas: una que necesita un valor predeterminado sin restricción en el tipo de resultado, y otra que no necesita un valor predeterminado, sino que restringe el resultado a los tipos de referencia. 24 sep. 082008-09-24 08:00:20

  0

Ver Editar para una prueba. 24 sep. 082008-09-24 10:15:34

  0

Eso es porque int? en realidad se compila en Nullable <int>, que en realidad es una estructura. Es solo magia del compilador lo que le permite compararlo con un nulo (falla correctamente donde TResult: restricción de clase). Es posible que tenga que agregar otra sobrecarga específica para Nullable <T> 26 sep. 082008-09-26 10:53:34

  0

Pensándolo bien: el valor anulable <int> solo tiene dos propiedades: HasValue an Value, ambos utilizados en ?? sintaxis. Esto no debería ser necesario para int? 06 oct. 082008-10-06 18:55:24


15

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

public static U PropagateNulls<T,U> (this T obj 
            ,Expression<Func<T,U>> expr) 
{ if (obj==null) return default(U); 

    //uses a stack to reverse Member1(Member2(obj)) to obj.Member1.Member2 
    var members = new Stack<MemberInfo>(); 

    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 usted puede hacer esto en cualquier puede ser nulo, o ninguno:

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

Muy perspicaz. ¡Gracias! 18 dic. 082008-12-18 00:58:20

  0

¡Me encanta esta idea! Sin embargo, (al igual que todas las otras soluciones aquí) no parece funcionar dentro de una declaración LINQ. Por ejemplo, al hacer una. Seleccione en un nuevo tipo anónimo como. Seleccione (s => nuevo {MyNewProperty = s.PropogateNulls (p => p.Thing)}). Esto no funciona Todavía tiene que usar la antigua verificación nula allí. 22 mar. 162016-03-22 23:24:17

  0

Tienes que modificar el código para aceptar métodos estáticos con dos argumentos, para 'Enumerable.Select (src, lambda)' 23 mar. 162016-03-23 05:31:48


0

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

  0

Gracias, pero eso es mucho más código para hacer lo mismo. También su clase Maybe es muy similar a la Nullable <T> del marco y al usar Invoke agrega un golpe de rendimiento innecesario. Aún así, es agradable ver una alternativa al mismo problema. 28 may. 092009-05-28 22:18:56


1

No es una respuesta a la pregunta exacta, pero hay es Operador nulo-condicionalin C# 6.0. Puedo discutir que será una mala opción para utilizar la opción de OP ya que C# 6.0 :)

Así que su expresión es más simple,

string propertyValue = myObject?.StringProperty; 

En caso myObject es nulo devuelve null. En caso de que la propiedad es un tipo de valor que tiene que utilizar el tipo anulable equivalente, como,

int? propertyValue = myObject?.IntProperty; 

O de lo contrario puede fusionarse con el operador coalescente nula para dar un valor predeterminado en caso de nulo.Por ejemplo,

int propertyValue = myObject?.IntProperty ?? 0; 

?. no es la única sintaxis disponibles. Para las propiedades indexadas, puede usar ?[..]. Por ejemplo,

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

Una sorprendente comportamiento del operador ?. es que puede pasar por alto de manera inteligente posteriores .Member llamadas si el objeto pasa a ser nulo. Un ejemplo de ello se da en el enlace:

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

En este caso result es nulo si value es nulo y value.Length expresión no resultaría en NullReferenceException.