C# 中幾個小“陷阱”


    每天寫代碼,偶爾就會有讓你抓狂的時候:代碼改了千百遍,驀然回首,Bug就在燈火闌珊處……這里就列舉一些容易犯錯的幾個小地方,以后遇到了其他的,再慢慢添加。

  1. 獲取程序當前運行路徑

  情景復現:WPF客戶端程序,開機自啟動后無法進入主界面,卡在初始屏(Splash Screen)

  處理問題:通過日志發現加載一個icon的時候,跳了一個Bug。初始代碼如下:

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

    很簡單,貌似不會有問題,相對目錄且正確。直接雙擊程序啟動完全正常,Debug啟動同樣完全正常,否則早就發現這個Bug了。開機自啟動時日志中的錯誤是:找不到“C:\Windows\System32\Images\xxx.ico”這個文件 ??? 這很讓人摸不着頭腦,程序中的相對目錄怎么會跑到sysem32里面了?目錄不對導致文件找不到,當然就進入到Exception里面了。

    第一反應是相對目錄可能不帶靠譜,就改成了下面的代碼:

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

    呵呵,還是不起作用,換一種寫法(被注釋的第二句),報的錯是一樣的。兩個方法返回的都是“C:\Windows\System32”這個路徑,在程序開機自啟動的時候。其實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()這類方法,生成真正的結果。

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM