Colecciono algunos casos de esquina y rompecabezas y siempre me gustaría escuchar más. La página solo cubre bits y bobs del lenguaje C #, pero también me parecen interesantes las cosas principales de .NET. Por ejemplo, aquí hay uno que no está en la página, pero que me parece increíble:
string x = new string(new char[0]);
string y = new string(new char[0]);
Console.WriteLine(object.ReferenceEquals(x, y));
Esperaría que eso imprima False: después de todo, "nuevo" (con un tipo de referencia) siempre crea un nuevo objeto, ¿no? Las especificaciones para C # y la CLI indican que debería. Bueno, no en este caso particular. Imprime True, y lo ha hecho en todas las versiones del marco con el que lo he probado. (No lo he probado en Mono, lo admito ...)
Para ser claros, este es solo un ejemplo del tipo de cosas que estoy buscando: no estaba buscando una discusión / explicación de esta rareza. (No es lo mismo que el internamiento de cadena normal; en particular, el internamiento de cadena normalmente no ocurre cuando se llama a un constructor). Realmente estaba pidiendo un comportamiento extraño similar.
¿Alguna otra gema al acecho?
Respuestas:
Creo que te mostré esto antes, pero me gusta la diversión aquí, ¡esto requirió un poco de depuración para localizarlo! (el código original era obviamente más complejo y sutil ...)
Entonces, ¿qué era T ...
Respuesta: cualquiera
Nullable<T>
, comoint?
. Todos los métodos se anulan, excepto GetType () que no puede ser; por lo que se convierte (en caja) al objeto (y, por lo tanto, a nulo) para llamar a object.GetType () ... que llama a nulo ;-pActualización: la trama se complica ... Ayende Rahien lanzó un desafío similar en su blog , pero con un
where T : class, new()
:¡Pero puede ser derrotado! Usando la misma indirección utilizada por cosas como la comunicación remota; advertencia: lo siguiente es puro mal :
Con esto en su lugar, la
new()
llamada se redirige al proxy (MyFunnyProxyAttribute
), que regresanull
. ¡Ahora ve y lávate los ojos!fuente
Redondeo de los banqueros.
Este no es tanto un error o mal funcionamiento del compilador, sino ciertamente un extraño caso de esquina ...
.Net Framework emplea un esquema o redondeo conocido como redondeo bancario.
En el redondeo de los banqueros, los números 0.5 se redondean al número par más cercano, entonces
Esto puede conducir a algunos errores inesperados en los cálculos financieros basados en el redondeo Round-Half-Up más conocido.
Esto también es cierto de Visual Basic.
fuente
int(fVal + 0.5)
tanta frecuencia incluso en idiomas que tienen una función de redondeo incorporada.¿Qué hará esta función si se llama como
Rec(0)
(no bajo el depurador)?Responder:
Esto se debe a que el compilador JIT de 64 bits aplica la optimización de llamada de cola , mientras que el JIT de 32 bits no.
Desafortunadamente, no tengo una máquina de 64 bits a mano para verificar esto, pero el método cumple con todas las condiciones para la optimización de llamadas de cola. Si alguien tiene uno, me interesaría ver si es verdad.
fuente
++
allí me tiró por completo. ¿No puedes llamarRec(i + 1)
como una persona normal?¡Asigna esto!
Esta es una que me gusta preguntar en las fiestas (que probablemente sea la razón por la que ya no me invitan):
¿Puedes hacer la siguiente compilación de código?
Un truco fácil podría ser:
Pero la verdadera solución es esta:
Por lo tanto, es un hecho poco conocido que los tipos de valor (estructuras) pueden reasignar sus
this
variables.fuente
//this = new Teaser();
:-)public Foo(int bar){this = new Foo(); specialVar = bar;}
. Esto no es eficiente y no está realmente justificado (specialVar
se asigna dos veces), sino solo para su información. (Esa es la razón dada en el libro, no sé por qué no deberíamos hacerlopublic Foo(int bar) : this()
)Hace unos años, cuando trabajábamos en un programa de lealtad, tuvimos un problema con la cantidad de puntos otorgados a los clientes. El problema estaba relacionado con la conversión / conversión de double a int.
En el código a continuación:
¿i1 == i2 ?
Resulta que i1! = I2. Debido a las diferentes políticas de redondeo en el operador Convertir y emitir, los valores reales son:
Siempre es mejor llamar a Math.Ceiling () o Math.Floor () (o Math.Round con MidpointRounding que cumple con nuestros requisitos)
fuente
Deberían haber hecho 0 un número entero incluso cuando hay una sobrecarga de la función enum.
Conocía la razón fundamental del equipo de C # para asignar 0 a enum, pero aún así, no es tan ortogonal como debería ser. Ejemplo de Npgsql .
Ejemplo de prueba:
fuente
none
que se puede usar convertida a cualquier enumeración, y hacer 0 siempre un int y no convertible implícitamente en una enumeración.0
debería haber sido convertible a enum. Vea el blog de Eric Lippert: blogs.msdn.com/b/ericlippert/archive/2006/03/28/563282.aspxEste es uno de los más inusuales que he visto hasta ahora (¡aparte de los que están aquí, por supuesto!):
Te permite declararlo, pero no tiene ningún uso real, ya que siempre te pedirá que envuelvas cualquier clase en el centro con otra tortuga.
[broma] Supongo que son tortugas hasta el fondo ... [/ broma]
fuente
class RealTurtle : Turtle<RealTurtle> { } RealTurtle t = new RealTurtle();
Aquí hay uno que descubrí recientemente ...
Lo anterior parece una locura a primera vista, pero en realidad es legal. No, de verdad (aunque me he perdido una parte clave, pero no es nada obsceno como "agregar una clase llamada
IFoo
" o "agregar unusing
alias para señalarIFoo
un clase").Vea si puede descubrir por qué, entonces: ¿Quién dice que no puede crear una instancia de una interfaz?
fuente
¿Cuándo un booleano no es verdadero ni falso?
Bill descubrió que puedes hackear un booleano para que si A es Verdadero y B sea Verdadero, (A y B) sea Falso.
Booleanos pirateados
fuente
Llego un poco tarde a la fiesta, pero tengo
trescuatrocinco:Si sondea InvokeRequired en un control que no se ha cargado / mostrado, dirá falso y explotará en tu cara si intentas cambiarlo desde otro hilo ( la solución es hacer referencia a esto. Manejar en el creador del controlar).
Otro que me hizo tropezar es que, dada una asamblea con:
si calcula MyEnum.Red.ToString () en otro ensamblado, y en algún momento alguien ha vuelto a compilar su enumeración para:
en tiempo de ejecución, obtendrás "Black".
Tenía un ensamblaje compartido con algunas constantes útiles. Mi predecesor había dejado una carga de propiedades de solo obtener feo, pensé en deshacerme del desorden y simplemente usar const público. Estaba más que un poco sorprendido cuando VS los compiló con sus valores, y no con referencias.
Si implementa un nuevo método de una interfaz desde otro ensamblaje, pero reconstruye haciendo referencia a la versión anterior de ese ensamblaje, obtiene una excepción TypeLoadException (sin implementación de 'NewMethod'), aunque la haya implementado (consulte aquí ).
Diccionario <,>: "El orden en que se devuelven los elementos no está definido". Esto es horrible , porque a veces puede morderte, pero trabajar en otros, y si has asumido ciegamente que Dictionary va a jugar bien ("¿por qué no debería? Pensé, List lo hace"), realmente tienes que hacerlo tenga la nariz puesta antes de que finalmente comience a cuestionar su suposición.
fuente
VB.NET, nulables y el operador ternario:
Esto me llevó algún tiempo depurar, ya que esperaba
i
contenerNothing
.¿Qué contengo realmente?
0
.Esto es sorprendente, pero en realidad es un comportamiento "correcto":
Nothing
en VB.NET no es exactamente lo mismo quenull
en CLR:Nothing
puede significarnull
odefault(T)
para un tipo de valorT
, dependiendo del contexto. En el caso anterior, seIf
infiereInteger
como el tipo común deNothing
y5
, por lo tanto, en este caso,Nothing
significa0
.fuente
Encontré un segundo caso de esquina realmente extraño que supera por mucho al primero.
El método String.Equals (String, String, StringComparison) no está libre de efectos secundarios.
Estaba trabajando en un bloque de código que tenía esto en una línea sola en la parte superior de alguna función:
Eliminar esa línea conduce a un desbordamiento de la pila en otro lugar del programa.
El código resultó estar instalando un controlador para lo que en esencia era un evento BeforeAssemblyLoad y tratando de hacer
Por ahora no debería tener que decírtelo. El uso de una cultura que no se haya usado antes en una comparación de cadenas provoca una carga de ensamblaje. InvariantCulture no es una excepción a esto.
fuente
Aquí hay un ejemplo de cómo puede crear una estructura que provoque el mensaje de error "Intentó leer o escribir memoria protegida. Esto es a menudo una indicación de que otra memoria está dañada". La diferencia entre el éxito y el fracaso es muy sutil.
La siguiente prueba de unidad demuestra el problema.
Vea si puede resolver lo que salió mal.
fuente
+= 500
llamadas:ldc.i4 500
(empuja 500 como Int32), entoncescall valuetype Program/MyStruct Program/MyStruct::op_Addition(valuetype Program/MyStruct, valuetype [mscorlib]System.Decimal)
, entonces trata como undecimal
(96 bits) sin ninguna conversión. Si lo usa,+= 500M
lo hace bien. Simplemente parece que el compilador cree que puede hacerlo de una manera (presumiblemente debido al operador int implícito) y luego decide hacerlo de otra manera.C # admite conversiones entre matrices y listas, siempre que las matrices no sean multidimensionales y haya una relación de herencia entre los tipos y los tipos son tipos de referencia
Tenga en cuenta que esto no funciona:
fuente
Esto es lo más extraño que he encontrado por accidente:
Usado de la siguiente manera:
Lanzará un
NullReferenceException
. Resulta que las múltiples adiciones son compiladas por el compilador de C # a una llamada aString.Concat(object[])
. Antes de .NET 4, hay un error solo en esa sobrecarga de Concat donde el objeto se verifica para nulo, pero no el resultado de ToString ():Este es un error de ECMA-334 §14.7.4:
fuente
.ToString
realmente nunca debería volver nulo, sino cadena. Vacío. Sin embargo y error en el marco.Interesante: cuando lo vi por primera vez, asumí que era algo que el compilador de C # estaba buscando, pero incluso si emites el IL directamente para eliminar cualquier posibilidad de interferencia, todavía sucede, lo que significa que realmente es el
newobj
código operativo que está haciendo el comprobación.También equivale a
true
si comprueba lostring.Empty
que significa que este código de operación debe tener un comportamiento especial para internar cadenas vacías.fuente
El resultado es "Se intentó leer la memoria protegida. Esto es una indicación de que otra memoria está dañada".
fuente
PropertyInfo.SetValue () puede asignar entradas a enumeraciones, entradas a entradas anulables, enumeraciones a enumeraciones anulables, pero no entradas a enumeraciones anulables.
Descripción completa aquí
fuente
¿Qué sucede si tiene una clase genérica que tiene métodos que podrían hacerse ambiguos dependiendo de los argumentos de tipo? Me encontré con esta situación recientemente escribiendo un diccionario bidireccional. Quería escribir
Get()
métodos simétricos que devolvieran lo contrario de cualquier argumento que se pasara. Algo como esto:Todo está bien si haces una instancia donde
T1
yT2
son diferentes tipos:Pero si
T1
yT2
son lo mismo (y probablemente si uno era una subclase de otro), es un error del compilador:Curiosamente, todos los demás métodos en el segundo caso todavía son utilizables; solo las llamadas al método ahora ambiguo causan un error del compilador. Caso interesante, aunque un poco improbable y oscuro.
fuente
Rompecabezas de accesibilidad C #
La siguiente clase derivada está accediendo a un campo privado desde su clase base, y el compilador mira silenciosamente hacia el otro lado:
El campo es de hecho privado:
¿Te importaría adivinar cómo podemos compilar ese código?
.
.
.
.
.
.
.
Responder
El truco es declarar
Derived
como una clase interna deBase
:Las clases internas tienen acceso completo a los miembros de la clase externa. En este caso, la clase interna también deriva de la clase externa. Esto nos permite "romper" la encapsulación de miembros privados.
fuente
class A { private int _i; public void foo(A other) { int res = other._i; } }
Acabo de encontrar una cosita linda hoy:
Esto arroja un error de compilación.
La llamada al método 'Inicializar' debe despacharse dinámicamente, pero no puede ser porque es parte de una expresión de acceso base. Considere lanzar los argumentos dinámicos o eliminar el acceso base.
Si escribo base.Initialize (cosas como objeto); funciona perfectamente, sin embargo, esta parece ser una "palabra mágica" aquí, ya que hace exactamente lo mismo, todo aún se recibe como dinámico ...
fuente
En una API que estamos usando, los métodos que devuelven un objeto de dominio pueden devolver un "objeto nulo" especial. En la implementación de esto, el operador de comparación y el
Equals()
método se anulan para devolvertrue
si se compara connull
.Entonces, un usuario de esta API podría tener un código como este:
o quizás un poco más detallado, como este:
donde
GetDefault()
es un método que devuelve algún valor predeterminado que queremos usar en lugar denull
. La sorpresa me golpeó cuando estaba usando ReSharper y siguiendo su recomendación de reescribir cualquiera de estos a lo siguiente:Si el objeto de prueba es un objeto nulo devuelto por la API en lugar de un apropiado
null
, el comportamiento del código ahora ha cambiado, ya que el operador de fusión nula realmente buscanull
, no se ejecutaoperator=
o noEquals()
.fuente
Considere este caso extraño:
Si
Base
yDerived
se declaran en el mismo ensamblado, el compilador lo haráBase::Method
virtual y sellado (en el CIL), aunqueBase
no implemente la interfaz.Si
Base
yDerived
están en ensamblajes diferentes, al compilar elDerived
ensamblaje, el compilador no cambiará el otro ensamblaje, por lo que introducirá un miembroDerived
que será una implementación explícita paraMyInterface::Method
eso solo delegará la llamada aBase::Method
.El compilador debe hacer esto para admitir el envío polimórfico con respecto a la interfaz, es decir, debe hacer que ese método sea virtual.
fuente
Lo siguiente podría ser conocimiento general que simplemente me faltaba, pero eh. Hace algún tiempo, tuvimos un caso de error que incluía propiedades virtuales. Resumiendo un poco el contexto, considere el siguiente código y aplique el punto de interrupción al área especificada:
Mientras está en el
Derived
contexto del objeto, puede obtener el mismo comportamiento al agregarbase.Property
como reloj o al escribirbase.Property
en el reloj rápido.Me tomó un tiempo darme cuenta de lo que estaba pasando. Al final fui iluminado por el Quickwatch. Al ingresar al Quickwatch y explorar el
Derived
objeto d (o desde el contexto del objetothis
) y seleccionar el campobase
, el campo de edición en la parte superior del Quickwatch muestra el siguiente reparto:Lo que significa que si la base se reemplaza como tal, la llamada sería
para los Watches, Quickwatch y la información sobre herramientas de depuración del mouse, y entonces tendría sentido que se muestre en
"AWESOME"
lugar de"BASE_AWESOME"
cuando se considera el polimorfismo. Todavía no estoy seguro de por qué lo transformaría en un elenco, una hipótesis es quecall
podría no estar disponible desde el contexto de esos módulos, y solocallvirt
.De todos modos, eso obviamente no altera nada en términos de funcionalidad,
Derived.BaseProperty
todavía volverá realmente"BASE_AWESOME"
, y por lo tanto, esta no fue la raíz de nuestro error en el trabajo, simplemente un componente confuso. Sin embargo, me pareció interesante cómo podría engañar a los desarrolladores que desconocen ese hecho durante sus sesiones de depuración, especialmente siBase
no se expone en su proyecto, sino que se hace referencia a él como un archivo DLL de terceros, lo que hace que los desarrolladores simplemente digan:fuente
Este es bastante difícil de superar. Me encontré con él mientras intentaba construir una implementación RealProxy que realmente sea compatible con Begin / EndInvoke (gracias MS por hacer que esto sea imposible sin trucos horribles). Este ejemplo es básicamente un error en el CLR, la ruta de código no administrada para BeginInvoke no valida que el mensaje de retorno de RealProxy.PrivateInvoke (y mi anulación Invoke) está devolviendo una instancia de IAsyncResult. Una vez que se devuelve, el CLR se confunde increíblemente y pierde la idea de lo que está sucediendo, como lo demuestran las pruebas en la parte inferior.
Salida:
fuente
No estoy seguro de si diría que esto es una rareza de Windows Vista / 7 o una rareza de .Net, pero me hizo rascarme la cabeza por un tiempo.
En Windows Vista / 7, el archivo se escribirá realmente en
C:\Users\<username>\Virtual Store\Program Files\my folder\test.txt
fuente
¿Alguna vez pensó que el compilador de C # podría generar CIL no válido? Ejecute esto y obtendrá un
TypeLoadException
:Sin embargo, no sé cómo le va en el compilador de C # 4.0.
EDITAR : esta es la salida de mi sistema:
fuente
Hay algo realmente emocionante en C #, la forma en que maneja los cierres.
En lugar de copiar los valores de la variable de pila en la variable libre de cierre, hace que la magia del preprocesador envuelva todas las ocurrencias de la variable en un objeto y, por lo tanto, la saque de la pila, ¡directamente al montón! :)
Supongo que eso hace que C # sea aún más funcionalmente completo (o lambda-complete huh)) que el propio ML (que usa el valor de pila copiando AFAIK). F # también tiene esa característica, como lo hace C #.
Eso me alegra mucho, ¡gracias, muchachos de MS!
Sin embargo, no es un caso extraño o de esquina ... sino algo realmente inesperado de un lenguaje VM basado en pila :)
fuente
De una pregunta que hice hace poco:
¿El operador condicional no puede emitir implícitamente?
Dado:
Dónde
aBoolValue
se le asigna Verdadero o Falso;Lo siguiente no se compilará:
Pero esto haría:
La respuesta proporcionada también es bastante buena.
fuente
ve not test it I
seguro de que esto funcionará: Byte aByteValue = aBoolValue? (Byte) 1: (Byte) 0; O: Byte aByteValue = (Byte) (aBoolValue? 1: 0);1 : 0
solo se lanzará implícitamente a int, no a Byte.El alcance en C # es realmente extraño a veces. Déjame darte un ejemplo:
Esto no se compila, porque el comando se vuelve a declarar? Hay algunas conjeturas interesantes sobre por qué funciona de esa manera en este hilo en stackoverflow y en mi blog .
fuente