C# 中几个小“陷阱” C# 中几个小“陷阱”      输出结果是9次。区域3里面的3次是由于调用GetStudents().ToList()方法,区域1和2则是由前面的两个foreach运行时输出的,而且每一次HashCode都不一样,说明每一个都是不同的实例。再联想一想Entity Framewor里面是不是有一个Lazy Loading,每一次使用集合中的某个对象,就会执行一次SQL,从数据库中查找该对象。 真相就在这里:IEnmerable 可以理解为只存储了集合的计算表达式,在使用的集合里面的对象时,会根据计算查找该对象。由于GetStudents()函数是用Select方法,所以每次在使用的时候都会重新的New一次,这就是上面每一个HashCode都不一样的原因。Linq中ToList()拓展方法就相当于执行IEnumerable中的计算表达式,把所有的对象都加载到集合中,这才是真正的集合。

每天写代码,偶尔就会有让你抓狂的时候:代码改了千百遍,蓦然回首,Bug就在灯火阑珊处……这里就列举一些容易犯错的几个小地方,以后遇到了其他的,再慢慢添加。

C# 中几个小“陷阱”
C# 中几个小“陷阱”

     输出结果是9次。区域3里面的3次是由于调用GetStudents().ToList()方法,区域1和2则是由前面的两个foreach运行时输出的,而且每一次HashCode都不一样,说明每一个都是不同的实例。再联想一想Entity Framewor里面是不是有一个Lazy Loading,每一次使用集合中的某个对象,就会执行一次SQL,从数据库中查找该对象。 真相就在这里:IEnmerable 可以理解为只存储了集合的计算表达式,在使用的集合里面的对象时,会根据计算查找该对象。由于GetStudents()函数是用Select方法,所以每次在使用的时候都会重新的New一次,这就是上面每一个HashCode都不一样的原因。Linq中ToList()拓展方法就相当于执行IEnumerable中的计算表达式,把所有的对象都加载到集合中,这才是真正的集合。

  1. 获取程序当前运行路径

  情景复现:WPF客户端程序,开机自启动后无法进入主界面,卡在初始屏(Splash Screen)

  处理问题:通过日志发现加载一个icon的时候,跳了一个Bug。初始代码如下:

var icon = new Icon("Images\xxx.ico");

    很简单,貌似不会有问题,相对目录且正确。直接双击程序启动完全正常,Debug启动同样完全正常,否则早就发现这个Bug了。开机自启动时日志中的错误是:找不到“C:WindowsSystem32Imagesxxx.ico”这个文件 ??? 这很让人摸不着头脑,程序中的相对目录怎么会跑到sysem32里面了?目录不对导致文件找不到,当然就进入到Exception里面了。

    第一反应是相对目录可能不带靠谱,就改成了下面的代码: 

var icon = new Icon(Directory.GetCurrentDirectory() + "\Images\xxx.ico");
//var icon = new Icon(Environment.CurrentDirectory + "\Images\xxx.ico");

    呵呵,还是不起作用,换一种写法(被注释的第二句),报的错是一样的。两个方法返回的都是“C:WindowsSystem32”这个路径,在程序开机自启动的时候。其实Environment.CurrentDirectory内部调用的也是Directory.GetCurrentDirectory()方法。

   解决方案*上面关于这个问题有个讨论,WinForm中Application.StartupPath也会有相同的问题,下面的是获取当前目录的推荐写法: 

var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

  2. IEnumerable应该正确的使用

    软件设计有个很重要的原则,就是高内聚,低耦合,于是我们经常的会面向接口编程,并且尽可能开放底层接口。所以IEnumerable就会经常用到,因为Linq操作中很多方法的返回值都是IEnumerable<T>。这一个陷阱就是关于IEnumberable,先看下面的代码,看看返回值和你想的是不是一样:

 View Code

    第一个foreach里面会输出3个true,这毫无疑问,第二个foreach里面任然会输出3个true?如果是的话,这个就不是陷阱了:-)

