在C#或.NET中,最奇怪的情况是什么?[关闭]


322

我收集了一些特殊情况和脑筋急转弯,并且总是希望听到更多。该页面仅真正涵盖了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上尝试过...)

需要明确的是,这只是我正在寻找的一种示例-我并不是特别希望对此奇怪的话题进行讨论/解释。(这与普通的字符串实习不同;特别是,在调用构造函数时,通常不会发生字符串实习。)我真的在要求类似的奇怪行为。

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


64
在Mono 2.0 rc上测试;返回True
Marc Gravell

10
两个字符串最终被的String.Empty,看来,该框架只保留一个参考到
阿德里安Zanescu

34
这是一个保护内存的事情。在MSDN文档中查找静态方法string.Intern。CLR维护一个字符串池。这就是为什么具有相同内容的字符串会显示为对相同内存(即对象)的引用。
约翰·莱德格伦

12
@John:字符串嵌入仅针对文字自动发生。事实并非如此。@DanielSwe:不需要使字符串不变就可以进行实习。可能的事实是不变性的一个很好的推论,但无论如何这里都不会发生常规的实习。
乔恩·斯基特

3
导致此行为的实现细节在此处进行了说明:blog.liranchen.com/2010/08/brain-teasing-with-strings.html
Liran

Answers:


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

那是什么...

答案:任意Nullable<T>-如int?。除了GetType()之外,所有方法都将被覆盖;因此将其强制转换(装箱)到对象(并因此转换为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()调用将重定向到proxy(MyFunnyProxyAttribute),该代理返回null。现在去洗眼睛!


9
为什么不能定义Nullable <T> .GetType()?结果不应该是typeof(Nullable <T>)吗?
Drew Noakes

69
德鲁:问题在于GetType()不是虚拟的,因此它没有被覆盖-这意味着该值被装在方法调用中。该框成为空引用,因此为NRE。
乔恩·斯基特

10
@提请; 此外,还有针对Nullable <T>的特殊装箱规则,这意味着空Nullable <T>框为null,而不是包含空Nullable <T>的框(以及对空Nullable <T的null取消框) >)
马克·格雷韦尔

29
非常非常酷 以一种很酷的方式。;-)
康拉德·鲁道夫

6
构造函数约束,在C#3.0语言规范中为10.1.5
Marc Gravell

216

银行家四舍五入。

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

.Net框架采用称为银行家舍入的方案或舍入。

在“银行家四舍五入”中,将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也是如此。


22
我也觉得很奇怪。至少,直到我整理出一大堆数字并计算出它们的和为止。然后,您意识到,如果简单地四舍五入,最终将与非四舍五入的数字之和产生巨大的差异。如果您要进行财务计算,那就太糟糕了!
茨维托米尔·特松涅夫

255
如果人们不知道,则可以执行以下操作:Math.Round(x,MidpointRounding.AwayFromZero); 更改舍入方案。
ICR

26
来自文档:此方法的行为遵循IEEE标准754,第4节。这种舍入有时称为“舍入到最接近的舍入”或“银行家的舍入”。它将由于在单个方向上一致地舍入中点值而导致的舍入误差最小化。
ICR

8
我想知道这是否就是为什么int(fVal + 0.5)即使在具有内置舍入功能的语言中也经常看到的原因。
本·布兰克

32
讽刺的是,我在一家银行一次,其他程序员开始翻转了这事,认为圆是在框架破碎

176

如果被调用为该函数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位计算机来验证这一点,但是该方法确实满足了尾部呼叫优化的所有条件。如果有人确实有一个,我很想看看它是否是真的。


10
必须在发布模式下进行编译,但绝对可以在x64上运行=)
Neil Williams

3
VS 2010面世时,可能值得更新您的答案,因为所有当前的JIT都将以发布模式进行TCO
ShuggyCoUk 2009年

3
刚刚在32位WinXP上的VS2010 Beta 1上尝试过。仍然会得到一个StackOverflowException。
squillman

130
为StackOverflowException +1
calvinlough 2010年

7
++完全把我赶走了。你不能Rec(i + 1)像普通人一样打电话吗?
configurator

111

分配这个!


我想在聚会上问这个问题(这可能就是为什么我不再被邀请的原因):

