¿Qué significan dos signos de interrogación juntos en C #?

1632

Corrió a través de esta línea de código:

FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();

¿Qué significan los dos signos de interrogación, es algún tipo de operador ternario? Es difícil buscar en Google.

Edward Tanguay
fuente
50
Definitivamente no es un operador ternario, ¡solo tiene dos operandos! Es un poco como el operador condicional (que es ternario) pero el operador de fusión nula es un operador binario.
Jon Skeet el
90
Lo expliqué en una entrevista en la que el posible empleador había expresado dudas sobre mis habilidades en C #, ya que había estado usando Java profesionalmente durante un tiempo antes. No habían oído hablar de él antes, y no cuestionaron mi familiaridad con C # después de eso :)
Jon Skeet el
77
@ Jon Skeet No ha habido un fracaso tan épico en reconocer la habilidad desde el tipo que rechazó a los Beatles. :-) De ahora en adelante, solo envíeles una copia de su libro con un enlace de URL a su perfil SO escrito en la portada interior.
Iain Holder
66
IainMH: Por si sirve de algo, yo no había bastante empezado a escribir el libro todavía. (O tal vez sólo estaba trabajando en el capítulo 1 -. Algo así) Es cierto que una búsqueda para mí habría encontrado rápidamente mi blog + artículos etc.
Jon Skeet
77
Re: última oración en la q: para referencia futura, SymbolHound es ideal para este tipo de cosas, por ejemplo, symbolhound.com/?q=%3F%3F&l=&e=&n=&u= [para cualquier persona sospechosa: no estoy afiliado a de todos modos, como una buena herramienta cuando encuentro una ...]
Steve Chambers

Respuestas:

2156

Es el operador de fusión nula, y bastante parecido al operador ternario (inmediato si). Ver también ?? Operador - MSDN .

FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();

se expande a:

FormsAuth = formsAuth != null ? formsAuth : new FormsAuthenticationWrapper();

que se expande aún más a:

if(formsAuth != null)
    FormsAuth = formsAuth;
else
    FormsAuth = new FormsAuthenticationWrapper();

En inglés, significa "Si lo que está a la izquierda no es nulo, úselo, de lo contrario use lo que está a la derecha".

Tenga en cuenta que puede usar cualquier número de estos en secuencia. La siguiente instrucción asignará la primera no nula Answer#a Answer(si todas las respuestas son nulas, entonces la Answeres nula):

string Answer = Answer1 ?? Answer2 ?? Answer3 ?? Answer4;

También vale la pena mencionar que si bien la expansión anterior es conceptualmente equivalente, el resultado de cada expresión solo se evalúa una vez. Esto es importante si, por ejemplo, una expresión es una llamada a un método con efectos secundarios. (Gracias a @Joey por señalar esto).

lc.
fuente
17
Tal vez sea potencialmente peligroso encadenarlos
Coops
66
@CodeBlend ¿cómo es eso?
lc.
68
@CodeBlend, no es peligroso. Si fuera a expandirse, tendría una serie de sentencias if / else anidadas. La sintaxis es simplemente extraña porque no estás acostumbrado a verla.
joelmdev
31
Hubiera sido un envío de Dios si también funcionara para cadenas vacías :)
Zyphrax
14
@Gusdor ??se deja asociativo, por lo que a ?? b ?? c ?? des equivalente a ((a ?? b) ?? c ) ?? d. "Los operadores de asignación y el operador ternario (? :) son asociativos correctos. Todos los demás operadores binarios quedan asociativos". Fuente: msdn.microsoft.com/en-us/library/ms173145.aspx
Mark E. Haase
284

Solo porque nadie más ha dicho las palabras mágicas todavía: es el operador de fusión nulo . Está definido en la sección 7.12 de la especificación del lenguaje C # 3.0 .

Es muy útil, particularmente por la forma en que funciona cuando se usa varias veces en una expresión. Una expresión de la forma:

a ?? b ?? c ?? d

dará el resultado de la expresión asi no es nulo, de lo contrario intente b, de lo contrario intente c, de lo contrario intente d. Cortocircuita en cada punto.

Además, si el tipo de dno es anulable, el tipo de la expresión completa tampoco lo es.

Jon Skeet
fuente
2
Me gusta cómo simplificaste su significado a "operador de fusión nula". Eso es todo lo que necesitaba.
FrankO
2
Tangencial pero relacionado: ahora está en Swift, pero es muy diferente: "Operador de fusión nula" developer.apple.com/library/ios/documentation/Swift/Conceptual/…
Dan Rosenstark
27

