你在C#或.NET中看到的最奇怪的角落是什么?

Jon*_*eet 322 .net c#

我收集了一些角落案例和脑筋急转弯,并且总是希望听到更多.该页面仅涵盖C#语言位和bobs,但我也发现核心.NET的东西也很有趣.例如,这是一个不在页面上,但我觉得不可思议的:

string x = new string(new char[0]);
string y = new string(new char[0]);
Console.WriteLine(object.ReferenceEquals(x, y));
Run Code Online (Sandbox Code Playgroud)

我希望打印False - 毕竟,"new"(带引用类型)总是会创建一个新对象,不是吗?C#和CLI的规范都表明它应该.好吧,不是在这种特殊情况下.它打印True,并在我测试过的每个版本的框架上完成.(我没有在Mono上尝试过,诚然......)

需要明确的是,这只是我正在寻找的那种事情的一个例子 - 我并不是特别想要讨论这种奇怪的事情.(它与正常的字符串实习不同;特别是,当调用构造函数时,通常不会发生字符串实习.)我真的要求类似的奇怪行为.

还有其他宝石潜伏在那里吗?

Mar*_*ell 394

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

    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());
    }
Run Code Online (Sandbox Code Playgroud)

那么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?");
}
Run Code Online (Sandbox Code Playgroud)

但它可以被击败!使用像远程处理这样的间接使用的相同方向; 警告 - 以下是纯粹的邪恶:

class MyFunnyProxyAttribute : ProxyAttribute {
    public override MarshalByRefObject CreateInstance(Type serverType) {
        return null;
    }
}
[MyFunnyProxy]
class MyFunnyType : ContextBoundObject { }
Run Code Online (Sandbox Code Playgroud)

有了这个,new()调用将被重定向到proxy(MyFunnyProxyAttribute),后者返回null.现在去洗眼睛吧!

  • Drew:问题是GetType()不是虚拟的,所以它没有被覆盖 - 这意味着该方法调用的值被加框.该框成为空引用,因此是NRE. (69认同)
  • 非常非常酷.以一种不酷的方式.;-) (29认同)
  • @Drew; 另外,Nullable <T>有一些特殊的装箱规则,这意味着一个空的Nullable <T>框为null,而不是一个包含空Nullable <T>的框(和一个空的Nullable <T的空的un-box) >) (10认同)
  • 为什么不能定义Nullable <T> .GetType()?结果不应该是typeof(Nullable <T>)吗? (9认同)
  • 构造函数约束,在C#3.0 langauge规范中为10.1.5 (6认同)
  • 也许Java的类型擦除毕竟不是一个坏主意. (3认同)

Sam*_*Kim 216

银行家的四舍五入.

这个不是编译器错误或故障,但肯定是一个奇怪的角落案例......

.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...
Run Code Online (Sandbox Code Playgroud)

这可能导致基于更为人熟知的Round-Half-Up四舍五入的财务计算中的一些意外错误.

Visual Basic也是如此.

  • 如果人们不知道,你可以这样做:Math.Round(x,MidpointRounding.AwayFromZero); 改变舍入方案. (255认同)
  • 具有讽刺意味的是,我曾在*bank*工作过一次,而其他程序员开始忽略这一点,认为在框架中打破了舍入行为 (32认同)
  • 来自文档:此方法的行为遵循IEEE标准754第4节.这种舍入有时称为舍入到最近,或者是银行家的舍入.它最大限度地减少了在单个方向上始终舍入中点值所导致的舍入误差. (26认同)
  • 这对我来说似乎很奇怪.也就是说,至少,直到我找到一大堆数字并计算它们的总和.然后你会意识到,如果你简单地向上舍入,你最终可能会得到与非舍入数字之和的巨大差异.如果您正在进行财务计算,那就非常糟糕 (22认同)
  • 我想知道这是不是我看到`int(fVal + 0.5)`的原因,即使在具有内置舍入功能的语言中也是如此. (8认同)
  • 在金融环境中工作,我不同意.如果您的业务需求需要一种舍入方式,并且如果您的软件行为不同,则会出现问题.在繁重的统计计算中,银行家的四舍五入更准确 - 这就是我认为你所指的. (6认同)
  • @Jim Leonardo,在学校,.5被围捕.在此代码中,如果*up*为偶数,则向上舍入.5,如果*up*为奇数则向下舍入.给定以下数组`{0.5,1.5,2.5,3.5,4.5,5.5}`,舍入将返回`{0,2,2,4,4,6}`. (2认同)

Gre*_*ech 176

如果被调用Rec(0)(不在调试器下),这个函数会怎么做?

static void Rec(int i)
{
    Console.WriteLine(i);
    if (i < int.MaxValue)
    {
        Rec(i + 1);
    }
}
Run Code Online (Sandbox Code Playgroud)

回答:

  • 在32位JIT上,它应该导致StackOverflowException
  • 在64位JIT上,它应该将所有数字打印到int.MaxValue

这是因为64位JIT编译器应用尾调用优化,而32位JIT则不适用.

不幸的是,我没有一台64位机器来验证这一点,但该方法确实满足了尾部调用优化的所有条件.如果有人有,我有兴趣看看它是否属实.

  • StackOverflowException为+1 (130认同)
  • 必须在发布模式下编译,但绝对适用于x64 =) (10认同)
  • 那个'++`完全让我失望了.难道你不能像正常人一样叫"Rec(i + 1)"吗? (7认同)
  • 当VS 2010问世时,可能值得更新您的答案,因为所有当前的JIT都将在发布模式下执行TCO (3认同)
  • 刚试过32位WinXP上的VS2010 Beta 1.仍然得到StackOverflowException. (3认同)
  • 是的,JIT中的尾调用仅在编译器生成尾部操作码前缀时才有用,它看起来像C#编译器仍然没有.但是,等效的F#代码应该可以正常工作.:) (3认同)

Ome*_*Mor 111

分配这个!


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

你可以编译下面这段代码吗?

    public void Foo()
    {
        this = new Teaser();
    }
Run Code Online (Sandbox Code Playgroud)

一个简单的骗子可能是:

string cheat = @"
    public void Foo()
    {
        this = new Teaser();
    }
";
Run Code Online (Sandbox Code Playgroud)

但真正的解决方案是:

public struct Teaser
{
    public void Foo()
    {
        this = new Teaser();
    }
}
Run Code Online (Sandbox Code Playgroud)

所以值得知道的是,值类型(结构)可以重新分配它们的this变量.

  • 这也是一个骗子:`// this = new Teaser();`:-) (70认同)
  • :-)我更喜欢我的生产代码中的那些作弊,而不是这种重新分配的憎恶...... (17认同)
  • C++类也可以这样做......正如我最近发现的那样,只是为了实际尝试使用它进行优化而大喊大叫:p (3认同)
  • 从CLR到C#:他们之所以这样做是因为你可以在另一个构造函数中调用struct的无参数构造函数.如果你只想初始化一个结构的一个值,并希望其他值为零/ null(默认),你可以编写`public Foo(int bar){this = new Foo(); specialVar = bar;}`.这不是有效的,也不是真正合理的(`specialVar`被分配两次),但仅仅是FYI.(这就是书中给出的理由,我不知道为什么我们不应该只做`public Foo(int bar):this()`) (2认同)

Jar*_*das 100

几年前,在制定忠诚计划时,我们遇到了给客户的积分问题.该问题与转换/转换double为int有关.

在下面的代码中:

double d = 13.6;

int i1 = Convert.ToInt32(d);
int i2 = (int)d;
Run Code Online (Sandbox Code Playgroud)

i1 == i2

事实证明,i1!= i2.由于Convert和cast运算符中的舍入策略不同,实际值为:

i1 == 14
i2 == 13
Run Code Online (Sandbox Code Playgroud)

调用Math.Ceiling()或Math.Floor()(或MidgleRounding符合我们要求的Math.Round)总是更好

int i1 = Convert.ToInt32( Math.Ceiling(d) );
int i2 = (int) Math.Ceiling(d);
Run Code Online (Sandbox Code Playgroud)

  • @Max:是的,但为什么转换为圆形? (57认同)
  • 转换为整数不会舍入,只是将其切掉(实际上总是向下舍入).所以这完全有道理. (44认同)
  • @Stefan Steinegger如果一切都是演员,那么首先没有理由,是吗?另请注意,类名称为Convert not Cast. (18认同)
  • 在VB中:CInt()轮次.Fix()截断.烧了我一次(http://blog.wassupy.com/2006/01/i-can-believe-it-not-truncating.html) (3认同)

Mic*_*uen 74

即使存在枚举函数重载,它们也应该使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");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 哇,这对我来说是一个新的.同样很奇怪ConverTo.ToIn32()如何工作但是转换为(int)0却没有.任何其他数字> 0都有效.(通过"工作"我的意思是调用对象超载.) (18认同)
  • ConverTo.ToIn32()有效,因为它的结果是没有编译时常量.并且只有编译时常量0可以转换为枚举.在早期版本的.net中,即使只有文字"0"应该可以转换为枚举.请参阅Eric Lippert的博客:http://blogs.msdn.com/b/ericlippert/archive/2006/03/28/563282.aspx (5认同)
  • IMO他们应该引入一个关键字`none`,它可以被转换为任何枚举,并使0总是一个int而不是可以隐式转换为枚举. (2认同)

RCI*_*CIX 67

这是迄今为止我见过的最不寻常的事情之一(当然除了这里的那些!):

public class Turtle<T> where T : Turtle<T>
{
}
Run Code Online (Sandbox Code Playgroud)

它允许你声明它但没有实际用途,因为它总是要求你用另一只海龟包裹你在中心的任何类.

[笑话]我猜这是乌龟一路下来...... [/笑话]

  • 你可以创建实例:`class RealTurtle:Turtle <RealTurtle> {} RealTurtle t = new RealTurtle();` (34认同)
  • 确实.这是Java enums使用的模式,效果很好.我也在Protocol Buffers中使用它. (24认同)
  • 这是'奇怪的反复出现的模板模式'http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern (20认同)
  • 我在花哨的仿制药中使用了这种模式.它允许像正确类型的克隆,或创建自己的实例. (8认同)
  • RCIX,哦是的. (6认同)
  • 我纠正了. (2认同)
  • 我是第二个Lucero ...如果你想让基类上的方法返回继承者的确切类型,请使用它. (2认同)
  • 这不是"奇怪的重复模板模式",因为它们不是模板而是泛型.不迂腐.该模式在C++中的使用不适用于C#泛型. (2认同)

Mar*_*ell 65

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

interface IFoo
{
   string Message {get;}
}
...
IFoo obj = new IFoo("abc");
Console.WriteLine(obj.Message);
Run Code Online (Sandbox Code Playgroud)

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

看看你能否找出原因,那么:谁说你无法实例化界面?


Jon*_*len 56

什么时候布尔既不是True也不是False?

Bill发现你可以破解一个布尔值,这样如果A为True且B为True,则(A和B)为False.

黑客布尔

  • 当它是FILE_NOT_FOUND时,当然! (134认同)
  • 总有一天我会编写一个依赖于这种行为的程序,而最黑暗的地狱恶魔会为我做好准备.Bwahahahahaha! (20认同)
  • 此示例使用按位,而不是逻辑运算符.这有多令人惊讶? (18认同)
  • 这很有趣,因为从数学上讲,这意味着C#中的任何语句都是可证明的.糟糕! (12认同)
  • 好吧,他破解了结构的布局,当然你会得到奇怪的结果,这不是那么令人惊讶或意外! (6认同)

Ben*_*jol 47

我迟到了一下参加聚会,但我有3 4五:

  1. 如果你在一个尚未加载/显示的控件上轮询InvokeRequired,它会说是假的 - 如果你试图从另一个线程更改它,那么你的脸就会爆炸(解决方案是引用它.在创建者中引用它.控制).

  2. 另一个绊倒我的是给定一个装配:

    enum MyEnum
    {
        Red,
        Blue,
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

    enum MyEnum
    {
        Black,
        Red,
        Blue,
    }
    
    Run Code Online (Sandbox Code Playgroud)

    在运行时,你会得到"黑色".

  3. 我有一个带有一些方便常量的共享程序集.我的前任留下了一堆丑陋的get-only属性,我以为我会摆脱杂乱而只是使用公共const.当VS将它们编译为它们的值而不是引用时,我有点惊讶.

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

  5. 字典<,>:"未定义项目的返回顺序".这太可怕了,因为它有时可能会咬你,但是工作别人,如果你只是盲目地认为词典会发挥得很好("为什么不应该呢?我想,List会这样做"),你真的需要在你最终开始质疑你的假设之前,请先了解它.

  • 我不同意#5是一个"边缘案例".根据插入值的时间,词典不应具有已定义的顺序.如果需要定义的顺序,请使用List,或使用可以对您有用的方式排序的键,或使用完全不同的数据结构. (53认同)
  • @Wedge,也许和SortedDictionary一样? (21认同)
  • #2是一个有趣的例子.枚举是编译器映射到整数值.因此,即使您没有明确地为它们分配值,编译器也会这样做,导致MyEnum.Red = 0和MyEnum.Blue = 1.当您添加Black时,您重新定义了值0以从红色映射到黑色.我怀疑这个问题也会出现在其他用法中,比如序列化. (6认同)
  • #3的发生是因为常量作为文字被插入到它们所使用的任何地方(至少在C#中).您的前任可能已经注意到了它,这就是他们使用get-only属性的原因.但是,readonly变量(与const相反)也可以正常工作. (4认同)
  • 要求调用+1.在我们的'我们更喜欢明确地将值分配给枚举,如红色= 1,蓝色= 2,因此可以在它之前或之后插入新的值,它总是会产生相同的值.如果要将值保存到数据库,则特别需要它. (3认同)

Hei*_*nzi 33

VB.NET,nullables和三元运算符:

Dim i As Integer? = If(True, Nothing, 5)
Run Code Online (Sandbox Code Playgroud)

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

我真的包含什么?0.

这是令人惊讶但实际上是"正确"的行为:Nothing在VB.NET中与nullCLR 不完全相同:Nothing可以表示null或者default(T)表示值类型T,具体取决于上下文.在上述情况下,If推断Integer为普通型的Nothing5,所以,在这种情况下,Nothing装置0.


Jos*_*hua 28

我找到了第二个非常奇怪的角落案例,远远超过了我的第一个案例.

String.Equals方法(String,String,StringComparison)实际上不是副作用.

我正在研究一个代码块,它在一些函数的顶部单独使用它:

stringvariable1.Equals(stringvariable2, StringComparison.InvariantCultureIgnoreCase);
Run Code Online (Sandbox Code Playgroud)

删除该行会导致程序中其他位置的堆栈溢出.

该代码原来是为了本质上是一个BeforeAssemblyLoad事件而安装一个处理程序并试图这样做

if (assemblyfilename.EndsWith("someparticular.dll", StringComparison.InvariantCultureIgnoreCase))
{
    assemblyfilename = "someparticular_modified.dll";
}
Run Code Online (Sandbox Code Playgroud)

到现在为止我不应该告诉你.在字符串比较中使用之前未使用过的文化会导致程序集加载.InvariantCulture也不例外.

  • 哇.这是维护者腿部的完美镜头.我想编写一个BeforeAssemblyLoad处理程序可能会导致很多这样的意外. (2认同)

cbp*_*cbp 20

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

以下单元测试演示了该问题.

看看你是否能解决出了什么问题.

    [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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

  • 看起来像编译器错误; `+ = 500`调用:`ldc.i4 500`(将500作为Int32推送),然后`调用valuetype Program/MyStruct Program/MyStruct :: op_Addition(valuetype Program/MyStruct,valuetype [mscorlib] System.Decimal)` - 因此它将其视为"十进制"(96位)而不进行任何转换.如果你使用`+ = 500M`它就是正确的.它只是看起来像编译器*认为*它可以单向执行(可能是由于隐式int运算符)然后决定以另一种方式执行它. (10认同)
  • 我几个月前写过这篇文章,但我记不起为什么会这样. (2认同)
  • @Ben得到编译器错误或修改不影响原始结构是没问题的.访问违规是一个完全不同的野兽.如果您只是编写安全的纯托管代码,运行时应该永远不会抛出它. (2认同)

Pet*_*den 18

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" };
Run Code Online (Sandbox Code Playgroud)

请注意,这不起作用:

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[]'
Run Code Online (Sandbox Code Playgroud)

  • IList <T>示例只是一个强制转换,因为string []已经实现了ICloneable,IList,ICollection,IEnumerable,IList <string>,ICollection <string>和IEnumerable <string>. (11认同)

Sam*_*ell 15

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

public class DummyObject
{
    public override string ToString()
    {
        return null;
    }
}
Run Code Online (Sandbox Code Playgroud)

使用如下:

DummyObject obj = new DummyObject();
Console.WriteLine("The text: " + obj.GetType() + " is " + obj);
Run Code Online (Sandbox Code Playgroud)

会抛出一个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;
Run Code Online (Sandbox Code Playgroud)

这是ECMA-334§14.7.4的错误:

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

  • 嗯,但我可以想象这个错误,因为`.ToString`真的应该永远不会返回null,而是string.Empty.然而,框架中的错误. (3认同)

Gre*_*ech 12

有意思 - 当我第一次看到它时,我认为它是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);
Run Code Online (Sandbox Code Playgroud)

它也等同于true你检查string.Empty哪个意味着这个操作码必须具有实习空字符串的特殊行为.

  • 你不聪明; 你错过了这一点 - 我想为这一个案例生成特定的IL.无论如何,鉴于Reflection.Emit对于这种类型的场景来说是微不足道的,它可能和在C#中编写程序然后打开反射器,找到二进制文件,找到方法等一样快......我甚至不需要离开IDE去做. (3认同)

Jos*_*hua 10

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
Run Code Online (Sandbox Code Playgroud)

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


小智 10

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 !!!
Run Code Online (Sandbox Code Playgroud)

这里有完整描述


tcl*_*lem 10

如果您的泛型类具有可能根据类型参数而变得模糊的方法,该怎么办?我最近遇到这种情况写了一本双向字典.我想写对称Get()方法,它会返回与传递的参数相反的方法.像这样的东西:

class TwoWayRelationship<T1, T2>
{
    public T2 Get(T1 key) { /* ... */ }
    public T1 Get(T2 key) { /* ... */ }
}
Run Code Online (Sandbox Code Playgroud)

如果你让这样的情况:一切都很好很好T1,并T2有不同的类型:

var r1 = new TwoWayRelationship<int, string>();
r1.Get(1);
r1.Get("a");
Run Code Online (Sandbox Code Playgroud)

但是如果T1并且T2是相同的(并且可能如果一个是另一个的子类),则是编译器错误:

var r2 = new TwoWayRelationship<int, int>();
r2.Get(1);  // "The call is ambiguous..."
Run Code Online (Sandbox Code Playgroud)

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


Ome*_*Mor 10

C#辅助功能Puzzler


以下派生类从其基类访问私有字段,编译器静默查找另一端:

public class Derived : Base
{
    public int BrokenAccess()
    {
        return base.m_basePrivateField;
    }
}
Run Code Online (Sandbox Code Playgroud)

该领域确实是私人的:

private int m_basePrivateField = 0;
Run Code Online (Sandbox Code Playgroud)

关心我们如何编译这样的代码?

.

.

.

.

.

.

.

回答


诀窍是声明Derived为内部类Base:

public class Base
{
    private int m_basePrivateField = 0;

    public class Derived : Base
    {
        public int BrokenAccess()
        {
            return base.m_basePrivateField;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

内部类可以完全访问外部类成员.在这种情况下,内部类也恰好从外部类派生.这使我们能够"打破"私人成员的封装.

  • 我不明白为什么 - ? (4认同)
  • 看起来你有一个内部类继承其所有者的非常强大的堆栈溢出的可能性...... (2认同)

TDa*_*ver 10

刚刚发现一件好事:

public class Base
{
   public virtual void Initialize(dynamic stuff) { 
   //...
   }
}
public class Derived:Base
{
   public override void Initialize(dynamic stuff) {
   base.Initialize(stuff);
   //...
   }
}
Run Code Online (Sandbox Code Playgroud)

这会引发编译错误.

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

如果我写base.Initialize(东西作为对象); 它完美地工作,但这似乎是一个"神奇的词",因为它完全相同,一切仍然被收到动态......


Tor*_*var 8

在我们使用的API中,返回域对象的方法可能会返回一个特殊的"null对象".在执行这个,比较操作符和Equals()方法被覆盖,返回true,如果它与比较null.

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

return test != null ? test : GetDefault();
Run Code Online (Sandbox Code Playgroud)

或许更冗长,像这样:

if (test == null)
    return GetDefault();
return test;
Run Code Online (Sandbox Code Playgroud)

where GetDefault()是一个返回我们想要使用的默认值的方法而不是null.当我使用ReSharper并遵循它的建议将其中任何一个重写为以下内容时,令我惊讶的是:

return test ?? GetDefault();
Run Code Online (Sandbox Code Playgroud)

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


Jor*_*dão 8

考虑这个奇怪的情况:

public interface MyInterface {
  void Method();
}
public class Base {
  public void Method() { }
}
public class Derived : Base, MyInterface { }
Run Code Online (Sandbox Code Playgroud)

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

如果Base并且Derived在不同的程序集中,在编译Derived程序集时,编译器将不会更改其他程序集,因此它将引入一个成员,Derived这将是一个显式实现MyInterface::Method,只需委托调用Base::Method.

编译器必须这样做才能支持关于接口的多态调度,即它必须使该方法成为虚拟的.


Dyn*_*ard 7

以下可能是我只是缺乏的一般知识,但是呃.前段时间,我们有一个包含虚拟属性的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 { } }
}
Run Code Online (Sandbox Code Playgroud)

Derived对象上下文中,您可以在添加base.Property为手表或键入快速手表时获得相同的行为base.Property.

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

((TestProject1.Base)(d))
Run Code Online (Sandbox Code Playgroud)

这意味着如果基地被替换,那么呼叫将是

public string BaseProperty { get { return ((TestProject1.Base)(d)).Property; } private set { } }
Run Code Online (Sandbox Code Playgroud)

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

无论如何,这显然不会在功能方面改变任何东西,Derived.BaseProperty仍然会真正返回"BASE_AWESOME",因此这不是我们工作中的bug的根源,只是一个令人困惑的组件.然而,我确实发现有趣的是它会误导开发人员,他们在调试会话期间不会意识到这一点,特别Base是如果没有在你的项目中公开,而是被引用为第三方DLL,导致Devs只是说:

"Oi,等等......什么?omg那个DLL就像是,......做一些有趣的事情"


Ste*_*eve 7

这个很难达到顶峰.当我尝试构建一个真正支持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.
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

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) 
Run Code Online (Sandbox Code Playgroud)


Spe*_*ort 6

我不确定你是否说这是一个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."
Run Code Online (Sandbox Code Playgroud)

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

  • 这确实是一个远景(不是7,afaik)安全增强.但很酷的是,您可以使用程序文件路径读取和打开文件,而如果您使用资源管理器查看该文件则没有任何内容.在我终于找到它之前,这个给了我近一天的工作@顾客. (2认同)

Jor*_*dão 6

你有没有想过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));
  }
}
Run Code Online (Sandbox Code Playgroud)

我不知道它在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]
Run Code Online (Sandbox Code Playgroud)