您可以编译以下代码吗?

    public void Foo()
    {
        this = new Teaser();
    }

一个简单的作弊可能是:

string cheat = @"
    public void Foo()
    {
        this = new Teaser();
    }
";

但是真正的解决方案是这样的:

public struct Teaser
{
    public void Foo()
    {
        this = new Teaser();
    }
}

因此,很少有人知道值类型(结构)可以重新分配其this变量。


3
C ++类也可以做到这一点...正如我最近发现的那样,只是因为实际尝试使用它来进行优化而大吼大叫:p
mpen

1
我实际上是使用就地新的。只是想要一种有效的方式来更新所有字段:)
mpen 2010年

70
这也是一种作弊://this = new Teaser();:-)
AndrewJacksonZA 2010年

17
:-)我更喜欢我的生产代码中的作弊手段,而不是这种
Omer Mor

2
从CLR通过C#:之所以这样做,是因为您可以在另一个构造函数中调用结构的无参数构造函数。如果您只想初始化一个结构的值,而又想将另一个值设为零/空(默认值),则可以编写public Foo(int bar){this = new Foo(); specialVar = bar;}。这效率不高,也不合理(specialVar被分配了两次),而仅供参考。(这就是书中给出的原因,我不知道为什么我们不应该这样做public Foo(int bar) : this()
kizzx2 2011年

100

几年前,在开展忠诚度计划时,我们对提供给客户的积分数量存在疑问。问题与将double转换/转换为int有关。

在下面的代码中:

double d = 13.6;

int i1 = Convert.ToInt32(d);
int i2 = (int)d;

i1 == i2吗?

事实证明,i1!= i2。由于转换和强制转换运算符中的舍入策略不同,因此实际值是:

i1 == 14
i2 == 13

最好总是调用Math.Ceiling()或Math.Floor()(或带有满足我们要求的MidpointRounding的Math.Round)

int i1 = Convert.ToInt32( Math.Ceiling(d) );
int i2 = (int) Math.Ceiling(d);

44
强制转换为整数不会舍入,只会将其舍去(实际上总是舍入)。因此,这完全有道理。
Max Schmeling

57
@Max:是的,但是转换为什么要舍入?
Stefan Steinegger,2009年

18
@Stefan Steinegger如果所有的工作都是铸造的,那么就没有理由了吗?还要注意,类名是Convert not Cast。
bug-a-lot 2009年

3
在VB中:CInt()四舍五入。Fix()会截断。曾经把我烧死blog.wassupy.com/2006/01/i-can-believe-it-not-truncating.html)–
迈克尔·哈伦

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

18
哇,这对我来说是新的。还不知道ConverTo.ToIn32()是如何工作的,但是强制转换为(int)0却没有。而其他任何大于0的数字都适用。(通过“工作”,我的意思是称对象超载。)
卢卡斯(Lucas

有一个推荐的代码分析规则可以强制执行关于此行为的良好实践: msdn.microsoft.com/zh-cn/library/ms182149%28VS.80%29.aspx 该链接还包含有关0映射如何工作的详细说明。
克里斯·克拉克

1
@克里斯·克拉克:我尝试将None = 0放在枚举Symbol上。仍然编译器选择0甚至(int)0的枚举
Michael Buen

2
IMO他们应该引入一个none可以转换为任何枚举的关键字,并将0始终设置为int且不能隐式转换为枚举。
CodesInChaos

5
之所以会使用ConverTo.ToIn32()是因为它的结果不是编译时常量。而且只有编译时常量0可以转换为枚举。在.net的早期版本中,即使只有文字也0应该可以转换为枚举。参见Eric Lippert的博客:blogs.msdn.com/b/ericlippert/archive/2006/03/28/563282.aspx
CodesInChaos 2010年

67

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

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

它可以让您声明它,但没有实际用途,因为它总是要求您用另一个Turtle将您在中心填充的任何类包装起来。

[笑话]我猜是乌龟一直到... [/笑话]


34
不过,您可以创建实例:class RealTurtle : Turtle<RealTurtle> { } RealTurtle t = new RealTurtle();
Marc Gravell

24
确实。这是Java枚举有效使用的模式。我也在协议缓冲区中使用它。
乔恩·斯基特

6
RCIX,哦,是的。
约书亚

8
我在花式泛型的东西中使用了很多这种模式。它允许诸如正确键入克隆或创建自身实例之类的事情。
Lucero

20
这是“好奇地重复使用的模板模式”,en.wikipedia.org
wiki / Curiously_recurring_template_pattern

65

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

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

上述乍看上去疯狂的,但实际上是合法的。没有,真的(虽然我已经错过了一个关键组成部分,但它不是什么哈克像“添加一个名为类IFoo”或“添加using别名点IFoo在类”)。

看看您是否能找出原因,然后:谁说您无法实例化接口?


1
+1表示“使用别名”-我不知道您能做到这一点
David

破解COM Interop的编译器:-)
Ion Todirel 2011年

