# 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.
*/
```