# Spring Boot 和 .NET Core 差異整理 此文為Java轉C#的整理,故許多描述會以Java為主去做對映。 ### 註解 **Spring Boot** ``` @RestController @RequestMapper("/api") @RequestParam @RequestBody @GetMapping @PostMapping("path") ``` **.NET Core** ``` [ApiController] [Route("api/[controller]")] [FromQuery] [FromBody] [HttpGet] [HttpPost("path")] ``` ### Autowired **Spring Boot** ``` @Component public class A{ } @Service public class B{ // Spring boot 會根據註解去自動掃描產生Bean,在有 Autowired 的地方,將Bean注入使用 @Autowired private A a; } ``` **.NET Core** ``` // 在 Program.cs or Startup.cs builder.Service.AddScoped<InterfaceName, Impl.namespace>(); // 在需要注入的類別 // 宣告欄位 private InterfaceName _xxx; // 之後在建構子的參數直接寫入,於建構子內初始化 public ClassName(InterfaceName xxx) { _xxx = xxx; } ``` --- ### URI配置 **.NET Core** `[Route("api/[controller]")]` `[controller]` 會帶入該Controller的前面部分 例如 `WeatherForecastController` 的呼叫路徑會變成 `https://localhost:port/api/WeatherForecast` --- ### final 參考資料:(https://blog.csdn.net/kucoffee12/article/details/85096938) Java 中的 `final` 在 C# 中被拆成很散(被分得很細) 分為 * const 屬性跟區域變數都可以使用 在宣告的時候就必須給值(初始化) (猜想:文章內說屬性是靜態的,不確定是否可以非靜態。靜態的可以用類別去取得很正常,但是否可以非靜態也用const,並直接在宣告時就寫死) * readonly 只有屬性可以使用 可以在宣告的時候就給值,也可以在建構子中給值。(如果兩邊都有給,會以建構子的為主) * sealed 放在類別前面:不可被繼承 放在方法前面:不可被複寫 結論:反正 `final` 的本質就是設定之後不可被修改 --- ### 屬性設定 **Java** 1. 命名風格整理 類別(class)內有屬性(field)和方法(method),類別名稱為大駝峰 變數分為 **基本資料型態** 和 **對物件的參考** 當屬性為一般變數時,則是名詞+小駝峰,例如: `addressCity` 當屬性為常數變數(用 final 修飾時),會以全大寫,字與字之間以底線分隔,例如:`EXPIRED_TIME` 而方法通常是動詞開頭,小駝峰,視情況決定有無參數(parameter),例如: `getAddressCity()` 介面名稱為大駝峰,不加額外字,實作字尾加 `Impl` 換句話說整理 大駝峰:類別、介面、枚舉... 小駝峰: 名詞:屬性 動詞:方法 全大寫用底線分格:常數 2. 範例 ``` package xxx.xxx.xxx; import Xxx.Xxx.Xxx; class ClassName{ private String name; public String getName(){ return this.name; } public void setName(String name){ this.name = name; } } ``` **C#** 參考資料:(http://kaiching.org/pydoing/cs-guide/unit-10-class.html) 1. 命名風格整理 在物件導向中,類別(class)內有屬性(field)和方法(method)。 但由於C#內的 **屬性** 是 **property**,而 **field** 被翻譯為 **欄位** 資料型態(變數)分為 **實質型態(基本資料型態)** 和 **參考(對物件的參考)** 欄位: 名詞,小駝峰,(**_開頭** 的,是用來識別被封裝為屬性的欄位) 屬性: 名詞,大駝峰 方法為動詞開頭,大駝峰 參數為小駝峰 介面名稱為大駝峰,開頭+`I`,實作則不加額外字 換句話說整理 大駝峰:類別、介面(前面+大寫I)、枚舉、方法(後面有括號)、屬性 小駝峰: 名詞:欄位 動詞:方法 全大寫用底線分格:常數 2. 範例 ``` using Xxx.Xxx.Xxx; namespace xxx.xxx.xxx { class ClassName { private string _name; public string Name { get => _name; set { _name = value; } } } } ``` --- ### 基本型別 **Java** ``` boolean ``` **C#** ``` bool ``` --- ### foreach **Java** ``` List<String> array; for(String item : array){ } ``` **C#** ``` List<String> array; for(string item in array){ } ``` --- ### substring **Java** ``` public String substring(int beginIndex) //擷取從 beginIndex 開始 ~ endIndex 之前的字串 public String substring(int beginIndex, int endIndex) ``` **C#** ``` public string Substring(int startIndex) //擷取從 beginIndex 開始,往後數 length 長度的字串 public string Substring(int startIndex, int length) ``` --- ### 匿名類別 **Java** ``` //編譯出Animal.class class Animal{ } public class ClassName{ public static void main(String args){ Animal a = new Animal(); //編譯之後就會有 Animal$1.class (憑以前的印象,這次我沒有實際產生來看) Animal b = new Animal(){ //override }; } } ``` **C#** ``` //該匿名類別具有 kg 和 price 兩種屬性,冒號後面為屬性的初始值 var banana = (kg: 10, price: 1); ``` 簡化的類別 struct | 不同之處 | struct | class | | -------- | -------- | -------- | | 型態 | 實質型態 | 參考型態 | | 記憶體位置 | Stack | Heap | | 繼承 | 只能實作 | 繼承/實作皆可 | | NULL | not null | 可 null | ``` namespace XxxXxx { public struct ClassName { public int x, y; } public struct Point2 { public int x, y; public Point2(int p1, int p2) { x = p1; y = p2; } public void PrintCoordinate() { Console.WriteLine("({0}, {1})", x, y); } } class Program { static void Main(string[] args) { Point1 p1; p1.x = 10; p1.y = 2; Console.WriteLine("p1: ({0}, {1})", p1.x, p1.y); Point2 p2 = new Point2(11, 22); p2.PrintCoordinate(); Console.WriteLine("p2: ({0}, {1})", p2.x, p2.y); } } } ``` --- ### Nullable **Java** 參考資料:(https://brianwu.gitbook.io/brian/java/java/jdk8#shi-yong-optional-qu-dai-null) 有回傳值的方法,有時候回傳的是物件(可為null) 很常發生 **沒檢查導致NPE** or **每次取回來都要先檢查(a != null)** 的問題發生 Clean Code 也提倡不要回傳null Java 8 有了 **Optional** 用來處理這個問題(個人理解) 如果回傳的值有可能是null,則回傳 `Optional<T>` 如何回傳則是用 `Optional.of()` 包起來 **範例請點參考連結,也是我以前整理的** ``` // C# 的 2. 相當於 Java 的 Optional.orElse(default) // 3. 我原本以為是 Optional,但查完好像猜錯了 QQ ``` **C#** 參考資料:(https://dotblogs.com.tw/keino123/2011/04/20/23132) 參考資料:(https://cloud.tencent.com/developer/article/1341287) 在C#中 **?** 的用途有三種 1.三元運算子 2.變數為null則設為預設值 3.可以為null的類型 4.Null 條件運算子 ``` //System.Nullable<T> 1. txtName.Text.Trim().Length == 0 ? null : txtName.Text.Trim() 2. a??0 相當於 if (a == null) { 取 0; } else { 取 a; } 3. int? number; Java 在設計的時候,不為 null 的會直接用基礎型別 int 可能為 null 的,會用類別 Integer 但 C# 沒有 Integer,而是用 ? 來做識別 4. obj?.field //使用程式碼 Customer customer = new Customer(); string name = customer?.Name; //編譯程式碼 Customer customer = new Customer(); if (customer != null) { string name = customer.Name; } //組合拳 if (customer?.Face()??false) //串火車 int? contactNameLen = contact?.Name?.Length; /* 這個語法糖的目的是在物件使用前檢查是否為null。 如果物件為空,則賦值給變數為空值,所以例子中需要一個可以為空的int型別、即int?。 如果物件不為空,則呼叫物件的成員取值,並賦值給變數。 */ ``` --- ### C#語法糖 語法糖可以提供比較便捷的寫法,但不要以為兩者會一模一樣。 參考資料:(https://dotblogs.com.tw/code6421/2017/12/06/CSharpSugar01) **語法糖整理** 參考資料:(https://www.796t.com/content/1546953131.html) 1. 簡化 Property 原本 ``` private string _myName; public string MyName { get { return _myName; } set { _myName = value; } } ``` 語法糖 ``` public string MyName { get; set; } // 想個別調整修飾詞時 public string MyName { get; protected internal set; } ``` 2 3 4 都只是改用 lambda 5. using == try finally 原本 ``` StreamWriter sw = null; try { sw = new StreamWriter("d:\abc.txt"); sw.WriteLine("test"); } finally { if(sw!= null) sw.Dispose(); } ``` 語法糖 ``` using (var sw = new StreamWriter("d:\abc.txt")) { sw.WriteLine("test"); } ``` *Java 也有一樣的東西 6. var 只能在區域變數使用,不能當欄位或方法的參數 7. 問號 (參考本文 Nullable) 8. 型別例項化的語法糖 可以在沒有建構子的情況下,直接設定初始值 ``` var abc = new Abc{ field1=value1, field2=value2, field3=value3 }; ``` 9. ~~傳說中的擴充套件方法~~ (完全有看沒懂) 10. 使用匿名類別(參考本文 匿名類別) 11. NULL 條件運算子 (參考本文 Nullable) 12. 字串格式化 原本 ``` var contactInfo = string.Format("Id:{0} Name:{1} EmailAddr:{2} PhoneNum:{3}", contact.Id, contact.Name, contact.EmailAddress, contact.PhoneNum); ``` 語法糖 ``` var contactInfo2 = $"Id:{contact.Id} Name:{contact.Name} EmailAddr:{contact.EmailAddress} PhoneNum:{contact.PhoneNum}"; // 新格式化方式還支援任何表示式的直接賦值: var contactInfo = $"Id:{contact.Id} Name:{(contact.Name.Length == 0 ? "Frank" : contact.Name)} EmailAddr:{contact.EmailAddress} PhoneNum:{contact.PhoneNum}"; ``` **語法糖剖析** 9. 異常過濾器when ``` var client = new HttpClient(); var streamTask = client.GetStringAsync("https://localHost:10000"); try { var responseText = await streamTask; return responseText; } catch (HttpRequestException e) when (e.Message.Contains("301")) { return "Site Moved"; } catch (HttpRequestException e) when (e.Message.Contains("404")) { return "Page Not Found"; } catch (HttpRequestException e) { return e.Message; } ``` When語法作用是:在進入到catch之前、驗證when括號裡myfilter方法返回的bool,如果返回true繼續執行,false不走catch直接丟擲異常。 使用這個filter可以更好的判斷一個錯誤是繼續處理還是重新丟擲去。按照以前的做法,在catch塊內如需再次丟擲去,需要重新throw出去,這時的錯誤源是捕捉後在拋的,而不是原先的,有了when語法就可以直接定位到錯誤源。 10. catch和finally程式碼塊內的Await Await非同步處理是在c#5.0提出的,但不能在catch和finally程式碼塊內使用,這次在C#6.0更新上支援了。 ``` async void Solve() { try { await HttpMethodAsync(); } catch (ArgumentException e) { await HttpMethodAsync(); } finally { await HttpMethodAsync(); } } ``` 編譯器把 catch 和 finally 的 await 生成到狀態機裡面的 MoveNext()裡面。 原來裡面只有 TaskAwaiter,現在多了2個。 狀態機裡面的程式碼和原先的一樣,只是更復雜了,有興趣的可以先看下Async、Await剖析再去深究。 11. nameof ``` string name = ""; Console.WriteLine(nameof(name)); // name ``` 有時候會需要程式中一些成員的字串名稱,比如丟擲 `ArgumentNullException` 異常的時候,想知道 `ArgumentNullException` 型別的字串名稱,這時候就可以用 `nameof` 獲取字元 串 `ArgumentNullException` 現在做法都是手動複製一下,但重構改名的時候容易忘記變更字串,使用 `nameof` 就可以避免了。 ### Map Java put 重複的 key,會直接覆蓋 C# 似乎會噴 Exception。 Java ``` Map<String,String> map = new HashMap<>(); ``` C# 參考資料:(https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2?view=net-8.0) ``` // Create a new dictionary of strings, with string keys. Dictionary<string, string> openWith = new Dictionary<string, string>(); // Add some elements to the dictionary. // There are no duplicate keys, but some of the values are duplicates. openWith.Add("txt", "notepad.exe"); openWith.Add("bmp", "paint.exe"); openWith.Add("dib", "paint.exe"); openWith.Add("rtf", "wordpad.exe"); // The Add method throws an exception if the new key is // already in the dictionary. try { openWith.Add("txt", "winword.exe"); } catch (ArgumentException) { Console.WriteLine("An element with Key = \"txt\" already exists."); } // The Item property is another name for the indexer, so you // can omit its name when accessing elements. Console.WriteLine("For key = \"rtf\", value = {0}.", openWith["rtf"]); // The indexer can be used to change the value associated // with a key. openWith["rtf"] = "winword.exe"; Console.WriteLine("For key = \"rtf\", value = {0}.", openWith["rtf"]); // If a key does not exist, setting the indexer for that key // adds a new key/value pair. openWith["doc"] = "winword.exe"; // The indexer throws an exception if the requested key is // not in the dictionary. try { Console.WriteLine("For key = \"tif\", value = {0}.", openWith["tif"]); } catch (KeyNotFoundException) { Console.WriteLine("Key = \"tif\" is not found."); } // When a program often has to try keys that turn out not to // be in the dictionary, TryGetValue can be a more efficient // way to retrieve values. string value = ""; if (openWith.TryGetValue("tif", out value)) { Console.WriteLine("For key = \"tif\", value = {0}.", value); } else { Console.WriteLine("Key = \"tif\" is not found."); } // ContainsKey can be used to test keys before inserting // them. if (!openWith.ContainsKey("ht")) { openWith.Add("ht", "hypertrm.exe"); Console.WriteLine("Value added for key = \"ht\": {0}", openWith["ht"]); } // When you use foreach to enumerate dictionary elements, // the elements are retrieved as KeyValuePair objects. Console.WriteLine(); foreach( KeyValuePair<string, string> kvp in openWith ) { Console.WriteLine("Key = {0}, Value = {1}", kvp.Key, kvp.Value); } // To get the values alone, use the Values property. Dictionary<string, string>.ValueCollection valueColl = openWith.Values; // The elements of the ValueCollection are strongly typed // with the type that was specified for dictionary values. Console.WriteLine(); foreach( string s in valueColl ) { Console.WriteLine("Value = {0}", s); } // To get the keys alone, use the Keys property. Dictionary<string, string>.KeyCollection keyColl = openWith.Keys; // The elements of the KeyCollection are strongly typed // with the type that was specified for dictionary keys. Console.WriteLine(); foreach( string s in keyColl ) { Console.WriteLine("Key = {0}", s); } // Use the Remove method to remove a key/value pair. Console.WriteLine("\nRemove(\"doc\")"); openWith.Remove("doc"); if (!openWith.ContainsKey("doc")) { Console.WriteLine("Key \"doc\" is not found."); } /* This code example produces the following output: An element with Key = "txt" already exists. For key = "rtf", value = wordpad.exe. For key = "rtf", value = winword.exe. Key = "tif" is not found. Key = "tif" is not found. Value added for key = "ht": hypertrm.exe Key = txt, Value = notepad.exe Key = bmp, Value = paint.exe Key = dib, Value = paint.exe Key = rtf, Value = winword.exe Key = doc, Value = winword.exe Key = ht, Value = hypertrm.exe Value = notepad.exe Value = paint.exe Value = paint.exe Value = winword.exe Value = winword.exe Value = hypertrm.exe Key = txt Key = bmp Key = dib Key = rtf Key = doc Key = ht Remove("doc") Key "doc" is not found. */ ```