我收集了一些极端案例和脑筋急转弯,总是想听到更多。这个页面只涵盖了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上尝试过……)
只是为了澄清,这只是我正在寻找的事情的一个例子-我并不是特别寻找对这个奇怪现象的讨论/解释。(这和普通的弦乐实习不一样;特别地,当调用构造函数时,字符串实习通常不会发生。)我真的是在要求类似的奇怪行为。
还有其他的宝藏吗?
我想我之前向您展示过这个,但我喜欢这里的乐趣——这需要一些调试才能跟踪!(原来的代码显然更加复杂和微妙……)
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());
}
那么T是什么?
答:任何可空<T> -如int?。所有的方法都被重写,除了GetType()不能;因此它被强制转换为object(因此为null)来调用object. gettype()…哪个调用null;-p
更新:情节变得越来越复杂……Ayende Rahien在他的博客上提出了类似的挑战,但使用了where T: class, new():
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?");
}
但它是可以被打败的!使用与远程处理一样的间接方式;警告-以下是纯粹的邪恶:
class MyFunnyProxyAttribute : ProxyAttribute {
public override MarshalByRefObject CreateInstance(Type serverType) {
return null;
}
}
[MyFunnyProxy]
class MyFunnyType : ContextBoundObject { }
在此之后,new()调用被重定向到代理(MyFunnyProxyAttribute),该代理返回null。现在去洗眼睛!
银行家的舍入。
这不是一个编译器错误或故障,但肯定是一个奇怪的极端情况…
. net框架采用了一种被称为银行家舍入的方案或舍入。
在银行家的四舍五入中,0.5的数字四舍五入到最接近的偶数,所以
Math.Round(-0.5) == 0
Math.Round(0.5) == 0
Math.Round(1.5) == 2
Math.Round(2.5) == 2
etc...
这可能会导致基于更广为人知的四舍五入的财务计算中出现一些意想不到的错误。
Visual Basic也是如此。
有趣的是,当我第一次看到它时,我认为这是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);
如果检查string,它也等于true。这意味着这个操作代码必须有特殊的行为来实习空字符串。
如果作为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位机器来验证这一点,但该方法确实满足尾部调用优化的所有条件。如果有人有的话,我很想看看这是不是真的。
这个让我很困惑(我很抱歉篇幅太长,但它是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.
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());
}
}
}
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[]'
我来派对晚了一点,但我有三四五
If you poll InvokeRequired on a control that hasn't been loaded/shown, it will say false - and blow up in your face if you try to change it from another thread (the solution is to reference this.Handle in the creator of the control).
Another one which tripped me up is that given an assembly with:
enum MyEnum
{
Red,
Blue,
}
if you calculate MyEnum.Red.ToString() in another assembly, and in between times someone has recompiled your enum to:
enum MyEnum
{
Black,
Red,
Blue,
}
at runtime, you will get "Black".
I had a shared assembly with some handy constants in. My predecessor had left a load of ugly-looking get-only properties, I thought I'd get rid of the clutter and just use public const. I was more than a little surprised when VS compiled them to their values, and not references.
If you implement a new method of an interface from another assembly, but you rebuild referencing the old version of that assembly, you get a TypeLoadException (no implementation of 'NewMethod'), even though you have implemented it (see here).
Dictionary<,>: "The order in which the items are returned is undefined". This is horrible, because it can bite you sometimes, but work others, and if you've just blindly assumed that Dictionary is going to play nice ("why shouldn't it? I thought, List does"), you really have to have your nose in it before you finally start to question your assumption.
下面是一个示例,说明如何创建导致错误消息“试图读写受保护内存”的结构。这通常表明其他记忆被破坏了。”
成功与失败的差别非常细微。
下面的单元测试演示了这个问题。
看看你能不能找出问题出在哪里。
[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);
}
}
几年前,在制定忠诚度计划时,我们遇到了一个关于给予客户积分数量的问题。这个问题与将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()(或Math. ceiling())总是更好。用符合我们要求的midpointrsurround进行四舍五入)
int i1 = Convert.ToInt32( Math.Ceiling(d) );
int i2 = (int) Math.Ceiling(d);
即使枚举函数重载,它们也应该使0为整数。
我知道c#核心团队将0映射到enum的基本原理,但是,它仍然没有像它应该的那样正交。来自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");
}
}
}
我发现了第二个非常奇怪的极端情况,远远超过了我的第一个。
字符串。Equals Method (String, String, StringComparison)实际上并不是没有副作用的。
我当时在写一段代码,在某个函数的顶部有一行这样的代码:
stringvariable1.Equals(stringvariable2, StringComparison.InvariantCultureIgnoreCase);
删除这一行将导致程序中其他地方的堆栈溢出。
这段代码实际上是在为BeforeAssemblyLoad事件安装一个处理程序,并尝试执行该处理程序
if (assemblyfilename.EndsWith("someparticular.dll", StringComparison.InvariantCultureIgnoreCase))
{
assemblyfilename = "someparticular_modified.dll";
}
到现在我都不用告诉你了。在字符串比较中使用从未使用过的区域性会导致程序集负载。InvariantCulture也不例外。
PropertyInfo.SetValue()可以将int赋值给enum,将int赋值给可空的int,将enum赋值给可空的enum,但不能将int赋值给可空的enum。
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 !!!
完整描述在这里
分配!
这是我在聚会上喜欢问的一个问题(这可能是我不再被邀请的原因):
你能编译下面这段代码吗?
public void Foo()
{
this = new Teaser();
}
一个简单的欺骗可以是:
string cheat = @"
public void Foo()
{
this = new Teaser();
}
";
但真正的解决方案是:
public struct Teaser
{
public void Foo()
{
this = new Teaser();
}
}
值类型(结构体)可以重新赋值它们的this变量,这是一个小常识。
以下可能是我缺乏的常识,但是,嗯。前段时间,我们遇到了一个包含虚拟财产的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是
, . .做一些有趣的事情”
我不确定你是否会说这是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\<username>\Virtual Store\Program Files\my folder\test.txt
这是我无意中遇到的最奇怪的事情:
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 +操作符执行字符串连接。如果字符串连接的操作数为空,则替换为空字符串。否则,任何非字符串操作数都将通过调用继承自type object的虚拟ToString方法转换为其字符串表示形式。如果ToString返回null,则替换为空字符串。
你有没有想过c#编译器会生成无效的CIL?运行这个,你会得到一个typeloadeexception:
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编译器中表现如何。
编辑:这是从我的系统输出:
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]
如果您有一个泛型类,它的方法可以根据类型参数而变得模糊,该怎么办?我最近在写一本双向词典时遇到了这种情况。我想要编写对称的Get()方法,它将返回传递的任何参数的相反值。就像这样:
class TwoWayRelationship<T1, T2>
{
public T2 Get(T1 key) { /* ... */ }
public T1 Get(T2 key) { /* ... */ }
}
如果你创建一个实例,其中T1和T2是不同类型的,那么一切都很好:
var r1 = new TwoWayRelationship<int, string>();
r1.Get(1);
r1.Get("a");
但如果T1和T2是相同的(可能如果一个是另一个的子类),这是一个编译器错误:
var r2 = new TwoWayRelationship<int, int>();
r2.Get(1); // "The call is ambiguous..."
有趣的是,第二种情况下的所有其他方法仍然可用;只有调用现在模棱两可的方法才会导致编译器错误。有趣的案例,只是有点不太可能和晦涩。
在我们使用的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;
}
}
}
内部类可以完全访问外部类成员。在这种情况下,内部类也恰好派生自外部类。这允许我们“打破”私有成员的封装。
以下是我的一些建议:
当调用实例方法而不抛出NullReferenceException时,此值可以为null
不必为枚举定义默认枚举值
首先简单一点:
enum NoZero
{
Number = 1
}
public bool ReturnsFalse()
{
//The default value is not defined!
return Enum.IsDefined(typeof (NoZero), default(NoZero));
}
下面的代码实际上可以打印真!
internal sealed class Strange
{
public void Foo()
{
Console.WriteLine(this == null);
}
}
一段简单的客户端代码将导致这样的结果
HelloDelegate(奇怪的条);
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();
}
}
这在大多数语言中都是正确的,只要调用实例方法时不使用对象的状态。只有在访问对象的状态时才解除引用
想想这个奇怪的例子:
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。
编译器必须这样做,以支持与接口有关的多态分派,即它必须使该方法为虚拟。
This one's pretty hard to top. I ran into it while I was trying to build a RealProxy implementation that truly supports Begin/EndInvoke (thanks MS for making this impossible to do without horrible hacks). This example is basically a bug in the CLR, the unmanaged code path for BeginInvoke doesn't validate that the return message from RealProxy.PrivateInvoke (and my Invoke override) is returning an instance of an IAsyncResult. Once it's returned, the CLR gets incredibly confused and loses any idea of whats going on, as demonstrated by the tests at the bottom.
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.
}
}
}
输出:
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)