zoukankan      html  css  js  c++  java
  • C# 中几个小“陷阱”

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

      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()方法。

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

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

      2. IEnumerable之LINQ 表达式

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

     1   internal class Program
     2   {
     3     private static void Main(string[] args)
     4     {
     5       //...
     6       var students = GetStudents();
     7       foreach (var student in students)
     8       {
     9         student.IsActived = true;
    10         Console.WriteLine(student.IsActived);
    11       }
    12 
    13       foreach (var student in students)
    14       {
    15         Console.WriteLine(student.IsActived);
    16       }
    17 
    18     }
    19 
    20     private static IEnumerable<string> GetNames()
    21     {
    22       //....
    23       return new[] { "AAA""BBB""CCC" };
    24     }
    25 
    26     private static IEnumerable<Student> GetStudents()
    27     {
    28       //...
    29       return GetNames().Select(s => new Student(s));
    30     }
    31 
    32     public class Student
    33     {
    34       public string Name { getset; }
    35       public bool IsActived { getset; }
    36 
    37       public Student(string name)
    38       {
    39         Name = name;
    40       }
    41     }
    42 
    43   }
    View Code

        第一个foreach里面会输出3个true,这毫无疑问,第二个foreach里面任然会输出3个true?

        如果你理解这样的结果,那么这个“陷阱”对你无效,你可以跳过这一条了……

        继续看下面的代码:

    1       var studentList = GetStudents().ToList();
    2       studentList[0].IsActived = true;
    3       var studentsActived = studentList.Where(s => s.IsActived);
    4 
    5       Console.WriteLine(studentsActived.Count());
    6 
    7       studentList[0].IsActived = false;
    8 
    9       Console.WriteLine(studentsActived.Count());
    View Code

        这次会输出什么?

        If(IsUnderstandingAgain) return; else……这里先不解释,我们用代码说话,继续看代码,修改一下GetStudents()方法:

     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     }

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

         输出结果是9次。区域3里面的3次是由于调用GetStudents().ToList()方法,区域1和2则是由前面的两个foreach运行时输出的,而且每一次HashCode都不一样,说明每一个都是不同的实例。再联想一想Entity Framewor里面是不是有一个Lazy Loading,每一次使用集合中的某个对象,就会执行一次SQL,从数据库中查找该对象。 真相就在这里,Llinq只是表达式(这里用的都是lambda写法),可以这么理解:每个表达式它会自动生成一个匿名方法,只有在需要结果的时候这个匿名方法才会去执行,这也就是为什么它的返回值是IEnmerable<T>而不是一个具体的类。 所以在需要全部所需集合时,最好先执行ToList(),ToDictionary()这类方法,生成真正的结果。
  • 相关阅读:
    Git的使用
    Ninject.Extensions.
    centos6的安装
    ASP.NET 5+EntityFramework 7
    Nancy和MVC的简单对比
    ASP.NET 5应用是如何运行的(3)
    映射层超类型
    缓存模块
    怎样的中奖算法能让人信服(转)
    JQuery Easy Ui (Tree树)详解(转)
  • 原文地址:https://www.cnblogs.com/Hans2Rose/p/CSharpTrap.html
Copyright © 2011-2022 走看看