¿Cuál es el caso de esquina más extraño que has visto en C # o .NET?

Recopilo algunas cajas de esquina y rompecabezas y siempre me gustaría escuchar más. La página solo cubre los bits y bobs de lenguaje C #, pero también me parecen interesantes las cosas de core .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 imprima False – después de todo, “nuevo” (con un tipo de referencia) siempre crea un nuevo objeto, ¿no? Las especificaciones para C # y CLI indican que debería. Bueno, no en este caso particular. Imprime True, y lo ha hecho en todas las versiones del framework con el que lo he probado. (No lo he probado en Mono, en verdad …)

Para que quede claro, esto es solo un ejemplo del tipo de cosa que estoy buscando, no estaba particularmente buscando discusión / explicación de esta rareza. (No es lo mismo que el interrogatorio de cadena normal, en particular, el interrogatorio de cadena no ocurre normalmente cuando se llama a un constructor). Realmente estaba pidiendo un comportamiento extraño similar.

¿Alguna otra gem acechando por ahí?

Creo que te mostré esta antes, pero me gusta la diversión aquí. ¡Esto requirió una depuración para rastrear! (el código original era obviamente más complejo y sutil …)

  static void Foo() where T : new() { T t = new T(); Console.WriteLine(t.ToString()); // works fine Console.WriteLine(t.GetHashCode()); // works fine Console.WriteLine(t.Equals(t)); // works fine // so it looks like an object and smells like an object... // but this throws a NullReferenceException... Console.WriteLine(t.GetType()); } 

Entonces, ¿qué era T …

Respuesta: cualquier Nullable – como int? . Todos los métodos están anulados, excepto GetType () que no puede ser; por lo tanto, se convierte (encajonado) en objeto (y, por lo tanto, en nulo) para llamar a object.GetType () … que llama a null ;-p


Actualización: la ttwig se complica … Ayende Rahien arrojó un desafío similar en su blog , pero con una where T : class, new() :

 private static void Main() { CanThisHappen(); } public static void CanThisHappen() where T : class, new() { var instance = new T(); // new() on a ref-type; should be non-null, then Debug.Assert(instance != null, "How did we break the CLR?"); } 

¡Pero puede ser derrotado! Usar la misma indirección utilizada por cosas como la comunicación remota; advertencia – lo siguiente es puro mal :

 class MyFunnyProxyAttribute : ProxyAttribute { public override MarshalByRefObject CreateInstance(Type serverType) { return null; } } [MyFunnyProxy] class MyFunnyType : ContextBoundObject { } 

Con esto en su lugar, la llamada new() se redirige al proxy ( MyFunnyProxyAttribute ), que devuelve null . ¡Ahora vete y lávate los ojos!

Redondeo de Banqueros.

Este no es tanto un error o mal funcionamiento del comstackdor, pero ciertamente es un extraño caso de esquina …

.Net Framework emplea un esquema o redondeo conocido como Banker’s Rounding.

En Bankers ‘Rounding los números 0.5 se redondean al número par más cercano, por lo que

 Math.Round(-0.5) == 0 Math.Round(0.5) == 0 Math.Round(1.5) == 2 Math.Round(2.5) == 2 etc... 

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 para Visual Basic.

¿Qué hará esta función si se llama como Rec(0) (no debajo del depurador)?

 static void Rec(int i) { Console.WriteLine(i); if (i < int.MaxValue) { Rec(i + 1); } } 

Responder:

  • En JIT de 32 bits debería dar como resultado una StackOverflowException
  • En JIT de 64 bits debe imprimir todos los números a int.MaxValue

Esto se debe a que el comstackdor JIT de 64 bits aplica la optimización de la cola de llamadas , mientras que el JIT de 32 bits no lo hace.

Desafortunadamente, no tengo una máquina de 64 bits para verificar esto, pero el método cumple con todas las condiciones para la optimización de la cola de llamada. Si alguien tiene uno, estaría interesado en ver si es verdad.

¡Asignar esto!


Este es uno que me gustaría preguntar en las fiestas (que es probablemente la razón por la que ya no me invitan):

¿Puedes hacer la siguiente comstackción de código?

  public void Foo() { this = new Teaser(); } 

Un engaño fácil podría ser:

 string cheat = @" public void Foo() { this = new Teaser(); } "; 

Pero la verdadera solución es esta:

 public struct Teaser { public void Foo() { this = new Teaser(); } } 

Por lo tanto, es un hecho poco conocido que los tipos de valores (estructuras) pueden reasignar su this variable.

Pocos años atrás, cuando trabajábamos en un progtwig de fidelidad, tuvimos un problema con la cantidad de puntos otorgados a los clientes. El problema estaba relacionado con la conversión / conversión doble a int.

En el código a continuación:

 double d = 13.6; int i1 = Convert.ToInt32(d); int i2 = (int)d; 

¿i1 == i2 ?

Resulta que i1! = I2. Debido a las diferentes políticas de redondeo en Convert y operador de conversión, los valores reales son:

 i1 == 14 i2 == 13 

Siempre es mejor llamar a Math.Ceiling () o Math.Floor () (o Math.Round con MidpointRounding que cumpla con nuestros requisitos)

 int i1 = Convert.ToInt32( Math.Ceiling(d) ); int i2 = (int) Math.Ceiling(d); 

Deberían haber hecho 0 un entero incluso cuando hay una sobrecarga de función enum.

Conocía la lógica del equipo central 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:

 namespace Craft { enum Symbol { Alpha = 1, Beta = 2, Gamma = 3, Delta = 4 }; class Mate { static void Main(string[] args) { JustTest(Symbol.Alpha); // enum JustTest(0); // why enum JustTest((int)0); // why still enum int i = 0; JustTest(Convert.ToInt32(0)); // have to use Convert.ToInt32 to convince the compiler to make the call site use the object version JustTest(i); // it's ok from down here and below JustTest(1); JustTest("string"); JustTest(Guid.NewGuid()); JustTest(new DataTable()); Console.ReadLine(); } static void JustTest(Symbol a) { Console.WriteLine("Enum"); } static void JustTest(object o) { Console.WriteLine("Object"); } } } 

Este es uno de los más inusuales que he visto hasta ahora (aparte de los que están aquí por supuesto):

 public class Turtle where T : Turtle { } 

Te permite declararlo pero no tiene un uso real, ya que siempre te pedirá que envuelvas cualquier clase que tengas en el centro con otra Tortuga.

[broma] Supongo que es tortugas todo el camino hacia abajo … [/ broma]

Aquí hay uno que descubrí recientemente …

 interface IFoo { string Message {get;} } ... IFoo obj = new IFoo("abc"); Console.WriteLine(obj.Message); 

Lo de arriba parece loco a primera vista, pero en realidad es legal. No, en realidad (aunque me he perdido una parte clave, pero no es nada hacky como “agregar una clase llamada IFoo ” o “agregar un alias al punto” IFoo en una clase “).

Vea si puede averiguar por qué, entonces: ¿Quién dice que no puede crear una instancia de una interfaz?

¿Cuándo un booleano no es verdadero ni falso?

Bill descubrió que puede hackear un booleano de modo que si A es Verdadero y B es Verdadero, (A y B) es Falso.

Booleanos pirateados

Llegaré un poco tarde a la fiesta, pero tengo tres cuatro cinco:

  1. Si sondeas InvokeRequired en un control que no ha sido cargado / mostrado, dirá falso – y explotará en tu cara si tratas de cambiarlo de otro hilo ( la solución es hacer referencia a esto. Manejar en el creador del controlar).

  2. Otra que me hizo tropezar es que me dieron una asamblea con:

     enum MyEnum { Red, Blue, } 

    si calcula MyEnum.Red.ToString () en otro ensamblado, y entre tanto alguien ha recomstackdo su enumeración para:

     enum MyEnum { Black, Red, Blue, } 

    en tiempo de ejecución, obtendrás “Black”.

  3. Tenía un ensamblaje compartido con algunas constantes útiles. Mi predecesor había dejado un montón de propiedades de aspecto feo, pensé que me desharía del desorden y simplemente usaría const público. Me sorprendió más que nada cuando VS compiló sus valores y no las referencias.

  4. Si implementa un nuevo método de una interfaz desde otro ensamblado, pero reconstruye haciendo referencia a la versión anterior de ese ensamblado, obtiene una TypeLoadException (sin implementación de ‘NewMethod’), aunque la haya implementado (consulte aquí ).

  5. Diccionario <,>: “El orden en que se devuelven los artículos no está definido”. Esto es horrible , porque a veces puede morderte, pero puede perjudicar a los demás, y si simplemente has asumido ciegamente que el Diccionario va a jugar bien (“¿por qué no debería ?, pensé, la Lista sí”), realmente tienes que ten cuidado con tu nariz antes de que finalmente comiences a cuestionar tu suposición.

VB.NET, nullables y el operador ternario:

 Dim i As Integer? = If(True, Nothing, 5) 

Esto me llevó un tiempo depurar, ya que esperaba que no contenga Nothing .

¿Qué es lo que realmente contengo? 0 .

Esto es sorprendente, pero en realidad es un comportamiento “correcto”: Nothing en VB.NET no es exactamente lo mismo que null en CLR: Nothing puede significar null o default(T) para un tipo de valor T , según el contexto. En el caso anterior, If infiere Integer como el tipo común de Nothing y 5 , entonces, en este caso, Nothing significa 0 .

Encontré un segundo caso de esquina realmente extraño que supera a mi primero de una sola vez.

El método String.Equals (String, String, StringComparison) no es realmente 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:

 stringvariable1.Equals(stringvariable2, StringComparison.InvariantCultureIgnoreCase); 

Eliminar esa línea conduce a un desbordamiento de la stack en otro lugar del progtwig.

El código resultó ser la instalación de un controlador para lo que era, en esencia, un evento BeforeAssemblyLoad y tratando de hacer

 if (assemblyfilename.EndsWith("someparticular.dll", StringComparison.InvariantCultureIgnoreCase)) { assemblyfilename = "someparticular_modified.dll"; } 

Por ahora no debería tener que decírtelo. Usar 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.

A continuación se muestra un ejemplo de cómo puede crear una estructura que provoque el mensaje de error “Intentó leer o escribir en la memoria protegida. Esto a menudo indica que otra memoria está dañada”. La diferencia entre el éxito y el fracaso es muy sutil.

La siguiente prueba unitaria demuestra el problema.

Vea si puede resolver lo que salió mal.

  [Test] public void Test() { var bar = new MyClass { Foo = 500 }; bar.Foo += 500; Assert.That(bar.Foo.Value.Amount, Is.EqualTo(1000)); } private class MyClass { public MyStruct? Foo { get; set; } } private struct MyStruct { public decimal Amount { get; private set; } public MyStruct(decimal amount) : this() { Amount = amount; } public static MyStruct operator +(MyStruct x, MyStruct y) { return new MyStruct(x.Amount + y.Amount); } public static MyStruct operator +(MyStruct x, decimal y) { return new MyStruct(x.Amount + y); } public static implicit operator MyStruct(int value) { return new MyStruct(value); } public static implicit operator MyStruct(decimal value) { return new MyStruct(value); } } 

C # admite conversiones entre matrices y listas siempre que las matrices no sean multidimensionales y exista una relación de herencia entre los tipos y los tipos sean tipos de referencia

 object[] oArray = new string[] { "one", "two", "three" }; string[] sArray = (string[])oArray; // Also works for IList (and IEnumerable, ICollection) IList sList = (IList)oArray; IList oList = new string[] { "one", "two", "three" }; 

Tenga en cuenta que esto no funciona:

 object[] oArray2 = new int[] { 1, 2, 3 }; // Error: Cannot implicitly convert type 'int[]' to 'object[]' int[] iArray = (int[])oArray2; // Error: Cannot convert type 'object[]' to 'int[]' 

Esto es lo más extraño que he encontrado por accidente:

 public class DummyObject { public override string ToString() { return null; } } 

Utilizado de la siguiente manera:

 DummyObject obj = new DummyObject(); Console.WriteLine("The text: " + obj.GetType() + " is " + obj); 

NullReferenceException una NullReferenceException . Resulta que las múltiples adiciones son comstackdas por el comstackdor de C # a una llamada a String.Concat(object[]) . Antes de .NET 4, hay un error en esa sobrecarga de Concat, donde el objeto se marca como nulo, pero no el resultado de ToString ():

 object obj2 = args[i]; string text = (obj2 != null) ? obj2.ToString() : string.Empty; // if obj2 is non-null, but obj2.ToString() returns null, then text==null int length = text.Length; 

Este es un error de ECMA-334 §14.7.4:

El operador binario + realiza la concatenación de cadenas cuando uno o ambos operandos son de tipo string . Si un operando de concatenación de cadenas es null , se sustituye una cadena vacía. De lo contrario, cualquier operando que no sea de cadena se convierte a su representación de cadena invocando el método virtual de ToString heredado de type object . Si ToString devuelve null , se sustituye una cadena vacía.

Interesante: cuando lo miré por primera vez, asumí que era algo que el comstackdor de C # estaba buscando, pero incluso si emite el IL directamente para eliminar cualquier posibilidad de interferencia, todavía sucede, lo que significa que realmente es el código de newobj que está haciendo la comprobación.

 var method = new DynamicMethod("Test", null, null); var il = method.GetILGenerator(); il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Newarr, typeof(char)); il.Emit(OpCodes.Newobj, typeof(string).GetConstructor(new[] { typeof(char[]) })); il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Newarr, typeof(char)); il.Emit(OpCodes.Newobj, typeof(string).GetConstructor(new[] { typeof(char[]) })); il.Emit(OpCodes.Call, typeof(object).GetMethod("ReferenceEquals")); il.Emit(OpCodes.Box, typeof(bool)); il.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new[] { typeof(object) })); il.Emit(OpCodes.Ret); method.Invoke(null, null); 

También equivale a true si se string.Empty con string.Empty que significa que este código de string.Empty debe tener un comportamiento especial para internar cadenas vacías.

 Public Class Item Public ID As Guid Public Text As String Public Sub New(ByVal id As Guid, ByVal name As String) Me.ID = id Me.Text = name End Sub End Class Public Sub Load(sender As Object, e As EventArgs) Handles Me.Load Dim box As New ComboBox Me.Controls.Add(box) 'Sorry I forgot this line the first time.' Dim h As IntPtr = box.Handle 'Im not sure you need this but you might.' Try box.Items.Add(New Item(Guid.Empty, Nothing)) Catch ex As Exception MsgBox(ex.ToString()) End Try End Sub 

La salida es “Intento de leer la memoria protegida. Esto indica que otra memoria está dañada”.

PropertyInfo.SetValue () puede asignar ints a enums, ints a ints nullables, enums a enums nullable, pero no a entradas a enums nullable.

 enumProperty.SetValue(obj, 1, null); //works nullableIntProperty.SetValue(obj, 1, null); //works nullableEnumProperty.SetValue(obj, MyEnum.Foo, null); //works nullableEnumProperty.SetValue(obj, 1, null); // throws an exception !!! 

Descripción completa aquí

¿Qué sucede si tiene una clase genérica que tiene métodos que podrían hacerse ambiguos según los argumentos de tipo? Me encontré con esta situación recientemente escribiendo un diccionario de dos vías. Quería escribir métodos simétricos de Get() que devolvieran el opuesto de cualquier argumento que se pasara. Algo como esto:

 class TwoWayRelationship { public T2 Get(T1 key) { /* ... */ } public T1 Get(T2 key) { /* ... */ } } 

Todo está bien si haces una instancia donde T1 y T2 son diferentes tipos:

 var r1 = new TwoWayRelationship(); r1.Get(1); r1.Get("a"); 

Pero si T1 y T2 son iguales (y probablemente si uno fuera una subclase de otro), es un error del comstackdor:

 var r2 = new TwoWayRelationship(); r2.Get(1); // "The call is ambiguous..." 

Curiosamente, todos los otros métodos en el segundo caso todavía se pueden usar; solo son llamadas al ahora ambiguo método que causa un error de comstackción. Caso interesante, aunque un poco improbable y oscuro.

C # Accessibility Puzzler


La siguiente clase derivada está accediendo a un campo privado desde su clase base, y el comstackdor mira silenciosamente al otro lado:

 public class Derived : Base { public int BrokenAccess() { return base.m_basePrivateField; } } 

El campo es de hecho privado:

 private int m_basePrivateField = 0; 

¿Te importa adivinar cómo podemos hacer que tal código se compile?

.

.

.

.

.

.

.

Responder


El truco es declarar Derived como una clase interna de Base :

 public class Base { private int m_basePrivateField = 0; public class Derived : Base { public int BrokenAccess() { return base.m_basePrivateField; } } } 

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.

Just found a nice little thing today:

 public class Base { public virtual void Initialize(dynamic stuff) { //... } } public class Derived:Base { public override void Initialize(dynamic stuff) { base.Initialize(stuff); //... } } 

This throws compile error.

The call to method ‘Initialize’ needs to be dynamically dispatched, but cannot be because it is part of a base access expression. Consider casting the dynamic arguments or eliminating the base access.

If I write base.Initialize(stuff as object); it works perfectly, however this seems to be a “magic word” here, since it does exactly the same, everything is still recieved as dynamic…

In an API we’re using, methods that return a domain object might return a special “null object”. In the implementation of this, the comparison operator and the Equals() method are overridden to return true if it is compared with null .

So a user of this API might have some code like this:

 return test != null ? test : GetDefault(); 

or perhaps a bit more verbose, like this:

 if (test == null) return GetDefault(); return test; 

where GetDefault() is a method returning some default value that we want to use instead of null . The surprise hit me when I was using ReSharper and following it’s recommendation to rewrite either of this to the following:

 return test ?? GetDefault(); 

If the test object is a null object returned from the API instead of a proper null , the behavior of the code has now changed, as the null coalescing operator actually checks for null , not running operator= or Equals() .

Consider this weird case:

 public interface MyInterface { void Method(); } public class Base { public void Method() { } } public class Derived : Base, MyInterface { } 

If Base and Derived are declared in the same assembly, the compiler will make Base::Method virtual and sealed (in the CIL), even though Base doesn’t implement the interface.

If Base and Derived are in different assemblies, when compiling the Derived assembly, the compiler won’t change the other assembly, so it will introduce a member in Derived that will be an explicit implementation for MyInterface::Method that will just delegate the call to Base::Method .

The compiler has to do this in order to support polymorphic dispatch with regards to the interface, ie it has to make that method virtual.

The following might be general knowledge I was just simply lacking, but eh. Some time ago, we had a bug case which included virtual properties. Abstracting the context a bit, consider the following code, and apply breakpoint to specified area :

 class Program { static void Main(string[] args) { Derived d = new Derived(); d.Property = "AWESOME"; } } class Base { string _baseProp; public virtual string Property { get { return "BASE_" + _baseProp; } set { _baseProp = value; //do work with the base property which might //not be exposed to derived types //here Console.Out.WriteLine("_baseProp is BASE_" + value.ToString()); } } } class Derived : Base { string _prop; public override string Property { get { return _prop; } set { _prop = value; base.Property = value; } //<- put a breakpoint here then mouse over BaseProperty, // and then mouse over the base.Property call inside it. } public string BaseProperty { get { return base.Property; } private set { } } } 

While in the Derived object context, you can get the same behavior when adding base.Property as a watch, or typing base.Property into the quickwatch.

Took me some time to realize what was going on. In the end I was enlightened by the Quickwatch. When going into the Quickwatch and exploring the Derived object d (or from the object's context, this ) and selecting the field base , the edit field on top of the Quickwatch displays the following cast:

 ((TestProject1.Base)(d)) 

Which means that if base is replaced as such, the call would be

 public string BaseProperty { get { return ((TestProject1.Base)(d)).Property; } private set { } } 

for the Watches, Quickwatch and the debugging mouse-over tooltips, and it would then make sense for it to display "AWESOME" instead of "BASE_AWESOME" when considering polymorphism. I'm still unsure why it would transform it into a cast, one hypothesis is that call might not be available from those modules' context, and only callvirt .

Anyhow, that obviously doesn't alter anything in terms of functionality, Derived.BaseProperty will still really return "BASE_AWESOME" , and thus this was not the root of our bug at work, simply a confusing component. I did however find it interesting how it could mislead developpers which would be unaware of that fact during their debug sessions, specially if Base is not exposed in your project but rather referenced as a 3rd party DLL, resulting in Devs just saying :

"Oi, wait..what ? omg that DLL is like, ..doing something funny"

This one’s pretty hard to top. I ran into it while I was trying to build a RealProxy implementation that truly supports Begin/EndInvoke (thanks MS for making this impossible to do without horrible hacks). This example is basically a bug in the CLR, the unmanaged code path for BeginInvoke doesn’t validate that the return message from RealProxy.PrivateInvoke (and my Invoke override) is returning an instance of an IAsyncResult. Once it’s returned, the CLR gets incredibly confused and loses any idea of whats going on, as demonstrated by the tests at the bottom.

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.Remoting.Proxies; using System.Reflection; using System.Runtime.Remoting.Messaging; namespace BrokenProxy { class NotAnIAsyncResult { public string SomeProperty { get; set; } } class BrokenProxy : RealProxy { private void HackFlags() { var flagsField = typeof(RealProxy).GetField("_flags", BindingFlags.NonPublic | BindingFlags.Instance); int val = (int)flagsField.GetValue(this); val |= 1; // 1 = RemotingProxy, check out System.Runtime.Remoting.Proxies.RealProxyFlags flagsField.SetValue(this, val); } public BrokenProxy(Type t) : base(t) { HackFlags(); } public override IMessage Invoke(IMessage msg) { var naiar = new NotAnIAsyncResult(); naiar.SomeProperty = "o noes"; return new ReturnMessage(naiar, null, 0, null, (IMethodCallMessage)msg); } } interface IRandomInterface { int DoSomething(); } class Program { static void Main(string[] args) { BrokenProxy bp = new BrokenProxy(typeof(IRandomInterface)); var instance = (IRandomInterface)bp.GetTransparentProxy(); Func doSomethingDelegate = instance.DoSomething; IAsyncResult notAnIAsyncResult = doSomethingDelegate.BeginInvoke(null, null); var interfaces = notAnIAsyncResult.GetType().GetInterfaces(); Console.WriteLine(!interfaces.Any() ? "No interfaces on notAnIAsyncResult" : "Interfaces"); Console.WriteLine(notAnIAsyncResult is IAsyncResult); // Should be false, is it?! Console.WriteLine(((NotAnIAsyncResult)notAnIAsyncResult).SomeProperty); Console.WriteLine(((IAsyncResult)notAnIAsyncResult).IsCompleted); // No way this works. } } } 

Salida:

 No interfaces on notAnIAsyncResult True o noes Unhandled Exception: System.EntryPointNotFoundException: Entry point was not found. at System.IAsyncResult.get_IsCompleted() at BrokenProxy.Program.Main(String[] args) 

I’m not sure if you’d say this is a Windows Vista/7 oddity or a .Net oddity but it had me scratching my head for a while.

 string filename = @"c:\program files\my folder\test.txt"; System.IO.File.WriteAllText(filename, "Hello world."); bool exists = System.IO.File.Exists(filename); // returns true; string text = System.IO.File.ReadAllText(filename); // Returns "Hello world." 

In Windows Vista/7 the file will actually be written to C:\Users\\Virtual Store\Program Files\my folder\test.txt

Have you ever thought the C# compiler could generate invalid CIL? Run this and you’ll get a TypeLoadException :

 interface I { TM(T p); } abstract class A : I { public abstract TM(T p); } abstract class B : A, I { public override TM(T p) { return p; } public int M(int p) { return p * 2; } } class C : B { } class Program { static void Main(string[] args) { Console.WriteLine(new C().M(42)); } } 

I don’t know how it fares in the C# 4.0 compiler though.

EDIT : this is the output from my system:

 C:\Temp>type Program.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { interface I { TM(T p); } abstract class A : I { public abstract TM(T p); } abstract class B : A, I { public override TM(T p) { return p; } public int M(int p) { return p * 2; } } class C : B { } class Program { static void Main(string[] args) { Console.WriteLine(new C().M(11)); } } } C:\Temp>csc Program.cs Microsoft (R) Visual C# 2008 Compiler version 3.5.30729.1 for Microsoft (R) .NET Framework version 3.5 Copyright (C) Microsoft Corporation. All rights reserved. C:\Temp>Program Unhandled Exception: System.TypeLoadException: Could not load type 'ConsoleAppli cation1.C' from assembly 'Program, Version=0.0.0.0, Culture=neutral, PublicKeyTo ken=null'. at ConsoleApplication1.Program.Main(String[] args) C:\Temp>peverify Program.exe Microsoft (R) .NET Framework PE Verifier. Version 3.5.30729.1 Copyright (c) Microsoft Corporation. All rights reserved. [token 0x02000005] Type load failed. [IL]: Error: [C:\Temp\Program.exe : ConsoleApplication1.Program::Main][offset 0x 00000001] Unable to resolve token. 2 Error(s) Verifying Program.exe C:\Temp>ver Microsoft Windows XP [Version 5.1.2600] 

There is something really exciting about C#, the way it handles closures.

Instead of copying the stack variable values to the closure free variable, it does that preprocessor magic wrapping all occurences of the variable into an object and thus moves it out of stack – straight to the heap! 🙂

I guess, that makes C# even more functionally-complete (or lambda-complete huh)) language than ML itself (which uses stack value copying AFAIK). F# has that feature too, as C# does.

That does bring much delight to me, thank you MS guys!

It’s not an oddity or corner case though… but something really unexpected from a stack-based VM language 🙂

From a question I asked not long ago:

Conditional operator cannot cast implicitly?

Dado:

 Bool aBoolValue; 

Where aBoolValue is assigned either True or False;

The following will not compile:

 Byte aByteValue = aBoolValue ? 1 : 0; 

But this would:

 Int anIntValue = aBoolValue ? 1 : 0; 

The answer provided is pretty good too.

The scoping in c# is truly bizarre at times. Lets me give you one example:

 if (true) { OleDbCommand command = SQLServer.CreateCommand(); } OleDbCommand command = SQLServer.CreateCommand(); 

This fails to compile, because command is redeclared? There are some interested guesswork as to why it works that way in this thread on stackoverflow and in my blog .