Cuál es el ?[]? sintaxis en C #?

85

Mientras estudiaba el delegado, que en realidad es una clase abstracta enDelegate.cs , vi el siguiente método en el que no entiendo

  • Por qué se usa el valor de retorno ?aunque ya es una referencia ( clase )
  • ?[]? significado en el parámetro

¿Podrías explicar?

public static Delegate? Combine(params Delegate?[]? delegates)
{
    if (delegates == null || delegates.Length == 0)
        return null;

    Delegate? d = delegates[0];
    for (int i = 1; i < delegates.Length; i++)
        d = Combine(d, delegates[i]);

    return d;
}
snr - Restablece a Monica
fuente
23
¿No es una matriz anulable que puede contener valores anulables?
Cid
3
c # 8, ahora puede especificar que una variable de objeto no puede ser nula. Si voltea el indicador del compilador, debe especificar cada variable que se permita que sea nula.
Jeremy Lakeman

Respuestas:

66

Explicación paso a paso:

params Delegate?[] delegates - Es una matriz de nulos Delegate

params Delegate?[]? delegates - Todo el conjunto puede ser anulable

Dado que cada parámetro es del tipo Delegate?y devuelve un índice de la Delegate?[]?matriz, tiene sentido que el tipo de retorno sea de lo Delegate?contrario el compilador devolvería un error como si estuviera retirándose y intde un método que devuelve una cadena.

Podría cambiar, por ejemplo, su código para devolver un Delegatetipo como este:

public static Delegate Combine(params Delegate?[]? delegates)
{
    Delegate defaulDelegate = // someDelegate here
    if (delegates == null || delegates.Length == 0)
        return defaulDelegate;

    Delegate d = delegates[0] ?? defaulDelegate;
    for (int i = 1; i < delegates.Length; i++)
        d = Combine(d, delegates[i]);

    return d;
}
Athanasios Kataras
fuente
44
¿Qué hay de mi primera pregunta? Por qué se usa el valor de retorno ?aunque ya es del tipo de referencia (clase)
snr - Restablecer Monica
2
¿Porque regresas d que es Delegado? tipo. Y también nulo o delegado según los parámetros
Athanasios Kataras
2
@snr El valor de retorno usa ?exactamente por la misma razón por la cual el valor del argumento usa el ?. Si entiendes lo último, automáticamente entiendes lo primero. Debido a que el método puede devolver nulo , como el parámetro puede aceptar nulo . Es decir, ambos pueden contener nulo .
GSerg
2
Lo toqué Acabo de responderlo como la segunda parte con el ejemplo. Since each parameter is of the type Delegate? and you return an index of the Delegate?[]? array, then it makes sense that the return type is Delegate?
Athanasios Kataras
2
@snr: ¿Por qué se usa el valor de retorno? aunque ya es un tipo de referencia (clase) (1) Puede ser un hábito o un refactorizador remanente si las estructuras también se usan en un código similar (2) En C # 8, los tipos de referencia pueden ser inherentemente anulables (lo que requiere una nulabilidad explícita ( 3) Foo?tiene una HasValuepropiedad ordenada , mientras Fooque debe == nullverificarse (4) Se comunica a los desarrolladores que leen la firma del método que los resultados nulos son posibles, esperados y correctos. (5) El código parece estar escrito por alguien que es muy aficionado a la nulabilidad y es explícito al respecto.
Flater
25

Tipos de referencia anulables son nuevos en C # 8.0, no existían antes.

Es una cuestión de documentación y de cómo se producen las advertencias en tiempo de compilación.

La excepción "objeto no establecido en una instancia de un objeto" es bastante común. Pero esta es una excepción en tiempo de ejecución, ya se puede descubrir parcialmente en tiempo de compilación.

Para un regular Delegate dsiempre puedes llamar

 d.Invoke();

es decir, puede codificarlo, en el momento de la compilación no sucederá nada. Puede generar excepciones en tiempo de ejecución.

Mientras que para un nuevo Delegate? peste Código

 p.Invoke();

producirá una advertencia del compilador. CS8602: Dereference of a possibly null reference a menos que escribas:

 p?.Invoke();

lo que significa, llamar solo si no es nulo.

Entonces, documentar una variable puede contener nulo o no. Genera advertencias antes y puede evitar múltiples pruebas de nulo. ¿Lo mismo que tienes para int e int ?. Usted sabe con certeza, uno no es nulo, y sabe cómo convertir uno a otro.

Holger
fuente
con la advertencia que no sabes con certeza que Delegateno es nula. Simplemente finge estar seguro (lo cual es lo suficientemente bueno en la mayoría de los casos).
Tim Pohlmann
@TimPohlmann Así como int i = null es imposible, también una cadena s = null es imposible (en C # 8 con este cambio de ruptura). Entonces es poco más que fingir algo. Por compatibilidad con versiones anteriores, se degrada a una advertencia. Si actualiza la advertencia a un error, sabe con certeza que no es nulo.
Holger
44
La última vez que revisé los NRT no son 100% a prueba de balas. Hay ciertos casos extremos que el compilador no puede detectar.
Tim Pohlmann
Sí, pero esto es lo mismo que para las advertencias de "variable no inicializada" o "no todas las rutas de código que devuelven un valor". Estas son todas las características de tiempo de compilación al 100%, sin detección en tiempo de ejecución. A diferencia de int ?, creo que no agregan memoria adicional para contener información adicional. Entonces digamos que es tan confiable como intellisense. Bastante bueno.
Holger
1
Si tienes un intnunca puede ser nulo. Si tiene un Delegatepuede ser nulo (por varias razones, por ejemplo, reflexión). Por lo general, es seguro asumir que Delegate(en C # 8 con NRT habilitado) no es nulo, pero nunca se sabe con certeza (de dónde intestamos seguros).
Tim Pohlmann
5

En C # 8, uno debe marcar explícitamente los tipos de referencia como anulables.

Por defecto, esos tipos no pueden contener nulos, algo similares a los tipos de valor. Si bien esto no cambia la forma en que funcionan las cosas debajo del capó, el verificador de tipo requerirá que lo haga manualmente.

El código dado se refactoriza para funcionar con C # 8, pero no se beneficia de esta nueva característica.

public static Delegate? Combine(params Delegate?[]? delegates)
{
    // ...[]? delegates - is not null-safe, so check for null and emptiness
    if (delegates == null || delegates.Length == 0)
        return null;

    // Delegate? d - is not null-safe too
    Delegate? d = delegates[0];
    for (int i = 1; i < delegates.Length; i++)
        d = Combine(d, delegates[i]);

    return d;
}

Aquí hay un ejemplo de un código actualizado (que no funciona, solo una idea) que aprovecha esta característica. Nos salvó de una verificación nula y simplificó un poco este método.

public static Delegate? Combine(params Delegate[] delegates)
{
    // `...[] delegates` - is null-safe, so just check if array is empty
    if (delegates.Length == 0) return null;

    // `d` - is null-safe too, since we know for sure `delegates` is both not null and not empty
    Delegate d = delegates[0];

    for (int i = 1; i < delegates.Length; i++)
        // then here is a problem if `Combine` returns nullable
        // probably, we can add some null-checks here OR mark `d` as nullable
        d = Combine(d, delegates[i]);

    return d;
}
amankkg
fuente
2
those types are not able to contain null, tenga en cuenta que genera una advertencia del compilador , no un error. El código se ejecutará bien (aunque podría generar NullReferenceExceptions). Esto se hace teniendo en cuenta la compatibilidad con versiones anteriores.
JAD