C# 中几个小“陷阱”
C# 中几个小“陷阱”

     输出结果是9次。区域3里面的3次是由于调用GetStudents().ToList()方法,区域1和2则是由前面的两个foreach运行时输出的,而且每一次HashCode都不一样,说明每一个都是不同的实例。再联想一想Entity Framewor里面是不是有一个Lazy Loading,每一次使用集合中的某个对象,就会执行一次SQL,从数据库中查找该对象。 真相就在这里:IEnmerable 可以理解为只存储了集合的计算表达式,在使用的集合里面的对象时,会根据计算查找该对象。由于GetStudents()函数是用Select方法,所以每次在使用的时候都会重新的New一次,这就是上面每一个HashCode都不一样的原因。Linq中ToList()拓展方法就相当于执行IEnumerable中的计算表达式,把所有的对象都加载到集合中,这才是真正的集合。

    是不是很难理解这样的结果,我也不懂:-(

    继续看下面的代码:

 View Code

    两个都会输出1 ? 

C# 中几个小“陷阱”
C# 中几个小“陷阱”

     输出结果是9次。区域3里面的3次是由于调用GetStudents().ToList()方法,区域1和2则是由前面的两个foreach运行时输出的,而且每一次HashCode都不一样,说明每一个都是不同的实例。再联想一想Entity Framewor里面是不是有一个Lazy Loading,每一次使用集合中的某个对象,就会执行一次SQL,从数据库中查找该对象。 真相就在这里:IEnmerable 可以理解为只存储了集合的计算表达式,在使用的集合里面的对象时,会根据计算查找该对象。由于GetStudents()函数是用Select方法,所以每次在使用的时候都会重新的New一次,这就是上面每一个HashCode都不一样的原因。Linq中ToList()拓展方法就相当于执行IEnumerable中的计算表达式,把所有的对象都加载到集合中,这才是真正的集合。

    有没有一种世界被颠覆的感觉……这里先不解释,我们用代码说话,继续看代码,修改一下GetStudents()方法: 

C# 中几个小“陷阱”
C# 中几个小“陷阱”

     输出结果是9次。区域3里面的3次是由于调用GetStudents().ToList()方法,区域1和2则是由前面的两个foreach运行时输出的,而且每一次HashCode都不一样,说明每一个都是不同的实例。再联想一想Entity Framewor里面是不是有一个Lazy Loading,每一次使用集合中的某个对象,就会执行一次SQL,从数据库中查找该对象。 真相就在这里:IEnmerable 可以理解为只存储了集合的计算表达式,在使用的集合里面的对象时,会根据计算查找该对象。由于GetStudents()函数是用Select方法,所以每次在使用的时候都会重新的New一次,这就是上面每一个HashCode都不一样的原因。Linq中ToList()拓展方法就相当于执行IEnumerable中的计算表达式,把所有的对象都加载到集合中,这才是真正的集合。
 1     private static IEnumerable<Student> GetStudents()
 2     {
 3       //...
 4       return GetNames().Select(s =>
 5       {
 6         var stu = new Student(s);
 7         Console.WriteLine(s + ": " + stu.GetHashCode());
 8         return stu;
 9       });
10     }
C# 中几个小“陷阱”
C# 中几个小“陷阱”

     输出结果是9次。区域3里面的3次是由于调用GetStudents().ToList()方法,区域1和2则是由前面的两个foreach运行时输出的,而且每一次HashCode都不一样,说明每一个都是不同的实例。再联想一想Entity Framewor里面是不是有一个Lazy Loading,每一次使用集合中的某个对象,就会执行一次SQL,从数据库中查找该对象。 真相就在这里:IEnmerable 可以理解为只存储了集合的计算表达式,在使用的集合里面的对象时,会根据计算查找该对象。由于GetStudents()函数是用Select方法,所以每次在使用的时候都会重新的New一次,这就是上面每一个HashCode都不一样的原因。Linq中ToList()拓展方法就相当于执行IEnumerable中的计算表达式,把所有的对象都加载到集合中,这才是真正的集合。

     在上面的代码中,GetStudent()方法被调用了2次,你觉得现在HashCode会输入几次,6次?

C# 中几个小“陷阱”
C# 中几个小“陷阱”

     输出结果是9次。区域3里面的3次是由于调用GetStudents().ToList()方法,区域1和2则是由前面的两个foreach运行时输出的,而且每一次HashCode都不一样,说明每一个都是不同的实例。再联想一想Entity Framewor里面是不是有一个Lazy Loading,每一次使用集合中的某个对象,就会执行一次SQL,从数据库中查找该对象。 真相就在这里:IEnmerable 可以理解为只存储了集合的计算表达式,在使用的集合里面的对象时,会根据计算查找该对象。由于GetStudents()函数是用Select方法,所以每次在使用的时候都会重新的New一次,这就是上面每一个HashCode都不一样的原因。Linq中ToList()拓展方法就相当于执行IEnumerable中的计算表达式,把所有的对象都加载到集合中,这才是真正的集合。

     输出结果是9次。区域3里面的3次是由于调用GetStudents().ToList()方法,区域1和2则是由前面的两个foreach运行时输出的,而且每一次HashCode都不一样,说明每一个都是不同的实例。再联想一想Entity Framewor里面是不是有一个Lazy Loading,每一次使用集合中的某个对象,就会执行一次SQL,从数据库中查找该对象。 真相就在这里:IEnmerable 可以理解为只存储了集合的计算表达式,在使用的集合里面的对象时,会根据计算查找该对象。由于GetStudents()函数是用Select方法,所以每次在使用的时候都会重新的New一次,这就是上面每一个HashCode都不一样的原因。Linq中ToList()拓展方法就相当于执行IEnumerable中的计算表达式,把所有的对象都加载到集合中,这才是真正的集合。

    所以IEnumerable应该正确的使用,作为接口中的返回值是很好的解耦方法,但是具体实现函数中的返回值最好是一个集合类,如List<T>。