Gracias a todos, aquí está la explicación más sucinta que encontré en el sitio de MSDN:

// y = x, unless x is null, in which case y = -1.
int y = x ?? -1;
Edward Tanguay
fuente
44
Esto sugiere un aspecto importante de la ?? operador: se introdujo para ayudar a trabajar con tipos anulables. En su ejemplo, "x" es de tipo "int?" (Anulable <int>).
Drew Noakes
99
@vitule no, si el segundo operando del operador de fusión nula no es anulable, entonces el resultado no es anulable (y -1es simplemente un simple int, que no es anulable).
Suzanne Dupéron
Realmente desearía que funcionara así. Quiero hacer esto todo el tiempo, pero no puedo porque la variable que estoy usando no es de tipo anulable. Funciona en coffeescript y = x? -1
PandaWood
@PandaWood En realidad, puedes. Si xes de tipo int?, pero yes de tipo int, podría escribir int y = (int)(x ?? -1). Se analizará xa un intsi no es null, o se asignará -1a ysi xes null.
Johan Wintgens
21

??está ahí para proporcionar un valor para un tipo anulable cuando el valor es nulo. Entonces, si formAuth es nulo, devolverá nuevo FormsAuthenticationWrapper ().

RedFilter
fuente
19

ingrese la descripción de la imagen aquí

Los dos signos de interrogación (??) indican que es un operador de fusión.

El operador coalescente devuelve el primer valor NO NULL de una cadena. Puedes ver este video de youtube que demuestra prácticamente todo.

Pero déjenme agregar más a lo que dice el video.

Si ve el significado en inglés de fusión, dice "consolidar juntos". Por ejemplo, a continuación se muestra un código de fusión simple que encadena cuatro cadenas.

Así que si str1es nullque va a tratar str2, si str2es nullque va a tratar str3y así sucesivamente hasta que encuentra una cadena con un valor no nulo.

string final = str1 ?? str2 ?? str3 ?? str4;

En palabras simples, el operador de fusión devuelve el primer valor NO NULL de una cadena.

Shivprasad Koirala
fuente
¡Me gusta esta explicación porque tiene un diagrama, y ​​luego la última oración lo explica en términos tan simples! Hace que sea fácil de comprender y aumenta mi confianza en el uso. ¡Gracias!
Joshua K
14

Es una mano corta para el operador ternario.

FormsAuth = (formsAuth != null) ? formsAuth : new FormsAuthenticationWrapper();

O para aquellos que no hacen ternary:

if (formsAuth != null)
{
  FormsAuth = formsAuth;
}
else
{
  FormsAuth = new FormsAuthenticationWrapper();
}
Benjamin Autin
fuente
3
Solo he corregido la ortografía de "ternario", pero en realidad el operador al que te refieres es el operador condicional. Resulta que es el único operador ternario en C #, pero en algún momento podrían agregar otro, en cuyo punto "ternario" será ambiguo pero "condicional" no lo será.
Jon Skeet el
Es la abreviatura de algo que puede hacer con el operador ternario (condicional). En su forma larga, tanto la prueba ( != null) como la segunda formsAuth(después de la ?) podrían modificarse; en la forma de fusión nula, ambos toman implícitamente los valores que ha proporcionado.
Bob Sammers
12

Si estás familiarizado con Ruby, me ||=parece similar a C # ??. Aquí hay un poco de Ruby:

irb(main):001:0> str1 = nil
=> nil
irb(main):002:0> str1 ||= "new value"
=> "new value"
irb(main):003:0> str2 = "old value"
=> "old value"
irb(main):004:0> str2 ||= "another new value"
=> "old value"
irb(main):005:0> str1
=> "new value"
irb(main):006:0> str2
=> "old value"

Y en C #:

string str1 = null;
str1 = str1 ?? "new value";
string str2 = "old value";
str2 = str2 ?? "another new value";
Sarah Vessels
fuente
44
x ||= yse desugará a algo así x = x || y, por ??lo que en realidad es más similar a la simple ||en Ruby.
qerub
66
Tenga en cuenta que ??sólo se preocupa null, mientras que el ||operador de Ruby, al igual que en la mayoría de lenguajes, es más acerca de null, falseo todo lo que puede ser considerado como un booleano con un valor de false(por ejemplo, en algunos idiomas, ""). Esto no es algo bueno o malo, simplemente una diferencia.
Tim S.
11

operador coalescente

es equivalente a

