我收集了一些极端案例和脑筋急转弯,总是想听到更多。这个页面只涵盖了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上尝试过……)
只是为了澄清,这只是我正在寻找的事情的一个例子-我并不是特别寻找对这个奇怪现象的讨论/解释。(这和普通的弦乐实习不一样;特别地,当调用构造函数时,字符串实习通常不会发生。)我真的是在要求类似的奇怪行为。
还有其他的宝藏吗?
我来派对晚了一点,但我有三四五
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.
有趣的是,当我第一次看到它时,我认为这是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。这意味着这个操作代码必须有特殊的行为来实习空字符串。
以下可能是我缺乏的常识,但是,嗯。前段时间,我们遇到了一个包含虚拟财产的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是
, . .做一些有趣的事情”
几年前,在制定忠诚度计划时,我们遇到了一个关于给予客户积分数量的问题。这个问题与将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);