你在C或.NET中看到的最奇怪的案例是什么?

What's the strangest corner case you've seen in C# or .NET?

我收集了一些角落的案例和脑筋急转弯,并总是想听到更多。这个页面只涵盖了C语言和BOB,但我也发现核心.NET内容也很有趣。例如,这里有一个不在页面上,但我觉得难以置信:

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

我希望打印错误——毕竟,"新建"(带有引用类型)总是创建一个新对象,不是吗?C和CLI的规范都表明应该这样做。好吧,在这种特殊情况下不是。它打印的是真的,并且在我测试过的框架的每个版本上都做过。(诚然,我还没试过单声道……)

只是说清楚,这只是我在寻找的一个例子-我不是特别想讨论/解释这个奇怪的事情。(这与普通的字符串实习生不同,尤其是,当调用构造函数时,字符串实习生通常不会发生。)我真的要求类似的奇怪行为。

还有其他的宝石吗?


我想我以前给你看过这个,但我喜欢这里的乐趣-这需要一些调试来跟踪!(原始代码显然更复杂和微妙…)

1
2
3
4
5
6
7
8
9
10
11
12
    static void Foo<T>() 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());
    }

所以什么是…

答:任何Nullable—如int?。除了getType()之外,所有方法都被重写;因此调用object.getType()时,它被强制转换(装箱)为object(因此为空)。它调用null;-p

更新:情节变厚…Ayende Rahien在他的博客中也提出了类似的挑战,但是他用了一个where T : class, new()来表示:

1
2
3
4
5
6
7
8
private static void Main() {
    CanThisHappen<MyFunnyType>();
}

public static void CanThisHappen<T>() 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?");
}

但它可以被打败!使用与远程处理相同的间接方法;警告-以下是纯粹的邪恶:

1
2
3
4
5
6
7
class MyFunnyProxyAttribute : ProxyAttribute {
    public override MarshalByRefObject CreateInstance(Type serverType) {
        return null;
    }
}
[MyFunnyProxy]
class MyFunnyType : ContextBoundObject { }

这样,new()调用被重定向到代理服务器(MyFunnyProxyAttribute上),该代理服务器返回null。现在去洗你的眼睛!


银行家的四舍五入。

这一个不是编译器的错误或故障,而是一个奇怪的角落案例…

.NET框架采用了一种称为银行家舍入的方案或舍入。

在银行家的四舍五入中,0.5的数字四舍五入到最接近的偶数,因此

1
2
3
4
5
Math.Round(-0.5) == 0
Math.Round(0.5) == 0
Math.Round(1.5) == 2
Math.Round(2.5) == 2
etc...

这可能会导致基于更著名的四舍五入的财务计算中出现一些意想不到的错误。

在Visual Basic中也是如此。


如果以Rec(0)的形式调用(不在调试器下),该函数将做什么?

1
2
3
4
5
6
7
8
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位机器来验证这一点,但该方法确实满足尾调用优化的所有条件。如果有人有,我会有兴趣看看这是不是真的。


分配这个!

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

你能编译下面的代码吗?

1
2
3
4
    public void Foo()
    {
        this = new Teaser();
    }

一个简单的欺骗可能是:

1
2
3
4
5
6
string cheat = @"
    public void Foo()
    {
        this = new Teaser();
    }
"
;

但真正的解决办法是:

1
2
3
4
5
6
7
public struct Teaser
{
    public void Foo()
    {
        this = new Teaser();
    }
}

因此,值类型(结构)可以重新分配它们的this变量是一个鲜为人知的事实。


几年前,在致力于忠诚度计划时,我们对给客户的点数有一个问题。这个问题与铸造/转换double到int有关。

在下面的代码中:

1
2
3
4
double d = 13.6;

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

I1=I2吗?

原来是I1!= I2。由于convert和cast运算符中的舍入策略不同,实际值为:

1
2
i1 == 14
i2 == 13

最好是调用math.heiling()或math.floor()(或math.round,中间点为rounding,满足我们的要求)

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


即使存在枚举函数重载,它们也应该使0成为整数。

我知道C核心团队将0映射到枚举的基本原理,但它仍然没有它应该的那样正交。来自NPGSQL的示例。

测试实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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");
        }
    }
}


这是迄今为止我看到的最不寻常的一个(当然,除了这里的那些!):

1
2
3
public class Turtle<T> where T : Turtle<T>
{
}

它可以让你申报,但没有真正的用途,因为它总是要求你用另一只乌龟来包装你在中心的任何课程。

[开玩笑]我想是乌龟一直往下…[笑话]


这是我最近才发现的一个…

1
2
3
4
5
6
7
interface IFoo
{
   string Message {get;}
}
...
IFoo obj = new IFoo("abc");
Console.WriteLine(obj.Message);

乍一看,上面看起来很疯狂,但实际上是合法的。不,真的(虽然我遗漏了一个关键部分,但它并不像"添加一个名为IFoo的类"或"添加一个using别名以指向IFoo的类")。

看看你是否能找出原因:谁说你不能实例化一个接口?


什么时候布尔值既不是真也不是假?

比尔发现你可以破解一个布尔值,所以如果A是真的,B是真的,(A和B)是假的。

黑客攻击的布尔人


我来派对有点晚了,但是我有three-four->strike>five:

  • 如果对一个尚未加载/显示的控件轮询InvokereRequired,如果试图从另一个线程更改该控件,它会说false,并在您的脸上爆炸(解决方案是引用该控件的创建者中的this.handle)。

  • 另一个绊倒我的是给了我一个装配:

    1
    2
    3
    4
    5
    enum MyEnum
    {
        Red,
        Blue,
    }

    如果在另一个程序集中计算myEnum.red.ToString(),并且在两次之间有人将枚举重新编译为:

    1
    2
    3
    4
    5
    6
    enum MyEnum
    {
        Black,
        Red,
        Blue,
    }

    在运行时,您将得到"黑色"。

  • 我有一个共享的程序集,里面有一些方便的常量。我的前任留下了一大堆难看的"只获取"属性,我想我可以摆脱混乱,只使用公共常量。当vs将它们编译为它们的值而不是引用时,我感到非常惊讶。

  • 如果从另一个程序集实现接口的新方法,但引用该程序集的旧版本重新生成,则会得到typeloadException(不实现"new method"),即使您已经实现了它(请参见此处)。

  • dictionary<,>:"返回项目的顺序未定义"。这太可怕了,因为它有时会咬你,但也会伤害其他人,如果你只是盲目地认为字典会很好用("为什么不应该呢?"我想,列表的确如此),在你最终开始质疑你的假设之前,你真的必须仔细研究它。


  • vb.net、nullables和三元运算符:

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

    我花了一些时间来调试,因为我期望i包含Nothing

    我真正包含什么?0

    这是令人惊讶的,但实际上是"正确"的行为:vb.net中的Nothing与clr中的null并不完全相同:Nothing对于值类型T,可以表示nulldefault(T),具体取决于上下文。在上述情况下,If推断IntegerNothing5的共同类型,因此在这种情况下,Nothing表示0


    我发现了第二个非常奇怪的街角案件,比我的第一个案件有了长足的进步。

    equals方法(string、string、stringcomparison)实际上不是没有副作用的。

    我正在开发一个代码块,它在某个函数的顶部有一行代码:

    1
    stringvariable1.Equals(stringvariable2, StringComparison.InvariantCultureIgnoreCase);

    删除该行会导致程序中其他地方的堆栈溢出。

    事实证明,代码正在为本质上是beforeassemblyload事件的事件安装处理程序,并尝试执行此操作。

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

    现在我不该告诉你。在字符串比较中使用以前未使用的区域性会导致程序集加载。InvariantCulture也不例外。


    下面是一个示例,说明如何创建导致错误消息"尝试读取或写入受保护的内存"的结构。这通常表示其他内存已损坏"。成功和失败之间的区别是非常微妙的。

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

    看看你能不能找出出什么问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
        [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支持数组和列表之间的转换,只要数组不是多维的,并且类型之间存在继承关系,类型是引用类型。

    1
    2
    3
    4
    5
    6
    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" };

    请注意,这不起作用:

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


    这是我偶然遇到的最奇怪的事情:

    1
    2
    3
    4
    5
    6
    7
    public class DummyObject
    {
        public override string ToString()
        {
            return null;
        }
    }

    使用方法如下:

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

    会抛出一个NullReferenceException。结果是,C编译器编译了对String.Concat(object[])调用的多个附加项。在.NET 4之前,只在concat的重载中存在一个bug,在该重载中检查对象是否为空,但不检查toString()的结果:

    1
    2
    3
    4
    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;

    这是ECMA-334第14.7.4条规定的一个缺陷:

    The binary + operator performs string concatenation when one or both operands are of type string. If an operand of string concatenation is null, an empty string is substituted. Otherwise, any non-string operand is converted to its string representation by invoking the virtual ToString method inherited from type object. If ToString returns null, an empty string is substituted.


    有趣的是,当我第一次看到它时,我假设它是C编译器正在检查的,但是即使您直接发出IL以消除任何干扰的可能性,它仍然会发生,这意味着它确实是执行检查的newobjop代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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);

    如果您对照string.Empty进行检查,它也等同于true,这意味着此操作代码必须具有特殊的行为来实习生空字符串。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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

    输出"尝试读取受保护的内存。这表示其他内存已损坏。"


    今天刚发现一个好东西:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class Base
    {
       public virtual void Initialize(dynamic stuff) {
       //...
       }
    }
    public class Derived:Base
    {
       public override void Initialize(dynamic stuff) {
       base.Initialize(stuff);
       //...
       }
    }

    这会引发编译错误。

    对方法"initialize"的调用需要动态调度,但不能是,因为它是基本访问表达式的一部分。考虑强制转换动态参数或消除基本访问。

    如果我写base.initialize(作为对象的东西);它工作得很好,但是这似乎是一个"神奇的词",因为它做的完全相同,所以所有的东西仍然是动态的…


    setValue()可以将Ints赋给枚举,Ints赋给可为空的Ints,Enum赋给可为空的Enum,但Ints不赋给可为空的Enum。

    1
    2
    3
    4
    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 !!!

    此处完整描述


    C无障碍拼图

    以下派生类正在从其基类访问私有字段,编译器将静默地查看另一面:

    1
    2
    3
    4
    5
    6
    7
    public class Derived : Base
    {
        public int BrokenAccess()
        {
            return base.m_basePrivateField;
        }
    }

    这个领域确实是私有的:

    1
    private int m_basePrivateField = 0;

    想知道我们怎样才能编译这样的代码吗?

    .

    .

    .

    .

    .

    .

    .

    回答

    诀窍是宣布DerivedBase的内部阶级:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class Base
    {
        private int m_basePrivateField = 0;

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

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


    如果您有一个泛型类,该类的方法可能因类型参数而变得不明确,该怎么办?我最近在写一本双向字典时遇到了这种情况。我想写对称的Get()方法,它将返回与传递的参数相反的结果。像这样:

    1
    2
    3
    4
    5
    class TwoWayRelationship<T1, T2>
    {
        public T2 Get(T1 key) { /* ... */ }
        public T1 Get(T2 key) { /* ... */ }
    }

    如果你举一个例子,其中T1T2是不同的类型,那么一切都很好:

    1
    2
    3
    var r1 = new TwoWayRelationship<int, string>();
    r1.Get(1);
    r1.Get("a");

    但是如果T1T2是相同的(并且可能其中一个是另一个的子类),这是一个编译器错误:

    1
    2
    var r2 = new TwoWayRelationship<int, int>();
    r2.Get(1);  //"The call is ambiguous..."

    有趣的是,第二种情况下的所有其他方法仍然可用;它只是对现在不明确的方法的调用,这会导致编译器错误。有趣的情况,如果有点不太可能和模糊。


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

    所以这个API的用户可能有如下代码:

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

    或者更详细一点,比如:

    1
    2
    3
    if (test == null)
        return GetDefault();
    return test;

    其中,GetDefault()是一种返回一些我们希望使用的默认值的方法,而不是null。当我使用Resharper时,我突然想到,按照它的建议,将这两种方法中的任何一种改写为:

    1
    return test ?? GetDefault();

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


    想想这个奇怪的案例:

    1
    2
    3
    4
    5
    6
    7
    public interface MyInterface {
      void Method();
    }
    public class Base {
      public void Method() { }
    }
    public class Derived : Base, MyInterface { }

    如果BaseDerived在同一程序集中声明,即使Base没有实现接口,编译器也会使Base::Method虚拟并密封(在cil中)。

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

    编译器必须这样做,以支持接口的多态调度,也就是说,它必须使该方法成为虚拟的。


    这个很难顶。我在尝试构建真正支持begin/endinvoke的realproxy实现时遇到了这个问题(感谢MS让没有可怕的黑客就无法做到这一点)。此示例基本上是CLR中的一个bug,BeginInvoke的非托管代码路径不验证来自realProxy.privateInvoke(和my invoke override)的返回消息是否返回IAsyncResult的实例。当它被返回时,clr会变得非常混乱,并且对正在发生的事情一无所知,正如底部的测试所证明的那样。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    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<int> 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.
            }
        }
    }

    输出:

    1
    2
    3
    4
    5
    6
    7
    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)


    以下可能是我所缺乏的常识,但是。不久前,我们有一个包含虚拟属性的bug案例。将上下文抽象一点,考虑以下代码,并将断点应用于指定区域:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    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对象上下文中,当添加base.Property作为一个表,或在quickwatch中键入base.Property时,可以获得相同的行为。

    我花了些时间才意识到发生了什么。最后我被快速表给启发了。当进入QuickWatch并浏览Derived对象d(或从对象上下文this中)并选择字段base时,QuickWatch顶部的编辑字段显示以下转换:

    1
    ((TestProject1.Base)(d))

    也就是说,如果这样替换base,那么调用

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

    对于手表,QuickWatch和调试鼠标在工具提示上,然后在考虑多态性时显示"AWESOME"而不是"BASE_AWESOME",这是有意义的。我仍然不确定为什么它会把它转换成一个演员,一个假设是call可能无法从这些模块的上下文中获得,而只有callvirt

    不管怎样,这显然不会改变任何功能,Derived.BaseProperty仍然会返回"BASE_AWESOME",因此这不是我们工作中bug的根源,只是一个令人困惑的组件。但是,我发现它很有趣,它如何误导开发人员,开发人员在调试会话期间不会意识到这一事实,特别是如果base未在您的项目中公开,而是被引用为第三方dll,从而导致devs只说:

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


    您有没有想过C编译器会生成无效的CIL?运行这个,你会得到一个TypeLoadException

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    interface I<T> {
      T M(T p);
    }
    abstract class A<T> : I<T> {
      public abstract T M(T p);
    }
    abstract class B<T> : A<T>, I<int> {
      public override T M(T p) { return p; }
      public int M(int p) { return p * 2; }
    }
    class C : B<int> { }

    class Program {
      static void Main(string[] args) {
        Console.WriteLine(new C().M(42));
      }
    }

    不过,我不知道在C 4.0编译器中它是如何运行的。

    编辑:这是我的系统的输出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    C:\Temp>type Program.cs
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    namespace ConsoleApplication1 {

      interface I<T> {
        T M(T p);
      }
      abstract class A<T> : I<T> {
        public abstract T M(T p);
      }
      abstract class B<T> : A<T>, I<int> {
        public override T M(T p) { return p; }
        public int M(int p) { return p * 2; }
      }
      class C : B<int> { }

      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]


    我不确定你会说这是Windows Vista/7的怪胎还是.NET的怪胎,但它让我抓耳挠腮了一会儿。

    1
    2
    3
    4
    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."

    在Windows Vista/7中,文件将实际写入C:\Users\\Virtual Store\Program Files\my folder\test.txt


    C有一些非常令人兴奋的地方,它处理闭包的方式。

    它不是将堆栈变量值复制到无闭包变量,而是将变量的所有出现都打包到一个对象中,从而将其移出堆栈-直接移到堆中!:)

    我猜,这使得C语言在功能上比ML本身(使用堆栈值复制afaik)更加完整(或者lambda完成huh))。F也有这个特性,就像C一样。

    这确实给我带来了很多快乐,谢谢你们,伙计们!

    不过,这不是一件怪事,也不是一件角落里的案子…但是,基于堆栈的虚拟机语言真的出乎意料:)


    从我不久前问的一个问题来看:

    条件运算符不能隐式强制转换?

    鉴于:

    1
    Bool aBoolValue;

    如果指定aBoolValue为真或假;

    以下内容将无法编译:

    1
    Byte aByteValue = aBoolValue ? 1 : 0;

    但这会:

    1
    Int anIntValue = aBoolValue ? 1 : 0;

    提供的答案也很好。


    以下内容不起作用:

    1
    2
    3
    4
    if (something)
        doit();
    else
        var v = 1 + 2;

    但这是可行的:

    1
    2
    3
    4
    5
    if (something)
        doit();
    else {
        var v = 1 + 2;
    }


    C中的范围有时确实很奇怪。让我举一个例子:

    1
    2
    3
    4
    5
    6
    if (true)
    {
       OleDbCommand command = SQLServer.CreateCommand();
    }

    OleDbCommand command = SQLServer.CreateCommand();

    编译失败,因为命令被重新声明?关于它为什么在stackoverflow的这个线程和我的博客中这样工作,有一些有趣的猜测。


    以下是我的一些:

  • 在调用实例方法时,如果不引发NullReferenceException,则此值可以为空。
  • 不必为枚举定义默认枚举值
  • 先简单一点:零点{号码=1}

    1
    2
    3
    4
    5
            public bool ReturnsFalse()
            {
                //The default value is not defined!
                return Enum.IsDefined(typeof (NoZero), default(NoZero));
            }

    下面的代码实际上可以打印为真!

    1
    2
    3
    4
    5
    6
    7
     internal sealed class Strange
    {
        public void Foo()
        {
            Console.WriteLine(this == null);
        }
    }

    一个简单的客户端代码,它将导致代表空虚的地狱之门(奇怪的酒吧);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class Program
    {
        [STAThread()]
        public static void Main(string[] args)
        {
            Strange bar = null;
            var hello = new DynamicMethod("ThisIsNull",
                typeof(void), new[] { typeof(Strange) },
             typeof(Strange).Module);
            ILGenerator il = hello.GetILGenerator(256);
            il.Emit(OpCodes.Ldarg_0);
            var foo = typeof(Strange).GetMethod("Foo");
            il.Emit(OpCodes.Call, foo);
            il.Emit(OpCodes.Ret);
            var print = (HelloDelegate)hello.CreateDelegate(typeof(HelloDelegate));
            print(bar);
            Console.ReadLine();
        }
    }

    在大多数语言中,只要实例方法被调用时不使用对象的状态,这实际上是正确的。只有在访问对象的状态时才会取消引用。


    这个很简单,但我还是觉得有点有趣。打给foo后x的值是多少?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    static int x = 0;

    public static void Foo()
    {
        try { return; }
        finally { x = 1; }
    }

    static void Main() { Foo(); }


    如果您有扩展方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static bool? ToBoolean(this string s)
    {
        bool result;

        if (bool.TryParse(s, out result))
            return result;
        else
            return null;
    }

    这个代码:

    1
    2
    string nullStr = null;
    var res = nullStr.ToBoolean();

    这不会引发异常,因为它是一个扩展方法(实际上是HelperClass.ToBoolean(null)),而不是实例方法。这可能会令人困惑。


    这一次我真的很困惑(我为长度道歉,但它是winform)。不久前我把它发到了新闻组。

    I've come across an interesting bug. I
    have workarounds but i'd like to know
    the root of the problem. I've stripped
    it down into a short file and hope
    someone might have an idea about
    what's going on.

    It's a simple program that loads a
    control onto a form and binds"Foo"
    against a combobox ("SelectedItem")
    for it's"Bar" property and a
    datetimepicker ("Value") for it's
    "DateTime" property. The
    DateTimePicker.Visible value is set to
    false. Once it's loaded up, select the
    combobox and then attempt to deselect
    it by selecting the checkbox. This is
    rendered impossible by the combobox
    retaining the focus, you cannot even
    close the form, such is it's grasp on
    the focus.

    I have found three ways of fixing this
    problem.

    a) Remove the binding to Bar (a bit
    obvious)

    b) Remove the binding to
    DateTime

    c) Make the DateTimePicker
    visible !?!

    I'm currently running Win2k. And .NET
    2.00, I think 1.1 has the same problem. Code is below.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    using System;
    using System.Collections;
    using System.Windows.Forms;

    namespace WindowsApplication6
    {
        public class Bar
        {
            public Bar()
            {
            }
        }

        public class Foo
        {
            private Bar m_Bar = new Bar();
            private DateTime m_DateTime = DateTime.Now;

            public Foo()
            {
            }

            public Bar Bar
            {
                get
                {
                    return m_Bar;
                }
                set
                {
                    m_Bar = value;
                }
            }

            public DateTime DateTime
            {
                get
                {
                    return m_DateTime;
                }
                set
                {
                    m_DateTime = value;
                }
            }
        }

        public class TestBugControl : UserControl
        {
            public TestBugControl()
            {
                InitializeComponent();
            }

            public void InitializeData(IList types)
            {
                this.cBoxType.DataSource = types;
            }

            public void BindFoo(Foo foo)
            {
                this.cBoxType.DataBindings.Add("SelectedItem", foo,"Bar");
                this.dtStart.DataBindings.Add("Value", foo,"DateTime");
            }

            /// <summary>
            /// Required designer variable.
            /// </summary>
            private System.ComponentModel.IContainer components = null;

            /// <summary>
            /// Clean up any resources being used.
            /// </summary>
            /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
            protected override void Dispose(bool disposing)
            {
                if (disposing && (components != null))
                {
                    components.Dispose();
                }
                base.Dispose(disposing);
            }

            #region Component Designer generated code

            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            private void InitializeComponent()
            {
                this.checkBox1 = new System.Windows.Forms.CheckBox();
                this.cBoxType = new System.Windows.Forms.ComboBox();
                this.dtStart = new System.Windows.Forms.DateTimePicker();
                this.SuspendLayout();
                //
                // checkBox1
                //
                this.checkBox1.AutoSize = true;
                this.checkBox1.Location = new System.Drawing.Point(14, 5);
                this.checkBox1.Name ="checkBox1";
                this.checkBox1.Size = new System.Drawing.Size(97, 20);
                this.checkBox1.TabIndex = 0;
                this.checkBox1.Text ="checkBox1";
                this.checkBox1.UseVisualStyleBackColor = true;
                //
                // cBoxType
                //
                this.cBoxType.FormattingEnabled = true;
                this.cBoxType.Location = new System.Drawing.Point(117, 3);
                this.cBoxType.Name ="cBoxType";
                this.cBoxType.Size = new System.Drawing.Size(165, 24);
                this.cBoxType.TabIndex = 1;
                //
                // dtStart
                //
                this.dtStart.Location = new System.Drawing.Point(117, 40);
                this.dtStart.Name ="dtStart";
                this.dtStart.Size = new System.Drawing.Size(165, 23);
                this.dtStart.TabIndex = 2;
                this.dtStart.Visible = false;
                //
                // TestBugControl
                //
                this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
                this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
                this.Controls.Add(this.dtStart);
                this.Controls.Add(this.cBoxType);
                this.Controls.Add(this.checkBox1);
                this.Font = new System.Drawing.Font("Verdana", 9.75F,
                System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point,
                ((byte)(0)));
                this.Margin = new System.Windows.Forms.Padding(4);
                this.Name ="TestBugControl";
                this.Size = new System.Drawing.Size(285, 66);
                this.ResumeLayout(false);
                this.PerformLayout();

            }

            #endregion

            private System.Windows.Forms.CheckBox checkBox1;
            private System.Windows.Forms.ComboBox cBoxType;
            private System.Windows.Forms.DateTimePicker dtStart;
        }

        public class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
                this.Load += new EventHandler(Form1_Load);
            }

            void Form1_Load(object sender, EventArgs e)
            {
                InitializeControl();
            }

            public void InitializeControl()
            {
                TestBugControl control = new TestBugControl();
                IList list = new ArrayList();
                for (int i = 0; i < 10; i++)
                {
                    list.Add(new Bar());
                }
                control.InitializeData(list);
                control.BindFoo(new Foo());
                this.Controls.Add(control);
            }

            /// <summary>
            /// Required designer variable.
            /// </summary>
            private System.ComponentModel.IContainer components = null;

            /// <summary>
            /// Clean up any resources being used.
            /// </summary>
            /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
            protected override void Dispose(bool disposing)
            {
                if (disposing && (components != null))
                {
                    components.Dispose();
                }
                base.Dispose(disposing);
            }

            #region Windows Form Designer generated code

            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            private void InitializeComponent()
            {
                this.components = new System.ComponentModel.Container();
                this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
                this.Text ="Form1";
            }

            #endregion
        }

        static class Program
        {
            /// <summary>
            /// The main entry point for the application.
            /// </summary>
            [STAThread]
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());
            }
        }
    }

    以下打印错误,而不是引发溢出异常:

    1
    2
    3
    4
    5
    6
    7
    Console.WriteLine("{0}", yep(int.MaxValue ));


    private bool yep( int val )
    {
        return ( 0 < val * 2);
    }


    我认为这个问题的答案是因为.NET使用字符串分隔可能导致相等字符串指向同一对象的内容(因为字符串是可变的,所以这不是问题)

    (我不是在谈论字符串类上重写的相等运算符)