2011-11-22 17 views
5

tengo este LINQ a objeto de consulta:LINQ a objetos cuando el objeto es nulo VS LINQ a SQL

var result = Users.Where(u => u.Address.Country.Code == 12) 

consigo una excepción si la dirección o el País son nulos.
¿Por qué esta consulta no verifica si la dirección es nula y acaba de ser procesada? De esta manera no necesitaré para escribir esta consulta terribles:

var result = Users.Where(u => u.Address != null && 
           u.Address.Country != null && 
           u.Address.Country.Code == 12) 

en LINQ a SQL la primera consulta wiil hacer el trabajo (de otras razones, por supuesto).

¿Es la forma de evitar los "controles nulos" en linq para objetar?

Respuesta

6

Desafortunadamente, "nulo" se trata de manera incoherente en C# (y en muchos otros lenguajes de programación). La aritmética anulable es elevada. Es decir, si haces cálculos aritméticos en enteros nulos, y ninguno de los operandos es nulo, entonces obtienes la respuesta normal, pero si alguno de ellos es nulo, obtienes nulo. Pero el operador de "acceso de miembros" es no elevado; si le das un operando nulo al miembro, accede "." operador, arroja una excepción en lugar de devolver un valor nulo.

¿Fuimos el diseño de un sistema de tipos a partir de cero, podríamos decir que todo tipos son anulable, y que cualquier expresión que contiene un operando nulo produce un resultado nulo, independientemente de los tipos de los operandos. Entonces, llamar a un método con un receptor nulo o un argumento nulo produciría un resultado nulo. Ese sistema tiene mucho sentido, pero obviamente es demasiado tarde para implementarlo ahora; Se han escrito millones y millones de líneas de código que esperan el comportamiento actual.

Hemos considerado agregar un operador de acceso de miembro "levantado", tal vez anotado .?. Por lo tanto, podría decir where user.?Address.?Country.?Code == 12 y eso produciría una int nullable que luego podría compararse con 12 como suelen ser las notas con nulos. Sin embargo, esto nunca ha superado la etapa "sí, eso podría ser bueno en una versión futura" del proceso de diseño, así que no lo esperaría en el futuro cercano.


ACTUALIZACIÓN: El operador "Elvis" mencionado anteriormente se implementó en C# 6.0.

+0

Y ahora está implementado en el lenguaje (si es al revés sintácticamente) pero no se implementa en la traducción LINQ SQL como no operativa, por lo que no se puede usar fácilmente. – NetMage

5

No, es una excepción de referencia nula al igual que el acceso a var x = u.Address.Country.Code; sería NullReferenceException.

Siempre debe asegurarse de que lo que está desreferenciando no sea null en LINQ a los objetos, como lo haría con cualquier otra declaración de código.

Usted puede hacer esto ya sea usando el && lógica que tiene, o que pudiera cadena Where cláusulas también (aunque esto podría contener más iteradores y probablemente realizar más lento):

var result = Users.Where(u => u.Address != null) 
        .Where(u.Address.Country != null) 
        .Where(u.Address.Country.Code == 12); 

NOTA: El Maybe() El método que sigue se ofrece simplemente como un método de "usted también puede", no digo que sea bueno o malo, solo muestra lo que algunas personas hacen. Por favor, no vote abajo si no le gusta el Maybe() Estoy repitiendo varias soluciones que he visto ...

He visto algunos métodos de extensión Maybe() escritos que le permiten hacer el tipo de cosas usted quiere. Algunas personas no le gustan porque son métodos de extensión que operan en una referencia null. No digo que eso sea bueno o malo, solo que algunas personas sienten que eso viola el buen comportamiento de OO.

Por ejemplo, se podría crear un método de extensión como:

public static class ObjectExtensions 
{ 
    // returns default if LHS is null 
    public static TResult Maybe<TInput, TResult>(this TInput value, Func<TInput, TResult> evaluator) 
     where TInput : class 
    { 
     return (value != null) ? evaluator(value) : default(TResult); 
    } 

    // returns specified value if LHS is null 
    public static TResult Maybe<TInput, TResult>(this TInput value, Func<TInput, TResult> evaluator, TResult failureValue) 
     where TInput : class 
    { 
     return (value != null) ? evaluator(value) : failureValue; 
    } 
} 

y luego hacer:

var result = Users.Where(u => u.Maybe(x => x.Address) 
        .Maybe(x => x.Country) 
        .Maybe(x => x.Code) == 12); 

Esencialmente, esto sólo cascadas del null abajo de la cadena (o el valor por defecto en el caso de un tipo sin referencia).

ACTUALIZACIÓN:

Si desea suministrar un valor de falla no predeterminado (es decir Código -1 si alguna parte es nulo), usted acaba de pasar el nuevo valor de fallo en Tal vez ():

// if you wanted to compare to zero, for example, but didn't want null 
// to translate to zero, change the default in the final maybe to -1 
var result = Users.Where(u => u.Maybe(x => x.Address) 
        .Maybe(x => x.Country) 
        .Maybe(x => x.Code, -1) == 0); 

Como dije, esta es solo una de muchas soluciones. Algunas personas no les gusta poder llamar a los métodos de extensión desde null tipos de referencia, pero es una opción que algunas personas tienden a usar para sortear estos null problemas de conexión en cascada.

En la actualidad, sin embargo, no es un operador de referencia nula de fallos integrado en C#, así que o bien vivir con los cheques nulos condicionales como la que tenía antes, la cadena de sus Where() declaraciones para que puedan filtrar el null, o construya algo que le permita conectar en cascada el null como los métodos anteriores Maybe().

+0

Tenga en cuenta que si se compara el 'Code' con' 0', se devolvería 'true' incluso si alguna propiedad principal fuera' null' (ya que '0' es el valor predeterminado para el tipo). –

+0

@GeorgeDuckett: Sí, es cierto. Esta es solo una sobrecarga de muchas, de hecho tengo varias sobrecargas en la biblioteca que uso para proporcionar otros valores predeterminados. Por ejemplo .... (ver código actualizado). –

+0

Realmente no me gustan los tal vez, es más difícil de entender cuando lee el código, casi el mismo código. – Magnus