Qual é o caso de canto mais estranho que você já viu em C # ou .NET?

Eu coleciono alguns casos de canto e quebra-cabeças e sempre gosto de ouvir mais. A página cobre apenas os bits e bobs da linguagem C #, mas também acho interessante as coisas centrais do .NET. Por exemplo, aqui está uma que não está na página, mas que acho incrível:

string x = new string(new char[0]); string y = new string(new char[0]); Console.WriteLine(object.ReferenceEquals(x, y)); 

Eu esperaria que imprimir False – afinal, “novo” (com um tipo de referência) sempre cria um novo object, não é? As especificações para o C # e a CLI indicam que deveria. Bem, não neste caso particular. Ele imprime True e foi feito em todas as versões do framework que testei. (Eu não tentei no Mono, admitidamente …)

Só para esclarecer, este é apenas um exemplo do tipo de coisa que estou procurando – eu não estava particularmente procurando por discussões / explicações sobre essa esquisitice. (Não é o mesmo que internar cadeias normais; em particular, a internação de strings normalmente não acontece quando um construtor é chamado.) Eu estava realmente pedindo um comportamento estranho semelhante.

Alguma outra jóia escondida por aí?

Eu acho que eu mostrei a você um presente antes, mas eu gosto da diversão aqui – isso levou alguns problemas para rastrear! (o código original era obviamente mais complexo e 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()); } 

Então, o que foi T …

Resposta: qualquer Nullable – como int? . Todos os methods são substituídos, exceto GetType (), que não pode ser; por isso, é cast (checkbox) para object (e, portanto, para null) para chamar object.GetType () … que chama de null;


Update: o enredo engrossa … Ayende Rahien lançou um desafio semelhante em seu blog , mas com um 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?"); } 

Mas isso pode ser derrotado! Usando o mesmo indireito usado por coisas como remoting; aviso – o seguinte é puro mal :

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

Com isso, a chamada new() é redirecionada para o proxy ( MyFunnyProxyAttribute ), que retorna null . Agora vá e lave seus olhos!

Arredondamento dos banqueiros.

Este não é tanto um erro ou mau funcionamento do compilador, mas certamente um estranho caso de canto …

O Framework .Net emprega um esquema ou arredondamento conhecido como Arredondamento do Banqueiro.

No arredondamento dos banqueiros, os números 0,5 são arredondados para o número par mais próximo,

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

Isso pode levar a alguns erros inesperados em cálculos financeiros com base no arredondamento Round-Half-Up mais conhecido.

Isso também é verdade do Visual Basic.

O que essa function fará se chamada como Rec(0) (não sob o depurador)?

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

Responda:

  • No JIT de 32 bits, isso deve resultar em um StackOverflowException
  • No JIT de 64 bits, deve imprimir todos os números para int.MaxValue

Isso ocorre porque o compilador JIT de 64 bits aplica otimização de chamada de cauda , enquanto o JIT de 32 bits não.

Infelizmente, não tenho uma máquina de 64 bits para verificar isso, mas o método atende a todas as condições para otimização de chamada. Se alguém tiver um, eu estaria interessado em ver se é verdade.

Atribuir isso!


Este é um dos que eu gosto de perguntar nas festas (que é provavelmente o porquê de eu não ser mais convidado):

Você pode compilar o seguinte trecho de código?

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

Uma fraude fácil poderia ser:

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

Mas a solução real é esta:

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

Portanto, é um fato pouco conhecido que os tipos de valor (structs) podem reatribuir this variável.

Há alguns anos, quando trabalhamos no programa de lealdade, tivemos um problema com a quantidade de pontos dados aos clientes. O problema estava relacionado à conversão / conversão de duplas para int.

No código abaixo:

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

faz i1 == i2 ?

Acontece que i1! = I2. Por causa das diferentes políticas de arredondamento no Conversor e no operador de casting, os valores reais são:

 i1 == 14 i2 == 13 

