CSharp - c#或者.Net最糟糕的情况是什么?

  显示原文与译文双语对照的内容

我最近使用了一个 DateTime 对象,并编写了如下内容:


DateTime dt = DateTime.Now;
dt.AddDays(1);
return dt;//still today's date! WTF?

这个智能感知文档了解 AddDays() 称将增加一天时间,这它不- 它实际上 返回一个日期有一个那一天的日期添加到它,所以你得把它写像:


DateTime dt = DateTime.Now;
dt = dt.AddDays(1);
return dt;//tomorrow's date

这个人曾经咬了我很多次,所以我认为编目最糟糕的C# 陷阱很有用。

时间:


private int myVar;
public int MyVar
{
 get { return MyVar; }
}

Blammo 。你的应用程序崩溃,没有堆栈跟踪。 这种事情时有发生

( 在getter中注意大写字母 MyVar 而不是小写 MyVar 。)

Type.GetType

我看到的咬伤大量的人的Type.GetType(string) 。 他们想知道为什么它对自己的程序集中的类型,以及 System.String 之类的类型,但不是 System.Windows.Forms.Form 。 答案是它只在当前程序集和 mscorlib 中查找。


匿名方法

C# 2.0引入了匿名方法,导致了类似这样的恶劣情况:


using System;
using System.Threading;

class Test
{
 static void Main()
 {
 for (int i=0; i <10; i++)
 {
 ThreadStart ts = delegate { Console.WriteLine(i); };
 new Thread(ts).Start();
 }
 }
}

打印出来的内容? 这完全取决于计划。 函数将打印 0 10数字,但大部分情况下应该没有输出,1.2.3 。4.5.6.7.8.9这是你的预期不同。 问题是它是被捕获的i 变量,而不是在创建委托时的值。 可以使用适当作用域的额外局部变量轻松解决这里问题:


using System;
using System.Threading;

class Test
{
 static void Main()
 {
 for (int i=0; i <10; i++)
 {
 int copy = i;
 ThreadStart ts = delegate { Console.WriteLine(copy); };
 new Thread(ts).Start();
 }
 }
}


迭代器块块的延迟延迟为

这里"man的单元测试"不通过- 为什么不通过?


using System;
using System.Collections.Generic;
using System.Diagnostics;

class Test
{
 static IEnumerable<char> CapitalLetters(string input)
 {
 if (input == null)
 {
 throw new ArgumentNullException(input);
 }
 foreach (char c in input)
 {
 yield return char.ToUpper(c);
 }
 }

 static void Main()
 {
//Test that null input is handled correctly
 try
 {
 CapitalLetters(null);
 Console.WriteLine("An exception should have been thrown!");
 }
 catch (ArgumentNullException)
 {
//Expected
 }
 }
}

答案是,内的代码的源 CapitalLetters 代码不会得到执行,直至到达 MoveNext() 迭代器方法首先被调用。

我的 brainteasers页面中还有一些其他怪异内容。

Re-throwing异常

获取大量新开发人员的一个问题,就是re-throw异常语义。

很多时候我看到像下面这样的代码


catch(Exception e) 
{
//Do stuff 
 throw e; 
}

问题在于它擦拭的堆栈跟踪并进行问题诊断困难了,导致你不能跟踪产生异常的。

正确的代码要么是不带参数的throw语句:


catch(Exception)
{
 throw;
}

或者在另一个中包装异常,并使用内部异常获取原始堆栈跟踪:


catch(Exception e) 
{
//Do stuff 
 throw new MySpecialException(e); 
}

手表窗口

如果你正在执行load-on-demand操作,这可能会严重影响你:


private MyClass _myObj;
public MyClass MyObj {
 get {
 if (_myObj == null)
 _myObj = CreateMyObj();//some other code to create my object
 return _myObj;
 }
}

现在,假设你有一些代码在别处使用这个:


//blah
//blah
MyObj.DoStuff();//Line 3
//blah

现在你想调试你的CreateMyObj() 方法。 因此,你在上面 3行放置了一个断点,目的是单步执行代码。 也只是作为额外的补充,你说 _myObj = CreateMyObj();,甚至上方的行中放置一个断点,断点放在 CreateMyObj() 本身。