你这个混蛋!您至少可以说“在某些情况下” ...我的编译器不赞成!
马·哈宁

56

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

Bill发现您可以破解一个布尔值,以便如果A为True且B为True,则(A和B)为False。

被黑的布尔值


134
当然是FILE_NOT_FOUND!
格雷格,

12
这很有趣,因为从数学上讲,这意味着C#中没有可证明的语句。哎呀
西蒙·约翰逊

20
有一天,我将编写一个依赖于这种行为的程序,最黑暗的地狱的恶魔会为我准备欢迎。哇哈哈哈哈哈!
Jeffrey L Whitledge,09年

18
本示例使用按位而非逻辑运算符。这有多令人惊讶?
Josh Lee,2009年

6
好吧,他破解了结构的布局,当然您会得到怪异的结果,这并不令人惊讶或意外!
离子托迪雷尔

47

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

  1. 如果您在尚未加载/显示的控件上轮询InvokeRequired,它将显示为false-如果尝试从另一个线程更改它,则会在您的面前大怒(解决方案是引用此控件。控制)。

  2. 另一个令我震惊的是,给了一个装配:

    enum MyEnum
    {
        Red,
        Blue,
    }

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

    enum MyEnum
    {
        Black,
        Red,
        Blue,
    }

    在运行时,您将获得“黑色”。

  3. 我有一个共享的程序集,其中包含一些方便的常量。我的前任留下了许多难看的get-only属性,我想我会摆脱混乱的局面,只使用public const。当VS将它们编译为它们的值而不是引用时,我感到有些惊讶。

  4. 如果您从另一个程序集实现接口的新方法,但是重新引用该程序集的旧版本,则即使您已经实现了TypeLoadException(也没有实现'NewMethod'),即使您已经实现了它(请参见此处)。

  5. 字典<,>:“未归还项目的顺序”。这太可怕了,因为它有时可能会咬你,但会打扰别人,并且如果您只是盲目地认为Dictionary会表现出色(“为什么不?我想,List可以”),您真的必须在最终开始质疑自己的假设之前,请先倾听自己的意见。


6
#2是一个有趣的示例。枚举是编译器到整数值的映射。因此,即使没有显式分配它们的值,编译器也进行了分配,导致MyEnum.Red = 0和MyEnum.Blue =1。添加Black时,您重新定义了值0,以从Red映射到Black。我怀疑该问题也会在其他用法中体现出来,例如序列化。
LBushkin

3
+1需要调用。在我们的实例中,我们更愿意为Red = 1,Blue = 2之类的枚举显式分配值,以便可以在始终产生相同值之前或之后插入新值。如果要将值保存到数据库,则特别有必要。
TheVillageIdiot

53
我不同意#5是“边缘案例”。字典不应根据您插入值的时间来定义顺序。如果需要定义的顺序,请使用列表或使用可以对您有用的方式进行排序的键,或者使用完全不同的数据结构。

21
@Wedge,也许像SortedDictionary?
2009年

4
#3之所以发生,是因为常量在使用的所有地方都作为常量插入(至少在C#中)。您的前任可能已经注意到了这一点,这就是为什么他们使用get-only属性。但是,只读变量(而不是const)也可以工作。
2009年

33

VB.NET,可为空值和三元运算符:

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

由于我希望i包含,因此花了一些时间进行调试Nothing

我真正包含什么?0

这是令人惊讶的,但实际上是“正确”的行为:Nothing在VB.NET是不完全一样null的CLR:Nothing既可以平均nulldefault(T)为值类型T,根据上下文。在上述情况下,If推断Integer为普通型的Nothing5,所以,在这种情况下,Nothing装置0


