你在c#或。net中见过的最奇怪的极端情况是什么?

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

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

还有其他的宝藏吗?

121228 次浏览

我想我之前向您展示过这个,但我喜欢这里的乐趣——这需要一些调试才能跟踪!(原来的代码显然更加复杂和微妙……)

    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是什么?

答案:任何Nullable<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。现在去洗眼睛!

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

输出为“试图读取受保护的内存。”这表明其他记忆已经被破坏了。”

银行家的舍入。

这不是一个编译器错误或故障,但肯定是一个奇怪的极端情况…

. 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也是如此。

我认为这个问题的答案是因为。net使用字符串实习,这可能会导致相同的字符串指向相同的对象(因为字符串是可变的,这不是一个问题)

(我说的不是string类上重写的相等运算符)

有趣的是,当我第一次看到它时,我认为这是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.Empty,它也等于true,这意味着这个操作代码必须有特殊的行为来实习空字符串。

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

比尔发现你可以破解一个布尔值,如果a为真,B为真,(a和B)为假。

黑布尔值

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

下面将打印False而不是抛出溢出异常:

Console.WriteLine("{0}", yep(int.MaxValue ));




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

这个让我很困惑(我很抱歉篇幅太长,但它是WinForm)。我之前在新闻组中发布了它。

我遇到了一个有趣的bug。我 有变通办法,但我想知道 问题的根源。我已经剥夺了 它变成了一个短文件和希望 也许有人知道

这是一个简单的程序,加载一个 控件到窗体上,并绑定"Foo" 针对一个组合框("SelectedItem") 因为它是"Bar"属性 datetimepicker ("Value") “日期时间”属性。的 DateTimePicker。可见值设置为 假的。加载完成后,选择 组合框,然后尝试取消选择 它通过选择复选框。这是 被组合框渲染成不可能 保持专注,你甚至不能 关闭形式,这是它的把握 焦点。< / p > 我找到了三种方法来解决这个问题 问题。< / p >

a)移除对Bar的绑定(bit . 明显的)< / p >

b)删除绑定到 DateTime < / p > c)创建DateTimePicker 可见! ? !< / p >

我正在运行Win2k。和。net 2.00,我认为1.1也有同样的问题。

.代码如下
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[]'

我来派对有点晚了,但我有三个 四个 5:

  1. 如果你轮询invokerrequired控件还没有加载/显示,它会说假-如果你试图从另一个线程改变它(解决方案是引用这个。句柄在控件的创建者)。

  2. 另一个让我困惑的是给定一个带有:

    enum MyEnum
    {
    Red,
    Blue,
    }
    

    如果你在另一个程序集中计算myenumer . red . tostring(),在此期间有人重新编译了你的enum:

    enum MyEnum
    {
    Black,
    Red,
    Blue,
    }
    

    在运行时,你会得到"Black".

  3. 我有一个共享程序集,其中有一些方便的常量。我的前任留下了大量难看的get-only属性,我想我应该摆脱这些混乱,只使用公共const。当VS编译它们的值,而不是引用时,我感到非常惊讶。

  4. 如果你从另一个程序集实现了一个接口的新方法,但你重新引用了该程序集的旧版本,你会得到一个TypeLoadException(没有实现'NewMethod'),即使你已经实现了它(参见here)。

  5. Dictionary<,>: "返回项的顺序未定义"。这是恐怖,因为它有时会咬你一口,但也会对其他人起作用,如果你只是盲目地假设Dictionary会玩得很好(“为什么不呢?我想,是List做的”),在你最终开始质疑你的假设之前,你真的必须深入了解它。

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

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

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

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

c#中的作用域有时真的很奇怪。让我给你们举个例子:

if (true)
{
OleDbCommand command = SQLServer.CreateCommand();
}


OleDbCommand command = SQLServer.CreateCommand();

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

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

interface IFoo
{
string Message {get;}
}
...
IFoo obj = new IFoo("abc");
Console.WriteLine(obj.Message);

上面的第一眼看起来很疯狂,但它是实际上法律。不,真的(虽然我错过了一个关键部分,但它不是任何像“添加一个名为IFoo的类”或“添加一个using别名以指向IFoo的类”这样的hacky)。

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

这是我迄今为止见过的最不寻常的一个(当然除了这里的!)

public class Turtle<T> where T : Turtle<T>
{
}

它允许你声明它,但没有真正的用途,因为它总是要求你用另一个Turtle来包装你塞在中间的任何类。

(笑话)我猜是乌龟一直往下走……[/笑话]

我发现了第二个非常奇怪的极端情况,远远超过了我的第一个。

字符串。Equals Method (String, String, StringComparison)实际上并不是没有副作用的。

我当时在写一段代码,在某个函数的顶部有一行这样的代码:

stringvariable1.Equals(stringvariable2, StringComparison.InvariantCultureIgnoreCase);

删除这一行将导致程序中其他地方的堆栈溢出。

这段代码实际上是在为BeforeAssemblyLoad事件安装一个处理程序,并尝试执行该处理程序

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

到现在我都不用告诉你了。在字符串比较中使用从未使用过的区域性会导致程序集负载。InvariantCulture也不例外。

VB。NET、可空值和三元操作符:

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

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

i到底包含了什么?0

这是令人惊讶的,但实际上是“正确的”行为:VB中的Nothing。NET与CLR中的null并不完全相同:对于值类型TNothing可以表示nulldefault(T),这取决于上下文。在上面的例子中,If推断IntegerNothing5的共同类型,所以,在这种情况下,Nothing意味着null1。

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对象上下文中,当添加base.Property作为手表或在quickwatch中输入base.Property时,可以得到相同的行为。

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

((TestProject1.Base)(d))

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

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

对于Watches、Quickwatch和调试鼠标移到工具提示,那么在考虑多态性时显示"AWESOME"而不是"BASE_AWESOME"就有意义了。我仍然不确定为什么它会将其转换为强制转换,一个假设是call可能无法从这些模块的上下文中获得,而只有callvirt

无论如何,这显然不会在功能方面改变任何东西,Derived.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的错误:

当一个或两个操作数都是string类型时,binary +操作符执行字符串连接。如果字符串连接的操作数为null,则替换为空字符串。否则,任何非字符串操作数都将通过调用继承自object类型的虚拟ToString方法转换为它的字符串表示形式。如果__ABC2返回null,则替换为空字符串。

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

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

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

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

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

你有没有想过c#编译器会生成无效的CIL?运行这个命令,你会得到一个TypeLoadException:

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) { /* ... */ }
}

如果你创建了一个实例,其中T1T2是不同类型的,那么一切都很好:

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

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

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,那么代码的行为现在已经改变了,因为空合并操作符实际上检查了null,而不是运行operator=Equals()

以下选项无效:

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

但这是可行的:

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

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

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

如果你有扩展方法:

public static bool? ToBoolean(this string s)
{
bool result;


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

这段代码:

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

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

以下是我的一些建议:

  1. 当调用实例方法而不抛出NullReferenceException时,此值可以为null
  2. 不必为枚举定义默认枚举值
第一个简单的问题: enum NoZero { Number = 1 } < / p >
        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 { }

如果BaseDerived在同一个程序集中声明,编译器将使Base::Method为虚拟且密封(在CIL中),即使Base没有实现接口。

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

编译器必须这样做,以支持与接口有关的多态分派,即它必须使该方法为虚拟。

今天刚发现一件小玩意儿

public class Base
{
public virtual void Initialize(dynamic stuff) {
//...
}
}
public class Derived:Base
{
public override void Initialize(dynamic stuff) {
base.Initialize(stuff);
//...
}
}

这会引发编译错误。

对方法'Initialize'的调用需要动态分派,但不能这样做,因为它是基本访问表达式的一部分。考虑强制转换动态参数或取消基本访问。

如果我写底。初始化(stuff as object);它工作得很完美,但这似乎是一个“神奇的词”,因为它的功能完全相同,一切仍然是动态的……

这一点很简单,但我还是觉得很有趣。调用Foo后x的值是多少?

static int x = 0;


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


static void Main() { Foo(); }

这个很难超越。当我试图构建一个真正支持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();
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)