What's the strangest corner case you've se

2018-12-31 08:19发布

I collect a few corner cases and brain teasers and would always like to hear more. The page only really covers C# language bits and bobs, but I also find core .NET things interesting too. For example, here's one which isn't on the page, but which I find incredible:

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

I'd expect that to print False - after all, "new" (with a reference type) always creates a new object, doesn't it? The specs for both C# and the CLI indicate that it should. Well, not in this particular case. It prints True, and has done on every version of the framework I've tested it with. (I haven't tried it on Mono, admittedly...)

Just to be clear, this is only an example of the kind of thing I'm looking for - I wasn't particularly looking for discussion/explanation of this oddity. (It's not the same as normal string interning; in particular, string interning doesn't normally happen when a constructor is called.) I was really asking for similar odd behaviour.

Any other gems lurking out there?

标签: c# .net
30条回答
只若初见
2楼-- · 2018-12-31 08:55

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

查看更多
初与友歌
3楼-- · 2018-12-31 08:57

C# Accessibility Puzzler


The following derived class is accessing a private field from its base class, and the compiler silently looks to the other side:

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

The field is indeed private:

private int m_basePrivateField = 0;

Care to guess how we can make such code compile?

.

.

.

.

.

.

.

Answer


The trick is to declare Derived as an inner class of Base:

public class Base
{
    private int m_basePrivateField = 0;

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

Inner classes are given full access to the outer class members. In this case the inner class also happens to derive from the outer class. This allows us to "break" the encapsulation of private members.

查看更多
路过你的时光
4楼-- · 2018-12-31 08:58

Few years ago, when working on loyality program, we had an issue with the amount of points given to customers. The issue was related to casting/converting double to int.

In code below:

double d = 13.6;

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

does i1 == i2 ?

It turns out that i1 != i2. Because of different rounding policies in Convert and cast operator the actual values are:

i1 == 14
i2 == 13

It's always better to call Math.Ceiling() or Math.Floor() (or Math.Round with MidpointRounding that meets our requirements)

int i1 = Convert.ToInt32( Math.Ceiling(d) );
int i2 = (int) Math.Ceiling(d);
查看更多
永恒的永恒
5楼-- · 2018-12-31 08:59

What will this function do if called as Rec(0) (not under the debugger)?

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

Answer:

  • On 32-bit JIT it should result in a StackOverflowException
  • On 64-bit JIT it should print all the numbers to int.MaxValue

This is because the 64-bit JIT compiler applies tail call optimisation, whereas the 32-bit JIT does not.

Unfortunately I haven't got a 64-bit machine to hand to verify this, but the method does meet all the conditions for tail-call optimisation. If anybody does have one I'd be interested to see if it's true.

查看更多
余欢
6楼-- · 2018-12-31 09:00

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"

查看更多
墨雨无痕
7楼-- · 2018-12-31 09:00

From a question I asked not long ago:

Conditional operator cannot cast implicitly?

Given:

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.

查看更多
登录 后发表回答