Es bastante molesto probar todas mis cadenas nullantes de poder aplicar de forma segura métodos como ToUpper(), StartWith()etc.
Si el valor predeterminado de stringfuera la cadena vacía, no tendría que probar, y sentiría que es más consistente con los otros tipos de valores como into doublepor ejemplo. Además Nullable<String>tendría sentido.
Entonces, ¿por qué los diseñadores de C # eligieron usar nullel valor predeterminado de las cadenas?
Nota: Esto se relaciona con esta pregunta , pero se centra más en el por qué en lugar de qué hacer con él.
c#
string
default-value
Marcel
fuente
fuente

null(y también le recomiendo que trate conceptualmentenully vacíe las cadenas como cosas diferentes). Un valor nulo podría ser el resultado de un error en alguna parte, mientras que una cadena vacía debe transmitir un significado diferente.Respuestas:
Porque
stringes un tipo de referencia y el valor predeterminado para todos los tipos de referencia esnull.Eso es consistente con el comportamiento de los tipos de referencia. Antes de invocar a los miembros de su instancia, se debe establecer una marca para una referencia nula.
Asignar el valor predeterminado a un tipo de referencia específico que
nullno sea inconsistente .Nullable<T>trabaja con los tipos de valor. Cabe destacar el hecho de queNullableno se introdujo en la plataforma .NET original , por lo que habría habido un montón de código roto si hubieran cambiado esa regla. ( Cortesía de @jcolebrand )fuente
String, excepto por (1) el comportamiento de tipo de valor de tener un valor predeterminado utilizable, y (2) una desafortunada capa adicional de indirección de boxeo cada vez que se lanzó aObject. Dado que la representación del montónstringes única, tener un tratamiento especial para evitar el boxeo adicional no habría sido muy difícil (en realidad, poder especificar comportamientos de boxeo no predeterminados también sería algo bueno para otros tipos).Habib tiene razón, porque
stringes un tipo de referencia.Pero lo más importante, no tiene que verificar
nullcada vez que lo usa. Sin embargo, probablemente debería arrojar unArgumentNullExceptionsi alguien pasa su función comonullreferencia.Aquí está la cosa: el marco arrojaría un
NullReferenceExceptionpara usted de todos modos si intenta llamar.ToUpper()a una cadena. Recuerde que este caso aún puede ocurrir incluso si prueba sus argumentosnullya que cualquier propiedad o método en los objetos pasados a su función como parámetros pueden evaluarnull.Dicho esto, la verificación de cadenas vacías o nulos es algo común, por lo que proporcionan
String.IsNullOrEmpty()yString.IsNullOrWhiteSpace()para este propósito.fuente
NullReferenceExceptionusted mismo ( msdn.microsoft.com/en-us/library/ms173163.aspx ); arroja unArgumentNullExceptionsi su método no puede aceptar referencias nulas. Además, los NullRef suelen ser una de las excepciones más difíciles de diagnosticar cuando se solucionan problemas, por lo que no creo que la recomendación de no verificar null sea muy buena.ArgumentNullExceptiontiene el beneficio adicional de poder proporcionar el nombre del parámetro. Durante la depuración, esto ahorra ... err, segundos. Pero segundos importantes.Podría escribir un método de extensión (para lo que vale):
Ahora esto funciona de manera segura:
fuente
.EmptyNull(), ¿por qué no simplemente usarlo(str ?? "")donde sea necesario? Dicho esto, estoy de acuerdo con el sentimiento expresado en el comentario de @ DaveMarkle: probablemente no deberías.nullyString.Emptyson conceptualmente diferentes, y no necesariamente puedes tratar a uno igual que a otro.También puede usar lo siguiente, a partir de C # 6.0
El resultado de la cadena será nulo.
fuente
public string Name { get; set; } = string.Empty;Las cadenas vacías y los valores nulos son fundamentalmente diferentes. Un valor nulo es la ausencia de un valor y una cadena vacía es un valor que está vacío.
El lenguaje de programación que hace suposiciones sobre el "valor" de una variable, en este caso una cadena vacía, será tan bueno como iniciar la cadena con cualquier otro valor que no cause un problema de referencia nulo.
Además, si pasa el identificador a esa variable de cadena a otras partes de la aplicación, entonces ese código no tendrá formas de validar si ha pasado intencionalmente un valor en blanco o si ha olvidado completar el valor de esa variable.
Otra ocasión en la que esto sería un problema es cuando la cadena es un valor de retorno de alguna función. Como string es un tipo de referencia y técnicamente puede tener un valor como nulo y vacío, por lo tanto, la función también puede devolver técnicamente un valor nulo o vacío (no hay nada que lo detenga). Ahora, dado que hay 2 nociones de la "ausencia de un valor", es decir, una cadena vacía y un valor nulo, todo el código que consume esta función tendrá que hacer 2 comprobaciones. Uno para vacío y otro para nulo.
En resumen, siempre es bueno tener solo 1 representación para un solo estado. Para una discusión más amplia sobre vacíos y nulos, vea los enlaces a continuación.
/software/32578/sql-empty-string-vs-null-value
NULL vs Empty cuando se trata de la entrada del usuario
fuente
nullrepresentaría la cadena vacía. Hay varias maneras en que .net podría haber implementado una semántica similar, si hubieran elegido hacerlo, especialmente dado queStringtiene una serie de características que lo convierten en un tipo único de todos modos [por ejemplo, y los dos tipos de matriz son los únicos tipos cuya asignación el tamaño no es constante].La razón / problema fundamental es que los diseñadores de la especificación CLS (que define cómo interactúan los lenguajes con .net) no definieron un medio por el cual los miembros de la clase pudieran especificar que deben ser llamados directamente, en lugar de vía
callvirt, sin que la persona que realiza la llamada realice un verificación de referencia nula; Tampoco proporcionó un significado de estructuras definitorias que no estarían sujetas al boxeo "normal".Si la especificación CLS hubiera definido dicho medio, entonces sería posible que .net siguiera consistentemente el liderazgo establecido por el Modelo de Objeto Común (COM), bajo el cual una referencia de cadena nula se consideraba semánticamente equivalente a una cadena vacía, y para otros tipos de clase inmutables definidos por el usuario que se supone que tienen una semántica de valores para definir igualmente los valores predeterminados. Esencialmente, lo que sucedería sería que cada miembro de
String, por ejemplo,Lengthse escribiera como algo así[InvokableOnNull()] int String Length { get { if (this==null) return 0; else return _Length;} }. Este enfoque habría ofrecido una semántica muy buena para cosas que deberían comportarse como valores, pero debido a problemas de implementación deben almacenarse en el montón. La mayor dificultad con este enfoque es que la semántica de conversión entre estos tipos yObjectpodría volverse un poco turbia.Un enfoque alternativo habría sido permitir la definición de tipos de estructura especiales que no heredaran,
Objectsino que tuvieran operaciones de boxeo y desempaquetado personalizadas (que se convertirían a / de algún otro tipo de clase). Bajo tal enfoque, habría un tipo de claseNullableStringque se comporta como lo hace una cadena ahora, y un tipo de estructura en caja personalizadaString, que contendría un único campoValuede tipo privadoString. El intento de convertir unStringaNullableStringoObjectvolveríaValuesi no nulo, oString.Emptysi es nulo. Intentando convertirString, una referencia no nula a unaNullableStringinstancia almacenaría la referencia enValue(tal vez almacenando nulo si la longitud fuera cero); emitir cualquier otra referencia arrojaría una excepción.Aunque las cadenas deben almacenarse en el montón, conceptualmente no hay ninguna razón por la que no deberían comportarse como tipos de valor que tienen un valor predeterminado no nulo. Tenerlos almacenados como una estructura "normal" que contenía una referencia habría sido eficiente para el código que los usó como tipo "cadena", pero habría agregado una capa adicional de indirección e ineficiencia al convertir a "objeto". Si bien no preveo que .net agregue ninguna de las características anteriores en esta fecha tardía, quizás los diseñadores de futuros marcos podrían considerar incluirlas.
fuente
Nullable<>, las cadenas podrían volver a implementarse como tipos de valor; No puedo hablar de los costos y beneficios de tal elección.stringes más eficiente deNullable<string>lo que sería, tener que usar el método "más eficiente" es más oneroso que poder usar el mismo enfoque para todos los valores de bases de datos anulables.Porque una variable de cadena es una referencia , no una instancia .
Inicializarlo a Vacío por defecto habría sido posible, pero habría introducido muchas inconsistencias en todo el tablero.
fuente
stringque deba ser un tipo de referencia. Para estar seguros, los caracteres reales que componen la cadena ciertamente deben almacenarse en el montón, pero dada la cantidad de soporte dedicado que las cadenas ya tienen en el CLR, no sería una exageración tenerSystem.Stringun tipo de valor con un Campo privado únicoValuede tipoHeapString. Ese campo sería un tipo de referencia, y sería predeterminadonull, pero unaStringestructura cuyoValuecampo era nulo se comportaría como una cadena vacía. La única desventaja de este enfoque sería ...Stringto aObject, en ausencia de un código de caso especial en el tiempo de ejecución, provocaría la creación de unaStringinstancia en caja en el montón, en lugar de simplemente copiar una referencia alHeapString..Lengthetc., de modo que las instancias que se mantienen una referencia nula no intentaría desreferenciarla, sino que se comportaría como corresponde para una cadena vacía. Si el Marco sería mejor o peor constringimplementada de esa manera, si uno queríadefault(string)ser una cadena vacía ...stringun envoltorio de tipo de valor en un campo de tipo de referencia sería el enfoque que requeriría la menor cantidad de cambios a otras partes de .net [de hecho, si uno estuviera dispuesto a aceptar la conversiónStringparaObjectcrear un elemento en caja adicional, uno podría simplementeStringser una estructura ordinaria con un campo de tipoChar[]que nunca expuso]. Creo que tener unHeapStringtipo probablemente sería mejor, pero de alguna manera la cadena de tipo de valor que contiene unChar[]sería más simple.Como las cadenas son tipos de referencia , los tipos de referencia tienen un valor predeterminado
null. Las variables de los tipos de referencia almacenan referencias a los datos reales.Usemos la
defaultpalabra clave para este caso;stres unstring, entonces es un tipo de referencia , entonces el valor predeterminado esnull.stres unint, entonces es un tipo de valor , entonces el valor predeterminado eszero.fuente
¡Incorrecto! Cambiar el valor predeterminado no cambia el hecho de que es un tipo de referencia y alguien aún puede establecer explícitamente que la referencia sea
null.Punto verdadero Tendría más sentido no permitir
nullningún tipo de referencia, sino que requeriríaNullable<TheRefType>esa característica.Consistencia con otros tipos de referencia. Ahora, ¿por qué permitir los
nulltipos de referencia? Probablemente para que se sienta como C, a pesar de que esta es una decisión de diseño cuestionable en un lenguaje que también proporcionaNullable.fuente
Quizás si usa el
??operador al asignar su variable de cadena, podría ayudarlo.fuente
Una cadena es un objeto inmutable, lo que significa que cuando se le da un valor, el valor anterior no se borra de la memoria, sino que permanece en la ubicación anterior, y el nuevo valor se coloca en una nueva ubicación. Entonces, si el valor predeterminado de
String afueString.Empty, desperdiciaría elString.Emptybloque en la memoria cuando se le dio su primer valor.Aunque parece minúsculo, podría convertirse en un problema al inicializar una gran variedad de cadenas con valores predeterminados de
String.Empty. Por supuesto, siempre podría usar laStringBuilderclase mutable si esto fuera un problema.fuente
String.Empty. ¿Estoy equivocado?stringsea una cadena vacía es permitir que todos los bits cero sean una representación de una cadena vacía. Hay varias maneras en que esto se podría lograr, pero no creo que ninguna implique la inicialización de referenciasString.Empty.String.Emptyo"".stringlas ubicaciones de almacenamiento habría requerido también cambiar el comportamiento de inicialización de todas las estructuras o clases que tienen campos de tipostring. Eso representaría un cambio bastante grande en el diseño de .net, que actualmente espera inicializar a cero cualquier tipo sin siquiera tener que pensar en qué es, salvo solo por su tamaño total.Como la cadena es un tipo de referencia y el valor predeterminado para el tipo de referencia es nulo.
fuente
Tal vez la
stringpalabra clave lo confundió, ya que se ve exactamente como cualquier otra declaración de tipo de valor , pero en realidad es un aliasSystem.Stringcomo se explica en esta pregunta .Además, el color azul oscuro en Visual Studio y la primera letra minúscula pueden inducir a error al pensar que es un
struct.fuente
objectpalabra clave? Aunque es cierto, eso es mucho menos utilizado questring.intes un alias paraSystem.Int32. ¿Cual es tu punto? :)System.Int32es unStructteniendo así un valor por defecto, mientrasSystem.Stringes unaClassque tiene un puntero con el valor por defecto denull. Se presentan visualmente en la misma fuente / color. Sin conocimiento, uno puede pensar que actúan de la misma manera (= tener un valor predeterminado). Mi respuesta fue escrita con una idea de psicología cognitiva en.wikipedia.org/wiki/Cognitive_psychology detrás de ella :-)Los tipos anulables no llegaron hasta 2.0.
Si los tipos anulables se hubieran hecho al comienzo del lenguaje, entonces string no hubiera sido anulable y string? habría sido anulable. Pero no pudieron hacer esto du a la compatibilidad con versiones anteriores.
Mucha gente habla de ref-type o no ref type, pero string es una clase fuera de lo común y se habrían encontrado soluciones para hacerlo posible.
fuente