É sempre melhor chamar Math.Ceiling () ou Math.Floor () (ou Math.Round com MidpointRounding que atenda aos nossos requisitos)

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

Eles devem ter feito 0 um inteiro mesmo quando há uma sobrecarga de function enum.

Eu conhecia o raciocínio da equipe central do C # para mapear 0 para enum, mas ainda assim, não é tão ortogonal quanto deveria ser. Exemplo do Npgsql .

Exemplo de teste:

 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 é um dos mais incomuns que vi até agora (além dos que estão aqui, é claro!):

 public class Turtle where T : Turtle { } 

Ele permite que você o declare, mas não tem uso real, já que ele sempre pedirá que você envolva qualquer class que você tenha no centro com outra Tartaruga.

[piada] Eu acho que é tartarugas todo o caminho … [/ piada]

Aqui está uma que eu só descobri recentemente …

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

O acima parece louco à primeira vista, mas na verdade é legal. Não, realmente (embora eu tenha perdido uma parte importante, mas não é nada hacky como “adicionar uma class chamada IFoo ” ou “adicionar um apelido using para apontar IFoo em uma aula “).

Veja se você consegue descobrir por que, então: Quem disse que você não pode instanciar uma interface?

Quando um booleano não é verdadeiro nem falso?

Bill descobriu que você pode hackear um booleano para que se A for True e B for True, (A e B) seja False.

Booleanos com hackers

Estou chegando um pouco atrasado para a festa, mas tenho três e quatro cinco:

  1. Se você invocar o InvokeRequired em um controle que não tenha sido carregado / mostrado, ele dirá false – e explodirá na sua cara se você tentar alterá-lo de outro thread ( a solução é fazer referência a isso.Handle no criador do ao controle).

  2. Outro que me tropeçou é que deu uma assembly com:

     enum MyEnum { Red, Blue, } 

    se você calcular MyEnum.Red.ToString () em outro assembly, e entre vezes alguém tenha recompilado seu enum para:

     enum MyEnum { Black, Red, Blue, } 

    em tempo de execução, você receberá “Black”.

  3. Eu tinha uma assembly compartilhada com algumas constantes úteis. Meu antecessor tinha deixado um monte de propriedades get-only feias, eu pensei em me livrar da desordem e usar apenas const pública. Fiquei mais do que um pouco surpreso quando VS os compilou com seus valores e não com referências.

  4. Se você implementar um novo método de uma interface de outro assembly, mas reconstruir referenciando a versão antiga desse assembly, obterá uma TypeLoadException (nenhuma implementação de ‘NewMethod’), mesmo que tenha sido implementada (veja aqui ).

  5. Dicionário <,>: “A ordem em que os itens são retornados é indefinida”. Isso é horrível , porque pode incomodá-lo algumas vezes, mas trabalhar com os outros, e se você acabou de assumir cegamente que o dictionary vai ser legal (“por que não deveria ?, pensei List,”), você realmente precisa tenha seu nariz nele antes de finalmente começar a questionar sua suposição.

VB.NET, nullables e o operador ternário:

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

Isso me levou algum tempo para depurar, já que esperava que i contivesse Nothing .

O que eu realmente contém? 0

Isso é surpreendente, mas na verdade o comportamento “correto”: Nothing no VB.NET não é exatamente o mesmo que null no CLR: Nothing pode significar null ou default(T) para um tipo de valor T , dependendo do contexto. No caso acima, If infers Integer como o tipo comum de Nothing e 5 , portanto, nesse caso, Nothing significa 0 .

Eu encontrei um segundo caso de esquina realmente estranho que bate meu primeiro por um tiro longo.

String.Equals Method (String, String, StringComparison) não é realmente efeito colateral livre.

Eu estava trabalhando em um bloco de código que tinha isso em uma linha por si só no topo de alguma function:

 stringvariable1.Equals(stringvariable2, StringComparison.InvariantCultureIgnoreCase); 