足够有趣的是,我找不到这个答案,所以不得不提出一个问题。好吧,谁知道答案在这个线程中?
Gserg 2011年

28

我发现了第二个非常奇怪的角落案例,远胜于我的第一个案例。

String.Equals方法(String,String,StringComparison)实际上并非没有副作用。

我正在编写一个代码块,该代码块本身在某些函数的顶部位于一行:

stringvariable1.Equals(stringvariable2, StringComparison.InvariantCultureIgnoreCase);

删除该行会导致程序中其他地方的堆栈溢出。

原来的代码是为本质上是BeforeAssemblyLoad事件安装一个处理程序,然后尝试执行

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

到现在为止,我不必告诉你。使用字符串比较之前未使用的区域性会导致程序集负载。InvariantCulture也不例外。


我猜“加载程序集”是一个副作用,因为您可以使用BeforeAssemblyLoad观察它!
Jacob Krall

2
哇。这是维护人员的最佳选择。我猜写一个BeforeAssemblyLoad处理程序可能会导致很多这样的意外。
wigy 2011年

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

我的头很痛...为什么不起作用?
杰森2009年

2
嗯,我是几个月前写的,但是我不记得为什么会这样。
cbp

10
看起来像一个编译器错误;的+= 500呼叫:ldc.i4 500(500推压以Int32),然后call valuetype Program/MyStruct Program/MyStruct::op_Addition(valuetype Program/MyStruct, valuetype [mscorlib]System.Decimal)-因此它然后将其视为一个decimal(96位)而无需任何转换。如果您使用+= 500M它会正确。看起来好像编译器认为它可以用一种方式来实现(大概是由于隐式int运算符),然后决定以另一种方式来实现。
马克·格雷夫

1
很抱歉出现双重讯息,这是更详细的说明。我将添加此内容,尽管对此我理解为什么,但我对此一直感到困惑。对我来说,这是对struct / valuetype的不幸限制。 bytes.com/topic/net/answers/...
贝内特萝

2
@Ben得到编译器错误或不影响原始结构的修改就可以了。访问冲突是完全不同的野兽。如果您只是在编写安全的纯托管代码,则运行时永远不要抛出它。
CodesInChaos 2010年

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

请注意,这不起作用:

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[]'

11
IList <T>示例只是强制转换,因为string []已经实现了ICloneable,IList,ICollection,IEnumerable,IList <string>,ICollection <string>和IEnumerable <string>。
卢卡斯2009年

15

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

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从type继承的虚拟方法,将任何非字符串操作数转换为其字符串表示形式object如果ToString返回null,则替换为空字符串。


3
嗯,但是我可以想象这个错误.ToString确实不会返回null,而应该返回string.Empty。尽管如此,框架中仍然存在错误。
Dykam'3

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

它也等同于true是否检查是否string.Empty意味着此操作码必须具有特殊行为才能内联空字符串。


别成为聪明的小人或其他东西,但您听说过反射器吗?在这种情况下非常方便;
RCIX

3
你不聪明 您错过了要点-我想针对这种情况生成特定的IL。而且无论如何,考虑到Reflection.Emit对于这种情况来说都是微不足道的,它可能与使用C#编写程序然后打开反射器,找到二进制文件,找到方法等一样快……而且我什至不必离开IDE去做。
格雷格·比奇

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

输出为“尝试读取受保护的内存。这表明其他内存已损坏。”


1
有趣!听起来像是编译器错误。我已经移植到C#,并且工作正常。就是说,Load中引发了很多异常问题,在有/没有调试器的情况下,它的行为都不同-您可以使用调试器,但不能(在某些情况下)。
马克·格雷韦尔

抱歉,我忘了,您需要先将组合框添加到表单中。
约书亚

这与使用SEH作为某种内部恐怖通信机制的对话框初始化有关吗?我隐约记得Win32中的类似内容。
Daniel Earwicker 2009年

1
这与上面的cbp问题相同。返回的值类型是一个副本,因此从所述拷贝而产生的任何性质的任何引用前往位斗地... bytes.com/topic/net/answers/...
贝内特迪尔

1
不。这里没有结构。我实际上调试了它。它将NULL添加到本机组合框的列表项集合中,导致延迟崩溃。
约书亚

10

PropertyInfo.SetValue()可以将整数分配给枚举,将整数分配给可空的整数,将枚举分配给可空的枚举,但不能将整数分配给可空的枚举。

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 !!!

完整说明在这里


10

如果您有一个泛型类,该类具有根据类型参数可能变得模棱两可的方法,该怎么办?最近,我在写两本字典时遇到了这种情况。我想编写对称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..."

有趣的是,第二种情况下的所有其他方法仍然可以使用。它只是对现在模棱两可的方法的调用会导致编译器错误。有趣的情况,如果不太可能且晦涩难懂。


方法重载的反对者会喜欢这个^^。
Christian Klauser

1
我不知道,这对我来说完全有意义。
Scott Whitlock

10

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

内部班级被授予对外部班级成员的完全访问权限。在这种情况下,内部类也恰好是从外部类派生的。这使我们可以“打破”私有成员的封装。


实际上有据可查;msdn.microsoft.com/zh-CN/library/ms173120%28VS.80%29.aspx。有时它可能是一个有用的功能,尤其是在外部类是静态的情况下。

是的-当然有记录。但是,很少有人解决这个难题,所以我认为这是一件很酷的琐事。
Omer Mor

2
似乎您很可能通过让内部类继承其所有者来产生堆栈溢出的可能性非常大……
Jamie Treworgy

另一个类似(完全正确)的情况是,一个对象可以访问同一类型的另一个对象的私有成员:class A { private int _i; public void foo(A other) { int res = other._i; } }
Olivier Jacot-Descombes

10

今天刚发现一件好事:

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(东西作为对象); 它可以完美地工作,但是这里似乎是一个“魔术词”,因为它的作用完全相同,所以一切仍然可以说是动态的。


8

在我们使用的API中,返回域对象的方法可能会返回特殊的“空对象”。在执行这个,比较操作符和Equals()方法覆盖,以返回true如果它相比null

因此,使用此API的用户可能会具有以下代码:

return test != null ? test : GetDefault();

或更详细些,例如:

if (test == null)
    return GetDefault();
return test;

其中GetDefault()的方法返回的是我们要使用的默认值,而不是null。当我使用ReSharper并遵循建议将其中之一重写为以下内容时,我感到惊讶:

return test ?? GetDefault();

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


1
不是真正的ac#案例,而是亲爱的上帝,谁想到了?!?
Ray Booysen 2010年

这段代码不只是使用可为空的类型吗?因此,ReSharper建议使用“ ??” 采用。正如雷所说,我不会以为这是一个极端的情况。还是我错了?
托尼2010年

1
是的,这些类型是可为空的-另外还有一个NullObject。如果是一个极端情况,我不知道,但至少是'if(a!= null)返回a;的情况。返回b;' 不同于'return a ?? b'。我绝对同意这是框架/ API设计的问题-重载== null以在对象上返回true当然不是一个好主意!
Tor Livar 2010年

8

考虑这种奇怪的情况:

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

编译器必须执行此操作以支持关于接口的多态分派,即必须使该方法成为虚拟方法。


这确实听起来很奇怪。稍后将需要进行调查:)
乔恩·斯基特

@乔恩飞碟双向:我发现这一点的同时研究实施策略在C#中的角色。希望得到您对此的反馈!
–Jordão

7

以下可能是我只是缺少的常识,但是。前一段时间,我们有一个错误案例,其中包含虚拟属性。稍微抽象上下文,考虑以下代码,并将断点应用于指定区域:

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))

这意味着如果以此方式替换base,则调用将为

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

对于“手表”,“快速观察”和调试鼠标悬停工具提示,然后使其有意义"AWESOME"而不是"BASE_AWESOME"在考虑多态时显示。我仍然不确定为什么将其转换为演员表,一个假设是,call这些模块的上下文可能无法使用,并且只能使用callvirt

无论如何,显然在功能方面没有任何改变,它Derived.BaseProperty仍然会真正返回"BASE_AWESOME",因此这不是我们工作中的错误的根源,只是一个令人困惑的组件。但是,我确实发现它很有趣,它可能会误导开发人员,使他们在调试过程中没有意识到这一事实,特别Base是如果未在您的项目中公开而是被引用为第三方DLL,那么开发人员就会说:

