我收集了一些极端案例和脑筋急转弯,总是想听到更多。这个页面只涵盖了c#语言的一些细节,但我也发现了。net核心的东西也很有趣。例如,这里有一个没有在页面上,但我觉得不可思议:

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

我希望输出False -毕竟,“new”(具有引用类型)总是创建一个新对象,不是吗?c#和CLI的规范都表明应该这样做。嗯,在这个特殊情况下不是这样。它输出True,并且在我测试过的框架的每个版本上都是这样。(不可否认,我还没有在Mono上尝试过……)

只是为了澄清,这只是我正在寻找的事情的一个例子-我并不是特别寻找对这个奇怪现象的讨论/解释。(这和普通的弦乐实习不一样;特别地,当调用构造函数时,字符串实习通常不会发生。)我真的是在要求类似的奇怪行为。

还有其他的宝藏吗?


当前回答

c#中有一些非常令人兴奋的东西,它处理闭包的方式。

它不是将堆栈变量值复制到闭包自由变量,而是执行预处理器的魔法,将变量的所有出现都包装到一个对象中,从而将其移出堆栈-直接移到堆中!:)

我想,这使得c#甚至比ML本身(使用堆栈值复制AFAIK)更功能完备(或lambda-complete huh)。f#和c#一样也有这个特性。

这给我带来了很多快乐,谢谢你们!

但这并不是一个奇怪的或极端的情况……但是基于堆栈的虚拟机语言真的出乎意料:)

其他回答

想想这个奇怪的例子:

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

如果在同一个程序集中声明Base和Derived,编译器将使Base::方法为虚拟且密封(在CIL中),即使Base没有实现接口。

如果Base和Derived在不同的程序集中,编译Derived程序集时,编译器不会更改其他程序集,因此它将在Derived中引入一个成员,该成员将是MyInterface::Method的显式实现,它只会将调用委托给Base::Method。

编译器必须这样做,以支持与接口有关的多态分派,即它必须使该方法为虚拟。

如果作为Rec(0)调用(不在调试器下),该函数将做什么?

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

答:

在32位JIT上,它应该导致StackOverflowException 在64位JIT上,它应该将所有数字打印为int。MaxValue

这是因为64位JIT编译器应用尾部调用优化,而32位JIT则没有。

不幸的是,我手头没有一台64位机器来验证这一点,但该方法确实满足尾部调用优化的所有条件。如果有人有的话,我很想看看这是不是真的。

在我们使用的API中,返回域对象的方法可能返回一个特殊的“空对象”。在此实现中,比较运算符和Equals()方法将被重写,如果与null进行比较则返回true。

所以这个API的用户可能会有这样的代码:

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

或者更啰嗦一点,像这样:

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

其中GetDefault()是一个方法,返回一些我们想要使用的默认值,而不是null。当我使用ReSharper并按照它的建议重写这其中的任何一个时,我感到惊讶:

return test ?? GetDefault();

如果测试对象是从API返回的空对象,而不是一个正确的空对象,那么代码的行为现在已经改变,因为空合并操作符实际上检查null,而不是运行operator=或Equals()。

c#无障碍谜题


下面的派生类正在从它的基类中访问一个私有字段,编译器会默默地查看另一端:

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

这个领域确实是私有的:

private int m_basePrivateField = 0;

想猜猜我们如何编译这样的代码吗?

.

.

.

.

.

.

.

回答


诀窍是将Derived声明为Base的内部类:

public class Base
{
    private int m_basePrivateField = 0;

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

内部类可以完全访问外部类成员。在这种情况下,内部类也恰好派生自外部类。这允许我们“打破”私有成员的封装。

分配!


这是我在聚会上喜欢问的一个问题(这可能是我不再被邀请的原因):

你能编译下面这段代码吗?

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

一个简单的欺骗可以是:

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

但真正的解决方案是:

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

值类型(结构体)可以重新赋值它们的this变量,这是一个小常识。