Remover essa linha leva a um estouro de pilha em algum outro lugar do programa.

O código acabou por instalar um manipulador para o que era essencialmente um evento BeforeAssemblyLoad e tentando fazer

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

Até agora eu não deveria ter que te dizer. Usando uma cultura que não tenha sido usada antes em uma comparação de seqüência de caracteres faz com que uma carga de assembly. InvariantCulture não é uma exceção a isso.

Aqui está um exemplo de como você pode criar uma estrutura que causa a mensagem de erro “Tentativa de ler ou gravar memory protegida. Isso geralmente é uma indicação de que outra memory está corrompida”. A diferença entre sucesso e fracasso é muito sutil.

O seguinte teste de unidade demonstra o problema.

Veja se você consegue descobrir o que deu errado.

  [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 # suporta conversões entre arrays e listas, desde que as matrizes não sejam multidimensionais e haja uma relação de inheritance entre os tipos e os tipos sejam tipos de referência

 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" }; 

Observe que isso não 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[]' 

Este é o mais estranho que encontrei por acidente:

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

Usado da seguinte maneira:

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

Vai lançar um NullReferenceException . Acontece que as múltiplas adições são compiladas pelo compilador C # para uma chamada para String.Concat(object[]) . Antes do .NET 4, há um bug apenas naquela sobrecarga da Concat onde o object é verificado para null, mas não o 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 é um bug da ECMA-334 §14.7.4:

O operador binário + executa a concatenação de strings quando um ou ambos os operandos são do tipo string . Se um operando de concatenação de cadeia for null , uma cadeia vazia será substituída. Caso contrário, qualquer operando não-cadeia será convertido em sua representação de cadeia chamando o método ToString virtual herdado do object tipo. Se ToString retornar null , uma string vazia será substituída.

Interessante – quando eu olhei pela primeira vez que eu assumi que era algo que o compilador C # estava verificando, mas mesmo se você emitir o IL diretamente para remover qualquer chance de interferência, isso ainda acontece, o que significa que ele é o newobj op código que está fazendo a verificação.

 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); 

Igualmente equivale a true se você verificar de encontro a string.Empty que significa que este op-código deve ter o comportamento especial para esticar cadeias vazias.

 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 

A saída é “Tentativa de ler a memory protegida. Essa é uma indicação de que outra memory está corrompida”.

PropertyInfo.SetValue () pode atribuir ints a enums, ints a ints anuláveis, enums a enums anuláveis, mas não ints a enums anuláveis.

 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 !!! 

Descrição completa aqui

E se você tiver uma class genérica que tenha methods que possam ser ambíguos dependendo dos argumentos de tipo? Eu encontrei essa situação recentemente escrevendo um dictionary bidirecional. Eu queria escrever methods Get() simétricos que retornariam o oposto de qualquer argumento que fosse passado. Algo assim:

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

Tudo está bem se você fizer uma instância em que T1 e T2 são tipos diferentes:

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

Mas se T1 e T2 são os mesmos (e provavelmente se um era uma subclass de outro), é um erro do compilador:

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

Curiosamente, todos os outros methods no segundo caso ainda são utilizáveis; são apenas chamadas para o método agora ambíguo que causa um erro do compilador. Caso interessante, se um pouco improvável e obscuro.

Puzzler de Acessibilidade C #


A seguinte class derivada está acessando um campo particular de sua class base, e o compilador olha silenciosamente para o outro lado:

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

O campo é de fato privado:

 private int m_basePrivateField = 0; 

Se importa em adivinhar como podemos compilar esse código?

.

.

.

.

.

.

.

Responda


O truque é declarar Derived como uma class interna de Base :

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

As classs internas recebem access total aos membros da class externa. Nesse caso, a class interna também é derivada da class externa. Isso nos permite “quebrar” o encapsulamento de membros 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. } } } 

Saída:

 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 .