在這篇文中紀錄如何擷取中英混雜的子字串。
以下是我想到的作法,就是利用big5中文兩字元、英文一字元的特性去做。
20220217,使用Linq重構部分程式碼。
1 | using System; |
專案存庫:https://github.com/yuhsiang237/csharp-notes/tree/master/SubEnglishChineseString
在這篇文中紀錄如何擷取中英混雜的子字串。
以下是我想到的作法,就是利用big5中文兩字元、英文一字元的特性去做。
20220217,使用Linq重構部分程式碼。
1 | using System; |
專案存庫:https://github.com/yuhsiang237/csharp-notes/tree/master/SubEnglishChineseString
撰寫中…
在這回中主要介紹C#的單元測試。
將會以AspNetCore中的xUnit、Moq套件進行範例測試。
至於為什麼要單元測試?
因為能夠快速的驗證結果、在撰寫時也只要執行片段程式碼。
對於有些程式碼容易有改A壞B的問題,能夠透過測試直接幫我們一次測完。
讓我們只需要針對錯誤案例進行程式碼錯誤區塊修改,加快整體專案除錯的時間。
完整測試專案連結:
https://github.com/yuhsiang237/csharp-unit-test
使用Moq套件的Mock建立假的物件,並且設定函式、屬性、返回的值,再使用xUnit提供的驗證方法進行測試。
首先建立我們要測試的介面與類別,一個是IFoo.cs、Bar.cs。
csharp-unit-test/UnitTestProject/Bar.cs
1 | using System; |
csharp-unit-test/UnitTestProject/IFoo.cs
1 | using System; |
再來建立測試,使用Moq、Xunit。
我們可以對函式返回值、假物件、屬性進行測試。
以下為測試範例:
/csharp-unit-test/UnitTestProjectTests/UnitTest.cs
1 | using System; |
再來開啟VisualStudio的測試視窗運行,可以得到以下結果:
表示測試成功。
撰寫測試能夠改善平常除錯的速度,特別當專案大起來時。
此外有些平台有測試的功能(如Azure DevOps Server的TFS),可以在CI自動整合時跑測試。
撰寫中…
如果在C#中遇到一些重複的String字串變數,是由變數的命名而來,就可以使用nameof直接提取出自串,避免再打一次造成打錯的問題。
nameof運算式會產生變數、類型或成員的名稱做為字串常數
1 | Console.WriteLine(nameof(System.Collections.Generic)); // output: Generic |
https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/operators/nameof#code-try-0
在這篇中主要筆記LINQ 延遲執行的特性。
一般我們如果使用純SQL語言去與資料庫查詢通常一下指令就會被馬上執行,而LINQ卻不一樣,是直到某個觸發點他才會開始運作。
即寫Select時並不會馬上運作。
1.foreach(var item in items)
2.轉為ToList(), ToArray(), ToDictionary(),因為需巡覽才能轉型,所以會直接執行。
3.取得彙總結果的聚合函数 Count(), Max(), Sum(), First(), Last()。
延遲執行的基礎 - Iterator、yield
關鍵:使用 yield 關鍵字,C# 會自動將該 block 實作為 iterator 方式執行。
1 | using System; |
實作IEnumerable介面,並且撰寫方法GetEnumerator,yield會把每個區塊視為iterator(迭代器)。
而foreach中取得每個item在逆向工程可以見到其實是呼叫GetEnumerator取得一個IEnumerator(即iterator)。
有這觀念再拉回來看LINQ的方法,可以發現是IEnumerable。
Where:
1 | public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) |
Select:
1 | public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector) |
想知道是不是延遲執行可以看返回值是否為以下3種物件:
1 | IEnumerable<TSource> |
1 | IEnumerable<IGrouping<TKey, TSource>> |
1 | IOrderedEnumerable<TSource> |
1 | using System; |
在這範例中使用泛型(Generic)的IEnumerable
1 | IEnumerable<T> |
因為Select、Where方法是IEnumerable,所以會在foreach觸發GetEnumerator時執行,進而達到延遲執行的效果。
1.可以再添加額外的表達式,如主要的查詢表達式後面可以添加子表達式過濾作為最終結果。
2.取得資料是最新的,因為當下才執行
可以得知C#的LINQ為什麼能夠延遲執行是因為採用IEnumerable介面,裡面會有yield區塊形成迭代器,所以直到foreach執行時才會開始觸發。
https://dotblogs.com.tw/hatelove/2013/09/10/csharp-linq-deferred-execution-introduction
https://dotblogs.com.tw/hatelove/2012/05/10/introducing-foreach-ienumerable-ienumerator-yield-iterator
https://dotblogs.com.tw/regionbbs/2012/01/12/linq_deferred_execution
在這篇主要介紹Properity(屬性)、Field(欄位)在物件導向class中的差別。
1.以public為主,能夠讓外部能存取。
2.包含get、set訪問器(能要求欄位只能讀或只能寫)。
3.可以增加邏輯處理,在改變一個欄位的值的時候希望改變物件的其它一些狀態,舉例:更改某外部公開屬性值x為1時,另一個內部私有欄位y需要連動更改數值為2。
4.不佔有實際內存,主要為get;set;存取私有欄位資料。
1.以private為主(有時會以protected),主要以class內部使用為主。
2.命名會加_在前面。
3.可能為靜態值所以設定為readonly
4.佔有實際內存、位址。
1 | public class User |
為什麼要使用屬性為什麼不用欄位就好?
因為能夠確保內部私有資料的安全性,並且能夠透過get;set;方式訪問時能夠增加邏輯處理、方便限制可讀、可寫。
https://kevintsengtw.blogspot.com/2011/09/property-field.html?m=1#:~:text=Field%EF%BC%9A_FullDescription%E7%82%BA%E7%89%A9%E4%BB%B6%E7%A7%81%E6%9C%89,%E4%BE%86%E5%AD%98%E6%94%BE%E5%B1%AC%E6%80%A7%E7%9A%84%E8%B3%87%E6%96%99%E3%80%82&text=%E7%AF%80%E9%8C%84%E6%95%B4%E7%90%86%EF%BC%9A,%E9%9C%80%E8%A6%81%E5%AE%8C%E5%85%A8%E7%9B%B8%E5%90%8C%E7%9A%84%E8%AA%9E%E6%B3%95%E3%80%82
https://www.itread01.com/content/1548110544.html
在這篇中簡易介紹C#同步與非同步的作法。
一般我們在做一些任務時有時需要:
1.等待上個任務完成
2.同時處理多個任務
這時就會需要用到非同步、同步的作法,可以節省時間(在同個時間內處理多個任務)。
可能用到Thead或是用Task作法async
、await
。在這篇中則以:
https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/concepts/async/
作為範例,簡易的操作一個需要處理同步/非同步的範例。
簡單來說,就是每個任務都是會等待上面的任務完成才依序執行。
下方以製作一份早餐的同步範例,1~7步驟都要等上面的步驟完成才會繼續。
1 | using System; |
結果:
可以發現這樣很難節省時間,所以其實能把某些部分拆成異步來做,有助於節省時間。(雖然同時間可能記憶體耗能會加大)
準備早餐程式碼的簡單非同步版本看起來像下列程式碼片段:
FryEggsAsync、FryBaconAsync、MakeToastWithButterAndJamAsync更新為Task,函式使用Async
。
這時會變成先做第一步,之後2、3、(4+5)步是併發執行,並使用while做判斷,依序把完成的Task移除List,仍需處理的Task數量為0就跳出,代表完成了!
再由下個任務6繼續。
1 | using System; |
結果:
可以發現為異步執行!
FryEggsAsync:原本需6秒
FryBaconAsync:原本需6秒
MakeToastWithButterAndJamAsync:原本需3秒
以同步執行的話需要:15秒
在異步執行只需取其中最大的秒數:6秒
大幅度的減省時間花費。
TaskAPI 包含兩種方法, Task.WhenAll 和 Task.WhenAny 撰寫非同步程式碼,能在多個背景工作上執行非封鎖等候。await Task.WhenAny
await Task.WhenAll
更多可參考:
https://gist.github.com/relyky/52b4abf32b44d5d00a674b9cd34ca3a3
用紅筆畫起來部分,表示其併發執行。
以Async為開頭的函數內步才能夠進行併發的操作。
並且搭配可以搭配await做Task的停頓。
即讓每個可Async的Task充分利用時間。
在工作上可能遇到需要併發、可同時執行時會使用,如有N個迴圈任務且沒有順序性,則可以寫入Task,或是需要用後端呼叫HTTP請求,可以一次併發,增加系統效率。或是有順序性執行的任務就很適合採用Task拆成子項目。
如果要再深入探討可以參考官方:
https://docs.microsoft.com/zh-tw/dotnet/csharp/async
https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/concepts/async/
https://gist.github.com/relyky/52b4abf32b44d5d00a674b9cd34ca3a3
https://docs.microsoft.com/zh-tw/dotnet/csharp/async
在這篇文章中簡易的介紹Stored Procedure。
A stored procedure is a prepared SQL code that you can save, so the code can be reused over and over again.
So if you have an SQL query that you write over and over again, save it as a stored procedure, and then just call it to execute it.
You can also pass parameters to a stored procedure, so that the stored procedure can act based on the parameter value(s) that is passed.
簡單來說就是一種預存程序,寫在資料庫裡面,可以藉由外部傳入參數去呼叫數值,而得到結果。
1.能夠讓不同應用程式直接呼叫,核心邏輯不用分散。
(但目前有微服務架構能改用API了,因此除非是特別有必要,否則還是很少碰到)
1.撰寫因為沒辦法中斷點Debug所以除錯困難。
1.建立一段searchProduct的Stored Procedure。用來查詢特定Id的商品。
1 | CREATE PROCEDURE searchProduct |
2.執行後可以在資料庫的「資料庫>Programmability>Stored Procedures」裡面找到
3.執行
1 | exec searchProduct 6 |
即可得到結果:
4.這樣就完成了一段最簡單的Stored Procedure。
但在實作上主要維護時會很花時間,如果是千行T-SQL,無法使用中斷點會像是天書那樣。
以目前寫過系統來說都是在後端程式裡面寫,而Stored Procedure算是一個新的體驗。
除非,真的有必要需求,否則還是用API式輕量化,因為比較好DEBUG。
在這裡整理一些專案常用的SQL語法。
1.新增一筆資料
1 | insert into Todos (Name) Values ('123') |
2.更新一筆資料
1 | update Todos set Name = '4123',IsComplete = 1 where Name = '123' |
3.刪除一筆資料
1 | delete from Todos where id = 17 |
4.查詢該表所有資料
1 | select * from Todos; |
5.使用查詢結果新增資料
1 | insert into Todos (Name) select Name from Todos where Name = '4123' |
6.INNER JOIN 主表跟次要表都有相同的值才撈出
1 | select * from ShipmentOrderDetails as a |
7.LEFT JOIN 主表值為主嘗試撈取次要表值,次要表有值就撈,沒值就NULL
1 | select * from ShipmentOrderDetails as a |
8.GROUP BY 取得以哪個欄位作為群,並且操作該群的集合資料
1 | select OrderId,sum(ProductPrice * ProductUnit) sum from ShipmentOrderDetails |
9.多對多資料處理(即使用left join/join的組合技)
1 | select a.Id,a.Name,c.Id as ProductCategoryId,c.Name as ProductCategoryName from Products as a |
這裡整理SQL常見用法。
因為在寫C#時其實內部會使用LINQ,所以就會對不常用的原生SQL生疏,因此在此整理在專案上常見的使用方法,有需要時就能節省再熟悉的時間。