2009-12-16 8 views
5

Supongamos que quiero comprobar un montón de objetos para asegurarse de que ninguna es nulo:¿Puedo forzar mi propio cortocircuito en una llamada a un método?

if (obj != null && 
    obj.Parameters != null && 
    obj.Parameters.UserSettings != null) { 

    // do something with obj.Parameters.UserSettings 
} 

Es una posibilidad atractiva para escribir una función de ayuda a aceptar un número variable de argumentos y simplificar este tipo de cheque:

static bool NoNulls(params object[] objects) { 
    for (int i = 0; i < objects.Length; i++) 
     if (objects[i] == null) return false; 

    return true; 
} 

A continuación, el código anterior podría llegar a ser:

if (NoNulls(obj, obj.Parameters, obj.Parameters.UserSettings)) { 
    // do something 
} 

derecho? incorrecto Si obj es nulo, obtendré NullReferenceException cuando intento pasar obj.Parameters a NoNulls.

Por lo tanto, el enfoque anterior es claramente erróneo. Pero la instrucción if que usa el operador && funciona bien, ya que está cortocircuitada. Entonces: ¿hay alguna manera de hacer que un método se cortocircuite, de modo que sus argumentos no se evalúen hasta que se haga referencia explícita dentro del método?

+11

Lo que realmente necesita para resolver este problema no es la evaluación perezosa de los argumentos del método, aunque, como usted nota, eso sería el truco. Lo que sería mejor es un operador x.?y que tenga la semántica de (x == null? Null: x.y) - de esa manera usted puede decir "if (obj.?Parameters.?UserSettings== null)". Consideramos un operador de este tipo para C# 4 pero nunca lo implementamos. Quizás para una futura versión hipotética. –

+0

Eso es realmente interesante. ¿Hay otros lenguajes que implementan un operador equivalente? –

+1

@Chris: Groovy sí. Se llama operador de desreferenciación seguro de nulos. Es bueno saber que el equipo de C# lo ha considerado y podría considerarlo de nuevo :) –

Respuesta

9

Bueno, esto es feo, pero ...

static bool NoNulls(params Func<object>[] funcs) { 
    for (int i = 0; i < funcs.Length; i++) 
     if (funcs[i]() == null) return false; 

    return true; 
} 

Entonces llaman con:

if (NoNulls(() => obj, 
      () => obj.Parameters, 
      () => obj.Parameters.UserSettings)) { 
    // do something 
} 

Básicamente va a proporcionar a los delegados para evaluar los valores perezosamente, en lugar de los propios valores (como la evaluación de esos valores es lo que causa una excepción).

yo no digo que sea agradable , pero está ahí como una opción ...

EDIT: En realidad, esto (y sin querer) llega al corazón de lo que Dan fue después, creo. Todos los argumentos de un método se evalúan antes de que se ejecute el método en sí. Usar delegados efectivamente le permite retrasar esa evaluación hasta que el método necesite llamar al delegado para recuperar el valor.

+4

Es feo y confuso, pero el operador coalescente nulo no hace el truco: 'if ((objeto z = a ?? b ?? c)! = Null) DoSomething();' – LBushkin

+0

Otra idea sería pasar un única instrucción lambda como un árbol de expresión para un método que podría desmontar el árbol de expresiones y realizar la evaluación en cortocircuito. – LBushkin

+0

De hecho, pensé en eso, lo creas o no.Por supuesto, para el propósito dado -verificación de nulos- esto requeriría aproximadamente la misma cantidad de código que el enfoque original y, por lo tanto, no es tan útil. Naturalmente, para otros fines, puede ser mejor. Sin embargo, la razón principal por la que no quiero usarlo es que requiere el uso de lambdas, que no están disponibles para VB (al menos antes de VB 10, ¿no?), Y quiero algo que sea útil desde tanto C# como VB, ya que usamos ambos idiomas en el trabajo. –

1

Se podría escribir una función que acepta un árbol de expresión y transforma ese árbol en una forma que va a comprobar si hay valores nulos, y devolver una Func<bool> que podrían ser evaluada de forma segura para determinar si hay un nulo.

Sospecho que si bien el código resultante puede ser genial, sería confuso y mucho menos eficaz que simplemente escribir un montón de comprobaciones a != null && a.b != null... cortocircuitadas. De hecho, es probable que sea menos eficiente que simplemente verificar todos los valores y capturar el NullReferenceException (no es que defienda el manejo de excepciones como un mecanismo de flujo de control).

La firma para tal función sería algo así como:

public static Func<bool> NoNulls(Expression<Func<object>> expr) 

y es de uso sería algo como:

NoNulls(() => new { a = obj, 
        b = obj.Parameters, 
        c = obj.Parameters.UserSettings })(); 

Si consigo algo de tiempo libre, voy a escribir una función que simplemente hace una transformación del árbol de expresiones y actualiza mi publicación.Sin embargo, estoy seguro de que Jon Skeet o Mark Gravell podrían escribir esa función con un ojo cerrado y una mano detrás de la espalda.

También me gustaría ver a C# implementar el operador .? al que Eric alude. Como podría decir un Eric diferente (Cartman), eso sería "patear el culo".

+1

No necesita hacer tres argumentos separados: simplemente use '() => obj.Parameters.UserSettings' y obtenga el árbol de expresiones para verificar el resultado de null entre cada invocación de propiedad. –

0

Podría usar reflejos si no le importa perder seguridad de tipo estático. Me importa, así que solo uso tu primera construcción en cortocircuito. Una característica como Eric mencionó sería bienvenida :)

He pensado en este problema algunas veces. Lisp tiene macros que resuelven el problema de la manera que mencionas, ya que te permiten personalizar tu evaluación.

También he intentado utilizar métodos de extensión para resolver este problema, pero nada es menos feo que el código original.

Editar: (Respuestas no me dejan insertar bloques de código, por lo que la edición de mi post)

Vaya, no mantenerse al día en esto. Lo siento :)

Puede usar reflejos para buscar y evaluar a un miembro o propiedad a través de una cadena. Una clase de uno de mis amigos escribió tomó una sintaxis como:

new ReflectionHelper(obj)["Parameters"]["UserSettings"] 

Se trabajó a través de método de encadenamiento, devolviendo un ReflectionHelper en cada nivel. Sé que NullReferenceException es un problema en ese ejemplo. Solo quería demostrar cómo la evaluación puede diferirse al tiempo de ejecución.

Un ejemplo ligeramente más cerca de ser útil:

public class Something 
{ 
    public static object ResultOrDefault(object baseObject, params string[] chainedFields) 
    { 
    // ... 
    } 
} 

Una vez más, esta sintaxis apesta. Pero esto demuestra el uso de cadenas + reflexiones para diferir la evaluación al tiempo de ejecución.

+0

Por curiosidad, ¿cómo se puede usar la reflexión para lograr esto? El problema parece ser que una vez que haya ingresado el método, todos sus argumentos ya han sido evaluados. No tengo claro cómo la reflexión podría remediar esto; pero tal vez me estoy perdiendo algo. –

+0

Editó la publicación para responder a su comentario –

Cuestiones relacionadas