我收集了一些极端案例和脑筋急转弯,总是想听到更多。这个页面只涵盖了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上尝试过……)

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

还有其他的宝藏吗?


当前回答

以下可能是我缺乏的常识,但是,嗯。前段时间,我们遇到了一个包含虚拟财产的bug案例。将上下文抽象一点,考虑以下代码,并将断点应用到指定区域:

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 { } }
}

而在Derived对象上下文中,您可以在添加基类时获得相同的行为。属性作为手表,或打字基地。财产成快表。

我花了些时间才意识到发生了什么。最后,我受到了Quickwatch的启发。当进入Quickwatch并探索派生对象d(或从对象的上下文,this)并选择字段基时,Quickwatch顶部的edit字段显示以下类型转换:

((TestProject1.Base)(d))

这意味着如果base被替换,调用会是

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

对于watch、Quickwatch和调试鼠标移到工具提示,在考虑多态性时显示“AWESOME”而不是“BASE_AWESOME”是有意义的。我仍然不确定为什么它会转换成一个类型转换,一个假设是,调用可能无法从这些模块的上下文,只有callvirt。

无论如何,这显然不会改变派生功能方面的任何东西。BaseProperty仍然会返回“BASE_AWESOME”,因此这不是我们工作中的bug的根源,只是一个令人困惑的组件。然而,我发现有趣的是,它会误导开发人员,他们在调试过程中不会意识到这一事实,特别是如果Base没有在你的项目中公开,而是作为第三方DLL引用,导致开发人员只是说:

“喂,等等……什么?”omg那个DLL是 , . .做一些有趣的事情”

其他回答

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

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

c#支持数组和列表之间的转换,只要数组不是多维的,并且类型之间有继承关系,并且类型是引用类型

object[] oArray = new string[] { "one", "two", "three" };
string[] sArray = (string[])oArray;

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

注意,这是无效的:

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[]'

下面是一个示例,说明如何创建导致错误消息“试图读写受保护内存”的结构。这通常表明其他记忆被破坏了。” 成功与失败的差别非常细微。

下面的单元测试演示了这个问题。

看看你能不能找出问题出在哪里。

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

如果作为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位机器来验证这一点,但该方法确实满足尾部调用优化的所有条件。如果有人有的话,我很想看看这是不是真的。

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

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

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

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

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