“哦,等等..什么?DLL就像是,..做了一些有趣的事”


没什么特别的,这只是重写工作的方式。
configurator

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

输出:

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) 

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."

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


2
这确实是一种远景(不是7,afaik)安全增强。但是很酷的事情是,您可以使用程序文件路径读取并打开文件,而如果使用资源管理器查看文件,则什么也没有。在我最终找到它之前,这花了我近一天的工作@一个客户。
亨利

绝对也是Windows 7。那是我遇到它时所使用的。我了解其背后的原因,但仍难以解决。
Spencer Ruport

在Vista / Win 7(从技术上也为winXP)中,应用程序应作为用户数据写入用户文件夹区域中的AppData文件夹。除非具有管理员权限,否则应用程序不应写入programfiles / windows / system32 / etc,这些权限仅应在其中表示升级程序/卸载程序/安装新功能。但!仍然不要写到system32 / windows / etc :)如果您以admin身份运行上述代码(右键单击>以admin身份运行),则理论上应该将其写入程序文件app文件夹。
史蒂夫·西弗


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

我不知道它在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]

适用于C#3.5编译器和C#4编译器...
Jon Skeet 2010年

在我的系统中,它不起作用。我将输出粘贴到问题中。
Jordão酒店

在.NET 3.5中,它对我失败了(没有时间测试4.0)。而且我可以用VB.NET代码来复制问题。
Mark Hurd

3

C#处理闭包的方式确实令人兴奋。

它没有将堆栈变量值复制到无闭包变量中,而是通过预处理器魔术将变量的所有出现都包装到一个对象中,从而将其移出堆栈-直接移到堆!:)

我猜想,这使得C#的语言比ML本身(使用堆栈值复制AFAIK)更加完整(或lambda完整)。F#也具有该功能,就像C#一样。

确实给我带来了很多乐趣,谢谢你们!

虽然这不是一个奇怪或极端的情况……但是基于堆栈的VM语言确实有一些意外:)


3

我不久前问了一个问题:

条件运算符不能隐式转换吗?

鉴于:

Bool aBoolValue;

在何处aBoolValue分配“真”或“假”;

以下内容将无法编译:

Byte aByteValue = aBoolValue ? 1 : 0;

但这将:

Int anIntValue = aBoolValue ? 1 : 0;

提供的答案也很好。


尽管我ve not test it I确定这将起作用:字节aByteValue = aBoolValue?(字节)1 :(字节)0; 或:字节aByteValue =(Byte)(aBoolValue?1:0);
亚历克斯·帕库拉

2
是的,Alex,那行得通。关键在于隐式转换。1 : 0单独将隐式转换为int,而不是Byte。
MPelletier

2

在c#中进行范围界定有时确实很奇怪。让我举一个例子:

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

OleDbCommand command = SQLServer.CreateCommand();

由于命令已重新声明,因此无法编译?关于为什么它在stackoverflow上的这个线程中以及在我的博客中以这种方式起作用的原因,有一些有趣的猜测。


34
我认为这不是特别奇怪。根据语言规范,您在博客中所谓的“完全正确的代码”完全不正确。这可能是你最好的一些假想的语言正确的 C#是,但语言规范是很清楚,在C#中是无效的。
乔恩·斯基特

7
好吧,它在C / C ++中有效。而且由于它是C#,所以我希望它仍然可以工作。最让我感到困扰的是,编译器没有理由这样做。嵌套范围界定并不难。我想这全都归结为最少惊奇的要素。这意味着规范可能会这样说,但如果这样做是完全不合逻辑的,那对我没有太大帮助。
Anders Rune Jensen

6
C#!= C / C ++。您是否还想使用cout <<“ Hello World!” << endl; 而不是Console.WriteLine(“ Hello World!”);?同样不是不合逻辑的,只需阅读规范即可。
Kredns

9
我说的是范围界定规则,它是语言核心的一部分。您正在谈论标准库。但是现在对我来说很清楚,我应该在开始使用c#语言进行编程之前先阅读一下它的微小规范。
Anders Rune Jensen

6
埃里克·利珀特(Eric Lippert)实际上发表了最近设计C#的原因:blogs.msdn.com/ericlippert/archive/2009/11/02/…。总结是因为更改不太可能带来意想不到的后果。
Helephant,2009年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.