代码在第 3行命中你的断点。 进入代码。 你期望输入条件代码,因为 _myObj 显然是空的? 呃。。那么。。为什么跳过了条件直接进入 return _myObj? ! 你将鼠标悬停在_myobj上。。 事实上,它有一个值 ! 怎么会这样? !

答案是IDE使它得到一个值,因为你有一个"观察器"窗口,特别是"汽车"监视窗口,它显示所有与当前或者上一行执行相关的变量/属性的值。 当你将这一切都上的断点行 3,在监视窗口决定,你将会有兴趣知道 MyObj的价值- 你在幕后,忽略你的任何断点,它离开了他统计为你 MyObj - 包括对 CreateMyObj()的调用的值,设定了值为 _myObj

这就是为什么我称之为海森堡监视窗口- 你不能在不影响它的情况下观察值。 : )


编辑评论 - 我觉得 @ChristianHayter's 评论应该包含在主要答案中,因为它看起来是这个问题的有效解决方法。 所以只要有lazy-loaded属性。。

用 [DebuggerBrowsable(DebuggerBrowsableState.Never)] 或者 [DebuggerDisplay("")] 装饰你的属性。 - 基督教 Hayter

这是另一个让我知道的时间:


static void PrintHowLong(DateTime a, DateTime b)
{
 TimeSpan span = a - b;
 Console.WriteLine(span.Seconds);//WRONG!
 Console.WriteLine(span.TotalSeconds);//RIGHT!
}


TimeSpan.Seconds 是 timespan ( 2分钟和 0秒的秒数为 0 )的秒部分。

TimeSpan.TotalSeconds 是以秒为单位测量的整个 timespan 。

由于没有un-hook事件而泄漏内存。

这甚至吸引了一些我认识的高级开发者。

想象一个带有很多东西的WPF表单,在那里你可以订阅一个事件。 如果你不取消订阅,整个表单将在关闭后保存在内存中,并且 de-referenced 。

我认为我看到的问题是在WPF表单中创建一个 DispatchTimer,并订阅了Tick事件,如果你的表单没有在计时器上执行 -= !

在本例中,你的反汇编代码应该有


timer.Tick -= TimerTickEventHandler;

这一点特别棘手,因为你在WPF表单中创建了DispatchTimer的实例,所以你会认为它是由垃圾收集进程处理的内部引用。。 不幸的是,DispatchTimer在用户界面线程上使用了一个静态的内部 List 订阅和服务请求,所以这个引用是静态类的'拥有'。

也许不是真正的问题,因为该行为在MSDN中写得很清楚,但我的脖子曾经被破坏过,因为我发现它相当 counter-intuitive:


Image image = System.Drawing.Image.FromFile("nice.pic");

这个人离开 "nice.pic" 文件,直到图像被释放。 在我面对它的时候,我虽然很好地加载了图标,但没有意识到( 最初) 是由许多打开和锁定的文件组成的 ! 图像跟踪它从哪里加载文件。。

怎么解决这个问题我以为一个衬垫会做这个工作。 我期望 FromFile() 有一个额外的参数,但没有,所以我写了这个。。


using (Stream fs = new FileStream("nice.pic", FileMode.Open, FileAccess.Read))
{
 image = System.Drawing.Image.FromStream(fs);
}

重载的==运算符和非类型化容器( arraylists,数据集,等等 ):


string my ="my"
Debug.Assert(my+"string" =="my string");//true

var a = new ArrayList();
a.Add(my+"string");
a.Add("my string");

//uses ==(object) instead of ==(string)
Debug.Assert(a[1] =="my string");//true, due to interning magic
Debug.Assert(a[0] =="my string");//false

解决方案?

  • 在比较字符串类型时总是使用 string.Equals(a, b)

  • 使用像 List<string> 这样的泛型确保两个操作数都是字符串。


[Serializable]
class Hello
{
 readonly object accountsLock = new object();
}

//Do stuff to deserialize Hello with BinaryFormatter
//and now... accountsLock == null ;)

故事的寓意:在反序列化对象时不运行字段 initialisers

...