目前工作由於領域的限制,必須要用 C# 來完成以前用 Python + Pandas 做的許多表格操作。在 C# 中,相當於 Python pandas.DataFrame 的型別是 System.Data.DataTable。
C# : System.Data.DataTable == Python : pandas.DataFrame
為了程式碼清爽精簡,我通常不喜歡寫 dataTable = dataTable.LINQ_func(),最好 dataTable.LINQ_func() 就可以對自身產生副作用。
這邊就要介紹一個以為 dataTable.LINQ_func() 可以產生副作用,結果踩到雷的例子。
先看看這段的程式碼, 你認為會輸出什麼 ?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| using System; using System.Data; using System.Linq; class Program { static void Main(string[] args) { DataTable dataTable = new DataTable(); dataTable.Columns.Add("Col_0", typeof(int)); dataTable.Columns.Add("Col_1", typeof(double));
foreach(int i in Enumerable.Range(0,10)) { dataTable.Rows.Add(new object[]{i, null}); }
dataTable.Rows.Cast<DataRow>() .Select(workRow => { workRow["Col_1"] = (int)workRow["Col_0"]/10.0; return workRow; });
foreach(DataRow row in dataTable.Rows.Cast<DataRow>()) { Console.WriteLine(String.Join(", ",row.ItemArray)); } } }
|
結果發現對 Col_0 的寫入沒有成功,沒有副作用,Select 並沒有改變 dataTable 的內容。
1 2 3 4 5 6 7 8 9 10 11 12
| > Executing task: dotnet run <
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
如果加上一個看起來無關痛癢的 .ToList()
1 2 3 4 5 6 7 8
| dataTable.Rows.Cast<DataRow>() .Select(workRow => { workRow["Col_1"] = (int)workRow["Col_0"]/10.0; return workRow; }.ToList();
|
卻改變輸出了,對 Col_0 的寫入成功了。
1 2 3 4 5 6 7 8 9 10 11 12
| > Executing task: dotnet run <
0, 0 1, 0.1 2, 0.2 3, 0.3 4, 0.4 5, 0.5 6, 0.6 7, 0.7 8, 0.8 9, 0.9
|
這是為什麼?為什麼看似沒有給 dataTable 重新賦值得 .ToList() 會有不同結果?
解釋
因為所有的 LINQ 都只是一種「預約命令」,要等到對 IEnumerabe 跑 foreach 才會執行 LINQ。如果要立刻生效,必須使用強制查詢。
- 強制查詢
- 方法1:呼叫 ToList() 或 ToArray() 可以強制查詢。
- 方法2:Count、Max、Average 和 First 這一類「彙總方法(Aggregation Method)」雖然沒有明確呼叫 foreach,但實作要 foreach 才能回傳結果,因此呼叫彙總方法也能完成強制查詢。
注意
但以下這樣是不會生效的。
1 2 3 4 5 6 7 8 9 10 11 12
| dataTable.Rows.Cast<DataRow>() .Select(workRow => { workRow["Col_1"] = (int)workRow["Col_0"]/10.0; return workRow; }); dataTable.Rows.Cast<DataRow>().ToList(); dataTable.Rows.Cast<DataRow>().Max(row=>row.ItemArray.First());
|
因為 ToList()、ToArray() 或 Aggregat Method 必須要與 LINQ 寫在同一個敘述式。
但是,這樣又會生效了
1 2 3 4 5 6 7 8 9 10
| var t = dataTable.Rows.Cast<DataRow>() .Select(workRow => { workRow["Col_1"] = (int)workRow["Col_0"]/10.0; return workRow; });
t.ToList();
|
因為 LINQ 建立的「預約命令」已經被寄託到參考 t 之上。
參考資料