我收集了一些角落案件和brain teasers并且总是希望听到更多 . 该页面仅涵盖C#语言位和bobs,但我也发现核心.NET的东西也很有趣 . 例如,这里是页面上的's one which isn' t,但我发现它令人难以置信:
string x = new string(new char[0]);
string y = new string(new char[0]);
Console.WriteLine(object.ReferenceEquals(x, y));
我希望打印False - 毕竟,"new"(带引用类型)总是会创建一个新对象,并且不会对它进行测试 . (我没有在Mono上尝试过,诚然......)
需要明确的是,这只是我正在寻找的那种事情的一个例子 - 我并不是特别想要讨论/解释这种奇怪的事情 . (它与正常的字符串实习不同;特别是,当调用构造函数时,通常不会发生字符串实习 . )我真的要求类似的奇怪行为 .
还有其他任何宝石潜伏在那里?
30 回答
这是我最近才发现的一个......
上面看起来很疯狂乍一看,但是 actually legal . 没有,真的(虽然我错过了一个关键部分,但是 isn't 任何hacky都像“添加一个名为
IFoo
" or "的类,在一个类中添加using
alias指向IFoo
” ) .看看你能弄清楚原因,然后:Who says you can’t instantiate an interface?
我不确定你是否说这是一个Windows Vista / 7奇怪或.Net怪,但它让我挠了一会儿 .
在Windows Vista / 7中,该文件实际上将写入
C:\Users\<username>\Virtual Store\Program Files\my folder\test.txt
银行家的四舍五入 .
这不是一个编译器错误或故障,但肯定是一个奇怪的角落案例......
.Net Framework使用一种称为Banker's Rounding的方案或舍入 .
在银行家的舍入中,0.5数字四舍五入到最接近的偶数,所以
这可能导致基于更为人熟知的Round-Half-Up四舍五入的财务计算中的一些意外错误 .
Visual Basic也是如此 .
C#支持数组和列表之间的转换,只要数组不是多维的,并且类型之间存在继承关系,类型是引用类型
请注意,这不起作用:
考虑这个奇怪的情况:
如果
Base
和Derived
在同一个程序集中声明,则编译器将使Base::Method
虚拟并密封(在CIL中),即使Base
未实现该接口 .如果
Base
和Derived
在不同的程序集中,则在编译Derived
程序集时,编译器不会更改其他程序集,因此它将在Derived
中引入一个成员,该成员将是MyInterface::Method
的显式实现,它将委托将调用委托给Base::Method
.编译器必须这样做以便支持关于接口的多态分派,即它必须使该方法是虚拟的 .
这个很难达到顶峰 . 当我试图 Build 一个真正支持Begin / EndInvoke的RealProxy实现时,我碰到了它(感谢MS让没有可怕的黑客无法做到这一点) . 此示例基本上是CLR中的错误,BeginInvoke的非托管代码路径不验证来自RealProxy.PrivateInvoke(和我的Invoke覆盖)的返回消息是否返回IAsyncResult的实例 . 一旦它返回,CLR就会变得非常困惑,并且不知道最近会发生什么,正如底部测试所证明的那样 .
输出:
几年前,在制定忠诚计划时,我们遇到了给客户的积分问题 . 该问题与转换/转换double为int有关 .
在下面的代码中:
does i1 == i2 ?
事实证明,i1!= i2 . 由于Convert和cast运算符中的舍入策略不同,实际值为:
调用Math.Ceiling()或Math.Floor()(或MidgleRounding符合我们要求的Math.Round)总是更好
我想我以前给你看了这个,但我喜欢这里的乐趣 - 这需要一些调试来追踪! (原始代码显然更加复杂和微妙......)
那么T ...
答案:任何
Nullable<T>
- 例如int?
. 除了GetType()之外,所有方法都被覆盖;所以它被转换(装箱)到object(因此为null)来调用object.GetType()...调用null ;-p更新:情节变浓...... Ayende Rahien扔了similar challenge on his blog,但是
where T : class, new()
:但它可以被击败!使用像远程处理这样的间接使用的相同方向;警告 - 以下是 pure evil :
有了这个,
new()
调用被重定向到代理(MyFunnyProxyAttribute
),它返回null
. 现在去洗眼睛吧!c#中的范围确实很奇怪 . 让我举个例子:
这无法编译,因为命令被重新声明?有一些有趣的猜测,为什么它在thread on stackoverflow和my blog中以这种方式工作 .
即使存在枚举函数重载,它们也应该使0为整数 .
我知道C#核心团队的理由是将0映射到枚举,但是,它仍然不像它应该的那样正交 . Npgsql中的示例 .
测试示例:
以下可能是我只是缺乏的一般知识,但是呃 . 前段时间,我们有一个包含虚拟属性的bug案例 . 稍微抽象一下上下文,考虑以下代码,并将断点应用于指定区域:
在
Derived
对象上下文中,将base.Property
添加为 Watch 或在快速 Watch 中键入base.Property
时,可以获得相同的行为 .花了我一些时间才意识到发生了什么 . 最后,我受到了Quickwatch的启发 . 进入Quickwatch并浏览
Derived
对象d(或从对象的上下文this
)并选择字段base
时,Quickwatch顶部的编辑字段显示以下演员:这意味着如果基地被替换,那么呼叫将是
对于Watches,Quickwatch和调试鼠标悬停工具提示,然后在考虑多态时它显示
"AWESOME"
而不是"BASE_AWESOME"
是有意义的 . 我仍然不确定它为什么会改变它是一个演员,一个假设是call
可能无法从这些模块的上下文中获得,只有callvirt
.无论如何,这显然不会改变任何功能,
Derived.BaseProperty
仍然会真正返回"BASE_AWESOME"
,因此这不是我们工作中的bug的根源,只是一个令人困惑的组件 . 然而,我确实觉得有趣的是它会误导开发人员,他们在调试会话期间不会意识到这一点,特别是如果Base
未在您的项目中公开,而是被引用为第三方DLL,导致Devs只是说:C#有一些令人兴奋的事情,它处理闭包的方式 .
它不是将堆栈变量值复制到闭包自由变量,而是将预处理器魔术包装到一个对象中,然后将其从堆栈中移出 - 直接到堆中! :)
我猜,这使得C#比ML本身(使用堆栈值复制AFAIK)更加功能完整(或lambda-complete huh)语言 . F#也有这个功能,就像C#一样 .
这确实给我带来了很多乐趣,谢谢MS家伙!
这不是一个奇怪或角落的情况......但是基于堆栈的VM语言确实出乎意料:)
我找到了第二个非常奇怪的角落案例,远远超过了我的第一个案例 .
String.Equals方法(String,String,StringComparison)实际上不是副作用 .
我正在研究一个代码块,它在一些函数的顶部单独使用它:
删除该行会导致程序中其他位置的堆栈溢出 .
该代码原来是为了本质上是一个BeforeAssemblyLoad事件而安装一个处理程序并试图这样做
到现在为止我不应该告诉你 . 在字符串比较中使用之前未使用过的文化会导致程序集加载 . InvariantCulture也不例外 .
When is a Boolean neither True nor False?
Bill发现你可以破解一个布尔值,这样如果A为True且B为True,则(A和B)为False .
Hacked Booleans
刚刚发现一件好事:
这会引发编译错误 .
需要动态调度方法'Initialize'的调用,但不能因为它是基本访问表达式的一部分 . 考虑转换动态参数或消除基本访问 .
如果我写base.Initialize(东西作为对象);它完美地工作,但这似乎是一个“神奇的词”,因为它完全相同,一切仍然被收到动态......
有意思 - 当我第一次看到它时,我认为它是C#编译器正在检查的东西,但即使你直接发出IL以消除任何干扰的机会它仍然会发生,这意味着它真的是
newobj
操作码正在做检查 .如果您检查
string.Empty
,它也等同于true
,这意味着此操作码必须对实习空字符串具有特殊行为 .C#辅助功能Puzzler
以下派生类从其基类访问 private field ,编译器静默查找另一端:
该领域确实是私人的:
小心猜测我们如何编译这样的代码?
.
.
.
.
.
.
.
答案
诀窍是将
Derived
声明为Base
的内部类:内部类可以完全访问外部类成员 . 在这种情况下,内部类也恰好来自外部类 . 这允许我们“打破”私人成员的封装 .
你有没有想过C#编译器可以生成无效的CIL?运行这个,你会得到
TypeLoadException
:我不知道它在C#4.0编译器中的表现如何 .
EDIT :这是我系统的输出:
如果调用
Rec(0)
(不在调试器下),该函数将执行什么操作?回答:
在32位JIT上,它应该导致StackOverflowException
在64位JIT上,它应该将所有数字打印到int.MaxValue
这是因为the 64-bit JIT compiler applies tail call optimisation,而32位JIT则没有 .
不幸的是,我没有一台64位机器来验证这一点,但该方法确实满足了尾部调用优化的所有条件 . 如果有人有,我有兴趣看看它是否属实 .
这是我偶然遇到的最奇怪的事情:
使用如下:
会抛出
NullReferenceException
. 结果是C#编译器编译多次添加到String.Concat(object[])
的调用 . 在.NET 4之前,在Concat的重载中存在一个错误,其中对象被检查为null,但不是ToString()的结果:这是ECMA-334§14.7.4的错误:
下面是一个示例,说明如何创建一个结构,导致错误消息“尝试读取或写入受保护的内存 . 这通常表明其他内存已损坏” . 成功与失败之间的区别非常微妙 .
以下单元测试演示了该问题 .
看看你能解决出了什么问题 .
如果您的泛型类具有可能根据类型参数而变得模糊的方法,该怎么办?我最近遇到这种情况写了一本双向字典 . 我想编写对称的
Get()
方法,这些方法将返回与传递的参数相反的方法 . 像这样的东西:如果你创建一个
T1
和T2
是不同类型的实例,那么一切都很好:但是如果
T1
和T2
是相同的(并且可能如果一个是另一个的子类),则是编译器错误:有趣的是,第二种情况下的所有其他方法仍然可用;它只调用现在不明确的方法导致编译器错误 . 有趣的案例,如果有点不可能和晦涩难懂 .
在我们使用的API中,返回域对象的方法可能会返回特殊的"null object" . 在执行此操作时,如果将
Equals()
与null
进行比较,则会重写比较运算符和Equals()
方法以返回true
.所以这个API的用户可能有这样的代码:
或许更冗长,像这样:
其中
GetDefault()
是一个返回我们想要使用的默认值而不是null
的方法 . 当我使用ReSharper并遵循它建议将其中任何一个重写为以下内容时,这个惊喜让我感到惊讶:如果测试对象是从API返回的空对象而不是正确的
null
,则代码的行为现在已更改,因为空合并运算符实际上检查null
,而不是运行operator=
或Equals()
.分配这个!
这是我喜欢在聚会上提出的问题(这可能是我不再受邀的原因):
你可以编译下面这段代码吗?
一个简单的欺骗可能是:
但真正的解决方案是:
因此,值得知道的是,值类型(结构)可以重新分配它们的
this
变量 .这是迄今为止我见过的最不寻常的事情之一(当然除了这里的那些!):
它允许你声明它但没有实际用途,因为它总是会要求你用另一只Turtle包装你在中心的任何类 .
[笑话]我猜这是乌龟一路下来...... [/笑话]
我'm arriving a bit late to the party, but I'得到三四五:
如果你在一个尚未加载/显示的控件上轮询InvokeRequired,它会说是假的 - 如果你试图从另一个线程改变它,那就在你的脸上爆炸(the solution是引用这个 . 在创建者中的手柄控制) .
另一个绊倒我的是给定一个装配:
如果你在另一个程序集中计算MyEnum.Red.ToString(),并且在两次之间有人重新编译你的枚举:
在运行时,你会得到“黑色” .
我有一个共享程序集,里面有一些方便的常量 . 我的前任留下了一堆丑陋的get-only属性,我以为我会摆脱杂乱而只是使用公共const . 当VS将它们编译为它们的值而不是引用时,我有点惊讶 .
如果从另一个程序集实现接口的新方法,但重建引用该接口该程序集的旧版本,即使已实现它,也会得到TypeLoadException(没有'NewMethod'的实现)(请参阅here) .
Dictionary <,>:"The order in which the items are returned is undefined" . 这太可怕了,因为它有时候可能会咬你,但是会工作其他人,如果你只是盲目地认为词典会发挥得很好("why shouldn't it? I thought, List does"),那么在你最终开始质疑之前,你真的必须掏鼻子 . 假设 .
输出“尝试读取受保护的内存 . 这表示其他内存已损坏 . ”
VB.NET,nullables和三元运算符:
这花了我一些时间来调试,因为我期望
i
包含Nothing
.我真的包含什么?
0
.这是令人惊讶的但实际上_118151_行为:VB.NET中的
Nothing
与CLR中的null
不完全相同:Nothing
对于值类型T
可能意味着null
或default(T)
,具体取决于上下文 . 在上面的例子中,If
推断Integer
为Nothing
和5
的公共类型,因此,在这种情况下,Nothing
表示0
.PropertyInfo.SetValue()可以为枚举分配int,为可为空的int分配int,为可为空的枚举分配枚举,但不为可为空的枚举分配int .
完整描述here
从我不久前问过的一个问题:
Conditional operator cannot cast implicitly?
鉴于:
aBoolValue
被指定为True或False;以下内容无法编译:
但这会:
提供的答案也很不错 .