FormsAuth = formsAUth == null ? new FormsAuthenticationWrapper() : formsAuth
aku
fuente
11

Nada peligroso sobre esto. De hecho, es hermoso. Puede agregar valor predeterminado si eso es conveniente, por ejemplo:

CÓDIGO

int x = x1 ?? x2 ?? x3 ?? x4 ?? 0;
dqninh
fuente
1
Entonces x1, x2, x3 y x4 podrían ser tipos anulables, por ejemplo: int? x1 = null;¿Es correcto
Kevin Meredith
1
@KevinMeredith x1- x4DEBE ser tipos anulables: no tiene sentido decir, efectivamente, "el resultado es 0si x4es un valor que no puede tomar" ( null). El "tipo anulable" aquí incluye tanto los tipos de valores anulables como los tipos de referencia, por supuesto. Es un error en tiempo de compilación si una o más de las variables encadenadas (excepto la última) no son anulables.
Bob Sammers
11

Como se señaló correctamente en numerosas respuestas, ese es el "operador de fusión nula" ( ?? ), hablando de lo cual es posible que también desee consultar a su primo el "Operador condicional nulo" ( ?. O ? [ ) Que es un operador que muchas veces se usa junto con ??

Operador condicional nulo

Se usa para probar nulo antes de realizar una operación de acceso de miembro ( ?. ) O índice ( ? [ ). Estos operadores lo ayudan a escribir menos código para manejar verificaciones nulas, especialmente para descender a estructuras de datos.

Por ejemplo:

// if 'customers' or 'Order' property or 'Price' property  is null,
// dollarAmount will be 0 
// otherwise dollarAmount will be equal to 'customers.Order.Price'

int dollarAmount = customers?.Order?.Price ?? 0; 

a la antigua usanza sin ? y ?? de hacer esto es

int dollarAmount = customers != null 
                   && customers.Order!=null
                   && customers.Order.Price!=null 
                    ? customers.Order.Price : 0; 

que es más detallado y engorroso.

brakeroo
fuente
10

Solo para su diversión (sabiendo que son todos C # chicos ;-).

Creo que se originó en Smalltalk, donde ha existido durante muchos años. Se define allí como:

en objeto:

? anArgument
    ^ self

en UndefinedObject (también conocido como clase nula):

? anArgument
    ^ anArgument

Hay versiones de evaluación (?) Y no evaluables (??) de esto.
A menudo se encuentra en métodos getter para variables privadas (instancia) de inicialización lenta, que se dejan en cero hasta que realmente se necesitan.

blabla999
fuente
suena como envolver ViewState con una propiedad en un UserControl. Inicialice solo en el primer get, si no está configurado antes. =)
Seiti
9

Algunos de los ejemplos aquí de obtener valores utilizando la fusión son ineficientes.

Lo que realmente quieres es:

return _formsAuthWrapper = _formsAuthWrapper ?? new FormsAuthenticationWrapper();

o

return _formsAuthWrapper ?? (_formsAuthWrapper = new FormsAuthenticationWrapper());

Esto evita que el objeto se vuelva a crear cada vez. En lugar de que la variable privada permanezca nula y se cree un nuevo objeto en cada solicitud, esto garantiza que la variable privada se asigne si se crea el nuevo objeto.

Rey de los hipócritas
fuente
¿No se ??evalúa el atajo? new FormsAuthenticationWrapper();se evalúa si y solo si _formsAuthWrapper es nulo.
MSalters
Sí, ese es todo el punto. Solo desea llamar al método si la variable es nula. @MSalters
KingOfHypocrites
8

Nota:

He leído todo este hilo y muchos otros, pero no puedo encontrar una respuesta tan completa como esta.

Por lo cual entendí completamente el "¿por qué usar ?? y cuándo usar ?? y cómo usar ??".

Fuente:

Base de comunicación de Windows desatada por Craig McMurtry ISBN 0-672-32948-4

Tipos de valores anulables

Hay dos circunstancias comunes en las que uno quisiera saber si un valor ha sido asignado a una instancia de un tipo de valor. La primera es cuando la instancia representa un valor en una base de datos. En tal caso, a uno le gustaría poder examinar la instancia para determinar si un valor está realmente presente en la base de datos. La otra circunstancia, que es más pertinente al tema de este libro, es cuando la instancia representa un elemento de datos recibido de alguna fuente remota. Nuevamente, uno quisiera determinar a partir de la instancia si se recibió un valor para ese elemento de datos.

.NET Framework 2.0 incorpora una definición de tipo genérico que proporciona casos como estos en los que se desea asignar un valor nulo a una instancia de un tipo de valor y comprobar si el valor de la instancia es nulo. Esa definición de tipo genérico es System.Nullable, que restringe los argumentos de tipo genérico que pueden ser sustituidos por T a los tipos de valor. Las instancias de tipos construidos a partir de System.Nullable pueden tener asignado un valor nulo; de hecho, sus valores son nulos por defecto. Por lo tanto, los tipos construidos a partir de System.Nullable pueden denominarse tipos de valor anulables. System.Nullable tiene una propiedad, Value, por la cual el valor asignado a una instancia de un tipo construido a partir de él se puede obtener si el valor de la instancia no es nulo. Por lo tanto, uno puede escribir:

System.Nullable<int> myNullableInteger = null;
myNullableInteger = 1;
if (myNullableInteger != null)
{
Console.WriteLine(myNullableInteger.Value);
}

El lenguaje de programación C # proporciona una sintaxis abreviada para declarar tipos construidos a partir de System.Nullable. Esa sintaxis permite abreviar:

System.Nullable<int> myNullableInteger;

a

int? myNullableInteger;

El compilador evitará que uno intente asignar el valor de un tipo de valor anulable a un tipo de valor ordinario de esta manera:

int? myNullableInteger = null;
int myInteger = myNullableInteger;

Impide que uno lo haga porque el tipo de valor anulable podría tener el valor nulo, que en realidad tendría en este caso, y ese valor no puede asignarse a un tipo de valor ordinario. Aunque el compilador permitiría este código,

int? myNullableInteger = null;
int myInteger = myNullableInteger.Value;

La segunda declaración provocaría una excepción porque cualquier intento de acceder a la propiedad System.Nullable.Value es una operación no válida si al tipo construido a partir de System.Nullable no se le ha asignado un valor válido de T, lo que no ha sucedido en este caso.

Conclusión:

Una forma adecuada de asignar el valor de un tipo de valor anulable a un tipo de valor ordinario es usar la propiedad System.Nullable.HasValue para determinar si se ha asignado un valor válido de T al tipo de valor anulable:

int? myNullableInteger = null;
if (myNullableInteger.HasValue)
{
int myInteger = myNullableInteger.Value;
}

Otra opción es usar esta sintaxis:

int? myNullableInteger = null;
int myInteger = myNullableInteger ?? -1;

Por el cual se asigna al entero común myInteger el valor del entero anulable "myNullableInteger" si a este último se le ha asignado un valor entero válido; de lo contrario, myInteger tiene asignado el valor de -1.

Muhammad Waqas Dilawar
fuente
1

Es un operador de fusión nula que funciona de manera similar a un operador ternario.

    a ?? b  => a !=null ? a : b 

Otro punto interesante para esto es, "Un tipo anulable puede contener un valor, o puede ser indefinido" . Entonces, si intenta asignar un tipo de valor anulable a un tipo de valor no anulable, obtendrá un error en tiempo de compilación.

int? x = null; // x is nullable value type
int z = 0; // z is non-nullable value type
z = x; // compile error will be there.

¿Para hacer eso usando? operador:

z = x ?? 1; // with ?? operator there are no issues
Ashish Mishra
fuente
1
FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();

es equivalente a

FormsAuth = formsAuth != null ? formsAuth : new FormsAuthenticationWrapper();

Pero lo bueno de esto es que puedes encadenarlos, como dijeron otras personas. Lo único que no se menciona es que en realidad puedes usarlo para lanzar una excepción.

A = A ?? B ?? throw new Exception("A and B are both NULL");
NolePTR
fuente
Es realmente genial que hayas incluido ejemplos en tu publicación, aunque la pregunta era buscar una explicación de qué es o hace el operador.
TGP1994
1

El ??operador se llama operador de fusión nula. Devuelve el operando de la izquierda si el operando no es nulo; de lo contrario, devuelve el operando de la derecha.

int? variable1 = null;
int variable2  = variable1 ?? 100;

Establezca variable2el valor de variable1, si variable1NO es nulo; de lo contrario, si se variable1 == nullestablece variable2en 100.

Jubayer Hossain
fuente
0

Otros han descrito lo Null Coalescing Operatorbastante bien. Para aquellos interesados, hay una sintaxis abreviada donde esta (la pregunta SO):

FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();

es equivalente a esto:

FormsAuth ??= new FormsAuthenticationWrapper();

Algunos lo encuentran más legible y sucinto.

Thane Plummer
fuente