我收集了一些角落案例和脑筋急转弯,并且总是希望听到更多.该页面仅涵盖C#语言位和bobs,但我也发现核心.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上尝试过,诚然......)
需要明确的是,这只是我正在寻找的那种事情的一个例子 - 我并不是特别想要讨论这种奇怪的事情.(它与正常的字符串实习不同;特别是,当调用构造函数时,通常不会发生字符串实习.)我真的要求类似的奇怪行为.
还有其他宝石潜伏在那里吗?
我想我以前给你看了这个,但我喜欢这里的乐趣 - 这需要一些调试来追踪!(原始代码显然更加复杂和微妙......)
static void Foo() 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()); }
那么T ...
答案:任何Nullable
- 如int?
.除了GetType()之外,所有方法都被覆盖; 所以它被转换(装箱)到object(因此为null)来调用object.GetType()...调用null ;-p
更新:情节更加浓密...... Ayende Rahien 在他的博客上提出了类似的挑战,但是where T : class, new()
:
private static void Main() { CanThisHappen(); } public static void CanThisHappen () 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?"); }
但它可以被击败!使用像远程处理这样的间接使用的相同方向; 警告 - 以下是纯粹的邪恶:
class MyFunnyProxyAttribute : ProxyAttribute { public override MarshalByRefObject CreateInstance(Type serverType) { return null; } } [MyFunnyProxy] class MyFunnyType : ContextBoundObject { }
有了这个,new()
调用将被重定向到proxy(MyFunnyProxyAttribute
),后者返回null
.现在去洗眼睛吧!
银行家的四舍五入.
这个不是编译器错误或故障,但肯定是一个奇怪的角落案例......
.Net Framework使用一种称为Banker's Rounding的方案或舍入.
在银行家的舍入中,0.5数字四舍五入到最接近的偶数,所以
Math.Round(-0.5) == 0 Math.Round(0.5) == 0 Math.Round(1.5) == 2 Math.Round(2.5) == 2 etc...
这可能导致基于更为人熟知的Round-Half-Up四舍五入的财务计算中的一些意外错误.
Visual Basic也是如此.
如果被调用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位机器来验证这一点,但该方法确实满足了尾部调用优化的所有条件.如果有人有,我有兴趣看看它是否属实.
这是我喜欢在聚会上提出的问题(这可能是我不再受邀的原因):
你可以编译下面这段代码吗?
public void Foo() { this = new Teaser(); }
一个简单的骗子可能是:
string cheat = @" public void Foo() { this = new Teaser(); } ";
但真正的解决方案是:
public struct Teaser { public void Foo() { this = new Teaser(); } }
所以值得知道的是,值类型(结构)可以重新分配它们的this
变量.
几年前,在制定忠诚计划时,我们遇到了给客户的积分问题.该问题与转换/转换double为int有关.
在下面的代码中:
double d = 13.6; int i1 = Convert.ToInt32(d); int i2 = (int)d;
i1 == i2?
事实证明,i1!= i2.由于Convert和cast运算符中的舍入策略不同,实际值为:
i1 == 14 i2 == 13
调用Math.Ceiling()或Math.Floor()(或MidgleRounding符合我们要求的Math.Round)总是更好
int i1 = Convert.ToInt32( Math.Ceiling(d) ); int i2 = (int) Math.Ceiling(d);
即使存在枚举函数重载,它们也应该使0为整数.
我知道C#核心团队的理由是将0映射到枚举,但是,它仍然不像它应该的那样正交.Npgsql的示例.
测试示例:
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"); } } }
这是迄今为止我见过的最不寻常的事情之一(当然除了这里的那些!):
public class Turtlewhere T : Turtle { }
它允许你声明它但没有实际用途,因为它总是要求你用另一只海龟包裹你在中心的任何类.
[笑话]我猜这是乌龟一路下来...... [/笑话]
这是我最近才发现的一个......
interface IFoo { string Message {get;} } ... IFoo obj = new IFoo("abc"); Console.WriteLine(obj.Message);
乍一看上面看起来很疯狂,但实际上是合法的.不,真的(虽然我错过了一个关键部分,但它并不像"添加一个叫做IFoo
" 的类或"添加using
别名指向IFoo
一个"类").
看看你能否找出原因,那么:谁说你无法实例化界面?
什么时候布尔既不是True也不是False?
Bill发现你可以破解一个布尔值,这样如果A为True且B为True,则(A和B)为False.
黑客布尔
我迟到了一下参加聚会,但我有3 4五:
如果你在一个尚未加载/显示的控件上轮询InvokeRequired,它会说是假的 - 如果你试图从另一个线程更改它,那么你的脸就会爆炸(解决方案是引用它.在创建者中引用它.控制).
另一个绊倒我的是给定一个装配:
enum MyEnum { Red, Blue, }
如果你在另一个程序集中计算MyEnum.Red.ToString(),并且在两次之间有人重新编译你的枚举:
enum MyEnum { Black, Red, Blue, }
在运行时,你会得到"黑色".
我有一个带有一些方便常量的共享程序集.我的前任留下了一堆丑陋的get-only属性,我以为我会摆脱杂乱而只是使用公共const.当VS将它们编译为它们的值而不是引用时,我有点惊讶.
如果从另一个程序集实现一个接口的新方法,但是重建引用该程序集的旧版本,则会得到一个TypeLoadException(没有实现'NewMethod'),即使你已经实现了它(参见这里).
字典<,>:"未定义项目的返回顺序".这太可怕了,因为它有时可能会咬你,但是工作别人,如果你只是盲目地认为词典会发挥得很好("为什么不应该呢?我想,List会这样做"),你真的需要在你最终开始质疑你的假设之前,请先了解它.
VB.NET,nullables和三元运算符:
Dim i As Integer? = If(True, Nothing, 5)
这花了我一些时间来调试,因为我希望i
包含Nothing
.
我真的包含什么?0
.
这是令人惊讶但实际上是"正确"的行为:Nothing
在VB.NET中与null
CLR 不完全相同:Nothing
可以表示null
或者default(T)
表示值类型T
,具体取决于上下文.在上述情况下,If
推断Integer
为普通型的Nothing
和5
,所以,在这种情况下,Nothing
装置0
.
我找到了第二个非常奇怪的角落案例,远远超过了我的第一个案例.
String.Equals方法(String,String,StringComparison)实际上不是副作用.
我正在研究一个代码块,它在一些函数的顶部单独使用它:
stringvariable1.Equals(stringvariable2, StringComparison.InvariantCultureIgnoreCase);
删除该行会导致程序中其他位置的堆栈溢出.
该代码原来是为了本质上是一个BeforeAssemblyLoad事件而安装一个处理程序并试图这样做
if (assemblyfilename.EndsWith("someparticular.dll", StringComparison.InvariantCultureIgnoreCase)) { assemblyfilename = "someparticular_modified.dll"; }
到现在为止我不应该告诉你.在字符串比较中使用之前未使用过的文化会导致程序集加载.InvariantCulture也不例外.
下面是一个如何创建结构的示例,该结构导致错误消息"尝试读取或写入受保护的内存.这通常表明其他内存已损坏".成功与失败之间的区别非常微妙.
以下单元测试演示了该问题.
看看你是否能解决出了什么问题.
[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#支持数组和列表之间的转换,只要数组不是多维的,并且类型之间存在继承关系,类型是引用类型
object[] oArray = new string[] { "one", "two", "three" }; string[] sArray = (string[])oArray; // Also works for IList (and IEnumerable, ICollection) IListsList = (IList )oArray; IList
请注意,这不起作用:
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[]'
这是我偶然遇到的最奇怪的事情:
public class DummyObject { public override string ToString() { return null; } }
使用如下:
DummyObject obj = new DummyObject(); Console.WriteLine("The text: " + obj.GetType() + " is " + obj);
会抛出一个NullReferenceException
.结果是C#编译器编译多次添加到一个调用String.Concat(object[])
.在.NET 4之前,在Concat的重载中存在一个错误,其中对象被检查为null,但不是ToString()的结果:
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的错误:
当一个或两个操作数是类型时,binary +运算符执行字符串连接
string
.如果字符串连接的操作数是null
,则替换空字符串.否则,通过调用ToString
从类型继承的虚方法,将任何非字符串操作数转换为其字符串表示形式object
.如果ToString
返回null
,则替换空字符串.
有意思 - 当我第一次看到它时,我认为它是C#编译器正在检查的东西,但即使你直接发出IL以消除任何干扰的可能性,它仍然会发生,这意味着它真的是正在执行的newobj
操作码检查.
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);
它也等同于true
你检查string.Empty
哪个意味着这个操作码必须具有实习空字符串的特殊行为.
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
输出"尝试读取受保护的内存.这表示其他内存已损坏."
PropertyInfo.SetValue()可以为枚举分配int,为可为空的int分配int,为可为空的枚举分配枚举,但不为可为空的枚举分配int.
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 !!!
这里有完整描述
如果您的泛型类具有可能根据类型参数而变得模糊的方法,该怎么办?我最近遇到这种情况写了一本双向字典.我想写对称Get()
方法,它会返回与传递的参数相反的方法.像这样的东西:
class TwoWayRelationship{ public T2 Get(T1 key) { /* ... */ } public T1 Get(T2 key) { /* ... */ } }
如果你让这样的情况:一切都很好很好T1
,并T2
有不同的类型:
var r1 = new TwoWayRelationship(); r1.Get(1); r1.Get("a");
但是如果T1
并且T2
是相同的(并且可能如果一个是另一个的子类),则是编译器错误:
var r2 = new TwoWayRelationship(); r2.Get(1); // "The call is ambiguous..."
有趣的是,第二种情况下的所有其他方法仍然可用; 它只调用现在不明确的方法导致编译器错误.有趣的案例,如果有点不太可能和模糊.
以下派生类从其基类访问私有字段,编译器静默查找另一端:
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 class Base { public virtual void Initialize(dynamic stuff) { //... } } public class Derived:Base { public override void Initialize(dynamic stuff) { base.Initialize(stuff); //... } }
这会引发编译错误.
对方法'Initialize'的调用需要动态调度,但不能因为它是基本访问表达式的一部分.考虑转换动态参数或消除基本访问.
如果我写base.Initialize(东西作为对象); 它完美地工作,但这似乎是一个"神奇的词",因为它完全相同,一切仍然被收到动态......
在我们使用的API中,返回域对象的方法可能会返回一个特殊的"null对象".在执行这个,比较操作符和Equals()
方法被覆盖,返回true
,如果它与比较null
.
所以这个API的用户可能有这样的代码:
return test != null ? test : GetDefault();
或许更冗长,像这样:
if (test == null) return GetDefault(); return test;
where GetDefault()
是一个返回我们想要使用的默认值的方法而不是null
.当我使用ReSharper并遵循它的建议将其中任何一个重写为以下内容时,令我惊讶的是:
return test ?? GetDefault();
如果测试对象是从API返回的空对象而不是正确的null
,则代码的行为现在已经改变,因为空合并操作符实际上检查null
,而不是运行operator=
或Equals()
.
考虑这个奇怪的情况:
public interface MyInterface { void Method(); } public class Base { public void Method() { } } public class Derived : Base, MyInterface { }
如果Base
和Derived
在同一个程序集中声明,编译器将进行Base::Method
虚拟和密封(在CIL中),即使Base
没有实现接口.
如果Base
并且Derived
在不同的程序集中,在编译Derived
程序集时,编译器将不会更改其他程序集,因此它将引入一个成员,Derived
这将是一个显式实现MyInterface::Method
,只需委托调用Base::Method
.
编译器必须这样做才能支持关于接口的多态调度,即它必须使该方法成为虚拟的.
以下可能是我只是缺乏的一般知识,但是呃.前段时间,我们有一个包含虚拟属性的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
对象上下文中,您可以在添加base.Property
为手表或键入快速手表时获得相同的行为base.Property
.
花了我一些时间才意识到发生了什么.最后,我受到了Quickwatch的启发.当进入Quickwatch并浏览Derived
对象d(或从对象的上下文中this
)并选择字段时base
,Quickwatch顶部的编辑字段显示以下强制转换:
((TestProject1.Base)(d))
这意味着如果基地被替换,那么呼叫将是
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,等等......什么?omg那个DLL就像是,......做一些有趣的事情"
这个很难达到顶峰.当我尝试构建一个真正支持Begin/EndInvoke的RealProxy实现时,我遇到了它(感谢MS让没有可怕的黑客无法做到这一点).此示例基本上是CLR中的错误,BeginInvoke的非托管代码路径不验证来自RealProxy.PrivateInvoke(以及我的Invoke覆盖)的返回消息是否返回IAsyncResult的实例.一旦它返回,CLR就会感到非常困惑,并且不知道最近会发生什么,正如底部测试所证明的那样.
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(); FuncdoSomethingDelegate = 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. } } }
输出:
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)
我不确定你是否说这是一个Windows Vista/7奇怪或.Net奇怪但它让我挠了一会儿.
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\
你有没有想过C#编译器会生成无效的CIL?运行这个,你会得到一个TypeLoadException
:
interface I{ T M(T p); } abstract class A : I { public abstract T M(T p); } abstract class B : A , I { public override T M(T p) { return p; } public int M(int p) { return p * 2; } } class C : B { } class Program { static void Main(string[] args) { Console.WriteLine(new C().M(42)); } }
我不知道它在C#4.0编译器中的表现如何.
编辑:这是我系统的输出:
C:\Temp>type Program.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { interface I{ T M(T p); } abstract class A : I { public abstract T M(T p); } abstract class B : A , I { public override T M(T p) { return p; } public int M(int p) { return p * 2; } } class C : B { } 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]