C# note

以下是為了能夠滿足段落所需的長度而定義的無意義內文,請自行參酌編排。

一個 C# 專案的完整架構:「蓋一棟辦公大樓」

—- 第一層:最小的零件 – 變數、運算子與常數

這是一個專案中最微觀的層次,就像是建築材料中的磚頭、水泥和工具。

變數 (Variable):一個用來裝東西的盒子。
作用:用來儲存單一的資料,例如一個數字 int age = 30;。

運算子 (Operator):用來操作盒子的工具。
作用:執行各種運算,例如加法 +、乘法 *,或比較大小 >。

常數 (Constant):一個永不變動的標籤。
作用:儲存固定不變的值,例如圓周率 const double PI = 3.14;。

—- 第二層:基本結構 – 欄位與屬性

當你把磚頭和水泥組合起來,就形成了牆壁和房間。這就是類別中的基本結構。

欄位 (Field):房間裡的電線或水管。這些是類別的內部零件,通常不會直接暴露在外。
作用:用來儲存類別的內部狀態和資料,通常宣告為 private。

屬性 (Property):房間裡的開關或插座。這些是讓外部可以安全地與內部電線(欄位)互動的介面。
作用:提供了 get 和 set 兩個存取器,用來安全地讀取和寫入欄位的值。

—- 第三層:功能單位 – 方法與建構子

當房間的牆壁、水電都佈置好後,我們需要給它加上功能,例如電梯、冷氣或燈光。

方法 (Method):一個可執行的動作或功能。
作用:封裝了一系列程式碼,用來執行一個特定的任務,例如 StartEngine() 或 CalculateTotal()。

建構子 (Constructor):大樓的組裝說明書。
作用:在物件被建立時,自動執行的初始化程式碼。它確保物件一被創造出來,所有基本設定都已經到位。

—- 第四層:核心設計圖 – 類別與結構

將多個房間和功能組合起來,就形成了大樓的樓層設計圖。

類別 (Class):大樓的設計圖。
作用:一個抽象的藍圖,定義了物件的屬性(欄位、屬性)和行為(方法)。它可以被實例化成無數個獨立的物件。

結構 (Struct):一個小型的工具間設計圖。
作用:與類別類似,但它是實值型別,通常用來處理輕量級、簡單的資料,效能比類別高。

—- 第五層:邏輯分區 – 命名空間

當你的大樓越來越多,就需要進行分區管理,例如「商業區」和「住宅區」。

命名空間 (Namespace):城市的區域劃分。
作用:用來組織相關的類別、結構等,避免命名衝突。例如,ProjectA.Models 和 ProjectB.Models。

—- 第六層:完整專案

將所有分區組合起來,就形成了一座完整的城市。

專案 (Project):一座完整的城市。
作用:包含一個或多個命名空間,是編譯後的最終產物,可以是一個執行檔、函式庫或網站應用程式。

可搭配 template 套版來建立基本版型。

前端 使用 id 、 class
後端 使用 name
起始 位置 : “~/file.txt”

宣告

const:永恆不變的值

宣告常數前面要加 const ,但設定後就不能修改

const <資料型態> <常數名稱> = <值>;

// 宣告圓周率為常數,就不能再改變,若是嘗試修改常數會導致編譯錯誤

const double PI = 3.14159265359;

宣告不等於要給值只是給變數一個可使用的空間大小,(預設值的情況;VScode預設值 = 0;看系統預設;某個地方的值)

所有的變數 (不管是實值型態或是參考型態),其內容 (變數名稱、型態與值) 都是儲存在 Stack 中的「地址」。

使用 new 關鍵字實體化類別的物件,其物件內容是儲存在 Heap 中。

Stack(堆疊):一個有秩序的、像疊盤子一樣的書桌。「後進先出」(Last-In, First-Out, LIFO)的特性,[ctrl + z] 就是這個特性。

Heap(堆積):一個雜亂無章的、像倉庫一樣的大空間。電腦用來儲存動態分配的記憶體的地方。

實值型態 (Value Type):像是一個簡單的便條紙,上面直接寫著內容(例如數字 10 、字元 ‘A’ 、布林等等)。這個便條紙很輕,可以直接放在書桌(Stack)上。

參考型態 (Reference Type):像是一個沉重的包裹,裡面放著複雜的內容(例如一個 型態、陣列、字串、物件、類別)。這個包裹太重了,不能直接放在書桌上。所以,你只能把包裹放在倉庫(Heap)裡,然後在你的書桌(Stack)上放一張便條紙,上面寫著包裹的「地址」。

類別 ———– PascalCase (大駝峰命名法)
範例 public class MyClass

屬性 ———– PascalCase (大駝峰命名法)
範例 public string FullName { get; set; }

方法 ———– PascalCase (大駝峰命名法)
範例 public void StartEngine()


結構 ———– PascalCase (大駝峰命名法)
範例 public struct Point

列舉———– PascalCase (大駝峰命名法)
範例 public enum DayOfWeek

物件 ———– camelCase (小駝峰命名法)
範例 MyClass myObject

區域變數 ——- camelCase (小駝峰命名法)
範例 int studentScore

方法參數——- camelCase (小駝峰命名法)
範例 public void SetName(string newName)

私有欄位 —– _camelCase (底線加小駝峰)
範例 private int _myField;

常數 ———– 全大寫
範例 const int MAX_VALUE = 100;

布林值 (Boolean)

布林值 (Boolean),預設值為false

名稱:bool
資料型態:System.Boolean
佔用位元組:1
說明:其值為 True/False。

—- 數值型態,預設值都為0

8位元無號短整數
名稱:byte
資料型態:System.Byte
佔用位元組:1
說明:8位元無正負整數。範圍:0 ~ 255

8位元有號短整數
名稱:sbyte
資料型態:System.SByte
佔用位元組:1
說明:8位元有正負整數。範圍:-128 ~ 127

短整數
名稱:short
資料型態:System.Int16
佔用位元組:2
說明:有正負整數。範圍:-32,768 ~ 32,767

整數
名稱:int
資料型態:System.Int32
佔用位元組:4
說明:有正負整數。範圍:-2,147,483,648 ~ 2,147,483,647

長整數
名稱:long
資料型態:System.Int64
佔用位元組:8
說明:有正負整數。範圍:-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807。

無號短整數
名稱:ushort
資料型態:System.UInt16
佔用位元組:2
說明:無正負整數。範圍:0 ~ 65,535

無號整數
名稱:uint
資料型態:System.UInt32
佔用位元組:4
說明:無正負整數。範圍:0 ~ 4,294,967,295
無號長整數
名稱:ulong
資料型態:System.UInt64
佔用位元組:8
說明:無正負整數。範圍:0 ~ 18,446,744,073,709,551,615

浮點數 (Floating-point)
名稱:float
資料型態:System.Single
佔用位元組:4
說明:二進位。範圍:約 ±1.5e-45 ±3.4e38精確度7位。也就是說只有前7位數字是精確的,常用來做小數運算,但可能有誤差。

被精確數
名稱:double
資料型態:System.Double
佔用位元組:8
說明:二進位。範圍:約 ±5.0e-324 ±1.7e308精確度15~16位。也就是說就算最多可以表達出小數點以下324位小數,最多只有15~16位數字是精確的。常用來做小數運算,但可能有誤差。

十進位浮點
名稱:decimal
資料型態:System.Decimal
佔用位元組:16
說明:十進位數字,有效位數28。相較於浮點數類型,此類型精確度較高且範圍較小,適合做財務金融類的運算。

—-字串/字元 (String/Character),預設值為null

名稱:char
資料型態:System.Char
預設值:\0
佔用位元組:2
說明:值是一個 Unicode 的字元,以單引號包住。範圍:0 ~ 65,535。如:’z’~`’單引號包裹著一個字’。

名稱:string
資料型態:System.String
佔用位元組:-
說明:顧名思義就是一串字元。是字元的陣列,如:「我是C#小菜菜」雙引號內可以存放任意文字資料。

—- 物件 (Object)

名稱:object
資料型態:System.Object
佔用位元組:-
說明:可以存放任意資料型態的資料。

使用 var 來宣告變數會自動判別資料型態。
var a=1; //自動將a定義為int

轉換型態的方式︰

1.隱性轉換 & 強制轉型
2.Parse & TryParse (專門將字串轉成數值)
3.ToString (專門將型態轉成字串)

4.Convert (通用轉換型態)

隱含轉換 (Implicit Conversion)

—– 隱含轉換 (Implicit Conversion)

int myInt = 100;
long myLong = myInt;   // 100

float myFloat = 1.234f;
double myDouble = myFloat;   // 1.234

——強制轉型 (可能遺失資料,不安全)

double myDouble = 123.45;
int myInt = (int)myDouble; // 123

—- Parse 和 TryParse 方法這兩個方法專門用來將字串轉換成其他基本型態(如 int、double)。

範例︰

string b = “321”;
int a = int.Parse(b); // 321

string invalidString = “abc”;
// int.Parse(invalidString); // 這裡會拋出例外,程式崩潰

(Try) 會嘗試將使用者輸入的字串轉換成指定的型態。

如果轉換成功,它會回傳 true,並將轉換後的整數透過 out 存入 num 變數。
如果轉換失敗(例如使用者輸入了 “abc”),它會回傳 false,
但不會像 int.Parse() 一樣崩潰。

TryParse(“轉換內容” out “”) 轉換方法

string inputDate = Console.ReadLine(); // 假設使用者輸入 “2024-05-20” 或 “abc”
DateTime parsedDate;
// 直接在 if 判斷式中嘗試轉換
if (DateTime.TryParse(inputDate, out parsedDate))
{
    // 如果轉換成功,程式碼會進入這裡
    Console.WriteLine($”你輸入的日期是: {parsedDate}”);
}
else
{
    // 如果轉換失敗,程式碼會進入這裡
    Console.WriteLine(“你輸入的日期格式不正確。”);
}

int num = 0;
// int.TryParse() 會回傳一個布林值,並將轉換結果寫入 num

if (int.TryParse(Console.ReadLine(), out num))
{
    // 轉換成功,可以使用 num
    Console.WriteLine($”轉換成功,數字為: {num}”);
}
else
{
    // 轉換失敗,num 仍為 0
    Console.WriteLine(“轉換失敗”);
}

// 成功轉換就會是輸入的數值,即輸入的值傳給 num
// 錯誤就會保持 num = 0,且不會跳出錯誤

——ToString():主要任務就是將任何東西都轉換成字串。

1. ToString() 的基本用法

int myNumber = 123;
double myDouble = 45.67;

// 將整數轉成字串
string numberString = myNumber.ToString();
Console.WriteLine(numberString); // 輸出: “123”

// 將浮點數轉成字串
string doubleString = myDouble.ToString();
Console.WriteLine(doubleString); // 輸出: “45.67”

2. ToString() 的進階用法:搭配不同的格式化符號來輸出內容

double myMoney = 1234567.89;
DateTime myBirthday = new DateTime(1990, 5, 20);

// N2: 數字格式,有千分位符號,並保留兩位小數
string moneyString = myMoney.ToString(“N2”);
Console.WriteLine($“我的存款: {moneyString}”); // 輸出: 我的存款: 1,234,567.89

// 格式化日期和時間
string dateString = myBirthday.ToString(“yyyy/MM/dd”);
Console.WriteLine($“我的生日: {dateString}”); // 輸出: 我的生日: 1990/05/20

3. String.Format() 和字串內插 ($)

int count = 5;
string item = “蘋果”;

// 使用 String.Format()

string message = String.Format(“我買了 {0} {1}。”, count, item);
Console.WriteLine(message); // 輸出: 我買了 5 個 蘋果。

int count = 5;
string item = “蘋果”;
// 使用字串內插
string message2 = $“我買了 {count} 個 {item}。”;
Console.WriteLine(message2); // 輸出: 我買了 5 個 蘋果。

// 字串內插也可以搭配 ToString() 的格式化
double price = 1000.5;
Console.WriteLine($“今天的價格是 {price:N2}。”); // 輸出: 今天的價格是 1,000.50。

——格式化符號:

F Fixed-point
固定小數點格式: F4 , 4 代表要保留四位小數
Fixed num = 2,123456789;
Console.WriteLine($“{area:F4}”); // 輸出: 2.1234

C Currency
貨幣格式:會自動加上當前地區的貨幣符號。 
double price = 1234.56;
Console.WriteLine($“{price:C2}”); // 輸出: $1,234.56

D Decimal
十進位格式:用來對齊整數,不足位數時會補零。
int num = 123;
Console.WriteLine($“{num:D5}”); <br> // 輸出: 00123

N Number
數字格式:會自動加上千分位逗號。
double num = 1234567.89;
Console.WriteLine($“{num:N2}”); // 輸出: 1,234,567.89

P Percent
百分比格式:數字乘以 100,然後加上百分比符號。
double ratio = 0.75;
Console.WriteLine($“{ratio:P1}”); // 輸出: 75.0%

X Hexadecimal
十六進位格式:將數字轉換為十六進位。
int colorCode = 255;
Console.WriteLine($“{colorCode:X}”); // 輸出: FF

—- Convert 類別

用途:
使用各種To方法,可以用來處理各種型態之間的轉換。
處理 null 值,如果轉換的來源是 null,它會回傳預設值而不會拋出例外。

double 轉 int:
double myValue = 123.45;
int intValue = Convert.ToInt32(myValue); // 轉為整數,並會進行四捨五入
Console.WriteLine(intValue); // 輸出:123

處理 null 值:
string nullString = null;
int intFromNull = Convert.ToInt32(nullString); // 轉換 null,不會拋出例外
Console.WriteLine(intFromNull); // 輸出:0

1. 轉換成數字
Convert.ToInt32() // 轉換成整數
Convert.ToDouble() // 轉換成雙精度浮點數
Convert.ToBoolean() // 轉成布林值

2.轉換成字串
Convert.ToString()

3. 轉換成日期
Convert.ToDateTime()
string s = “2024/01/01”;
DateTime dt = Convert.ToDateTime(s); // 將字串轉成日期時間型態

DateTime 物件的方法

DateTime.Now:取得當前的日期和時間。
DateTime.Today:取得當前日期,時間部分會是 00:00:00。
new DateTime(年, 月, 日):用指定的年、月、日來創建一個 DateTime 物件。你也可以傳入時、分、秒等參數。
DateTime.Parse(字串):將字串轉換成 DateTime 物件。這個方法比較「寬鬆」,會自動嘗試解析多種格式。
DateTime.ParseExact(字串, 格式字串, 文化設定):將字串按照精確的格式來轉換成 DateTime 物件。這個方法更嚴格,更適合處理固定格式的輸入。
DateTime.TryParse(字串, out 變數):與 Parse 類似,但它會安全地嘗試轉換,如果失敗,不會拋出例外,而是回傳 false。

DateTime 物件的屬性

Year:取得年份。
Month:取得月份。
Day:取得日。
Hour:取得小時。
Minute:取得分鐘。
Second:取得秒。
DayOfWeek:取得星期幾(回傳 DayOfWeek 列舉,0 為星期日,1 為星期一)。
DayOfYear:取得一年中的第幾天。

時間運算方法
AddDays(天數):在日期上增加或減少指定的天數。
AddMonths(月數):在日期上增加或減少指定的月數。
AddYears(年數):在日期上增加或減少指定的年數。
AddHours(時數):在時間上增加或減少指定的時數。
AddMinutes(分數):在時間上增加或減少指定的分鐘。

ToString(“yyyy/MM/dd ddd HH:mm”):將 DateTime 物件轉換成指定字串格式。
yyyy 四位數的年份 2025
yy 兩位數的年份 25
MM 兩位數的月份 09
M 一位或兩位數的月份 9
dd 兩位數的日期 18
d 一位或兩位數的日期 18
HH 24 小時制的小時 (00-23) 13
hh 12 小時制的小時 (01-12) 01
mm 兩位數的分鐘 30
ss 兩位數的秒鐘 58
tt 上午/下午的指示符 下午
ddd 星期的英文三位簡寫 Thu
dddd 星期的英文完整名稱 Thursday

TimeSpan 物件可以取得時間間隔的各種資訊:

TotalDays:取得總天數(包含小數)。
TotalHours:取得總小時數(包含小數)。
TotalMinutes:取得總分鐘數(包含小數)。
Days:取得時間間隔中的天數部分。
Hours:取得時間間隔中,不滿一天的小時部分。
Minutes:取得時間間隔中,不滿一小時的分鐘部分。

DateTime day1 = DateTime.ParseExact(Console.ReadLine(),”yyyyMMdd”,null);
DateTime day2 = DateTime.ParseExact(Console.ReadLine(),”yyyyMMdd”,null);
TimeSpan a = day1 – day2;
Console.Write(Math.Abs(a.TotalDays));

輸入與輸出方法

Console.WriteLine():這個方法會在你輸出完內容後,自動換到下一行。
Console.WriteLine(“Hello”);
Console.WriteLine(“World”);
// 輸出結果:
// Hello
// World

Console.Write():這個方法只會輸出內容,不會自動換行。
Console.Write(“Hello”);
Console.Write(“World”);
// 輸出結果:
// HelloWorld

跳脫字元(Escape Character/逸出字元)
使用@可以防止跳脫字元的報錯

Console.ReadKey():等待一個按鍵
用途:等待使用者確認或做下一步動作,讓執行視窗停住,不然程式一跑完視窗就會立刻關閉
回傳值:它會回傳一個 ConsoleKeyInfo 物件,這個物件包含了你按下的按鍵資訊,像是按鍵是哪一個,以及有沒有同時按下 Ctrl、Shift 等修飾鍵。

—- 字串拼接方法

StringBuilder.Append():最有效率的拼接
比喻:就像一個可伸縮的樂高底板。你可以不斷地把新的積木(字串)加到底板上,而不需要每次都重新買一個新的底板。

StringBuilder sb = new StringBuilder();
sb.Append(“我愛”);
sb.Append(“C#”);
sb.Append(“程式語言”);

// 必須使用 ToString() 來取得最終的字串
string result = sb.ToString();
Console.WriteLine(result); // 輸出: 我愛C#程式語言

使用 StringBuilder.AppendFormat 來替換傳遞的格式說明

int num = 520;
string result1 = sb.AppendFormat($“{num}”).ToString();
Console.WriteLine(result1); // 輸出: 我愛C#程式語言520

StringBuilder sb = new StringBuilder(“我愛C#程式語言”, 10);
// 可以使用 sb.Capacity 屬性來設定字元上限值
(避免浪費系統速度,不用每次都建立)

// 可以指定他能容納的字元數上限值 (10),
當超過後才會在重新配置它的大小(增加超出的字元容量值)

String.Join():用分隔符拼接陣列
比喻:String.Join() 就像一個串珠工具。你給它一堆珠子(字串陣列)和一條線(分隔符),它會自動幫你把所有珠子串起來。

string[] words = { “Hello”, “World”, “C#” };
// 用空格連接
string sentence = String.Join(” “, words);
Console.WriteLine(sentence); // 輸出: Hello World C#

String.Concat():簡單的無分隔符拼接
比喻:String.Concat() 就像一個膠水。你給它一堆樂高積木,它會無縫地把它們黏在一起。

string part1 = “Happy”;
string part2 = ” Birthday”;
string part3 = ” to you”;
// 用 Concat() 拼接
string message = String.Concat(part1, part2, part3);
Console.WriteLine(message); // 輸出: Happy Birthday to you

—- 字串尋找

String.Contains():全車廂搜尋
比喻:Contains() 就像一個偵探,他會地毯式地搜尋整列火車,只要找到任何一個車廂裡面有你要找的「乘客」(子字串),他就會告訴你:「有!(True)」

string train = “Hello, World!”;
bool containsHello = train.Contains(“Hello”); // True
bool containsWorld_case = train.Contains(“world”); // False,因為大小寫不同

String.StartsWith():車頭偵測
比喻:StartsWith() 就像一個在火車站入口的偵探,他只會檢查火車的第一個車廂。只要車頭的「乘客」符合你的要求,他就告訴你:「是!(True)」

string sentence = “The quick brown fox.”;
bool startsWithThe = sentence.StartsWith(“The”); // True
bool startsWithFox = sentence.StartsWith(“Fox”); // False,因為大小寫不同

String.EndsWith():車尾偵測
比喻:EndsWith() 則是在火車站尾部的偵探。他只會檢查火車的最後一個車廂。

string filename = “report.txt”;
bool endsWithTxt = filename.EndsWith(“.txt”); // True
bool endsWithDoc = filename.EndsWith(“.doc”); // False

IndexOf():首次發現的報告
比喻:IndexOf() 是一個精準的偵探,他會從火車車頭開始搜尋,一旦首次發現你要找的「乘客」,他會立即回報給你他的位置編號(索引)。

string text = “Hello World, Hello Universe!”;
int index1 = text.IndexOf(“Hello”); // 第一次出現是在索引 0
int index2 = text.IndexOf(“World”); // 輸出 6
int index3 = text.IndexOf(“Z”); // 找不到,輸出 -1

LastIndexOf():最後一次發現的報告
比喻:LastIndexOf() 則是一個從車尾開始搜尋的偵探,他會給你最後一次發現「乘客」的位置編號。

string text = “Hello World, Hello Universe!”;
int lastIndex = text.LastIndexOf(“Hello”); // 最後一次出現是在索引 13
int lastIndex2 = text.LastIndexOf(“World”); // 輸出 6

—- 字串擷取

Substring():精準的切割機
比喻:Substring() 就像一台精密的切割機。你告訴它從哪裡開始,以及要切多長,它就會從麵包上精準地切下一片給你。
# Substring() 如果指定的索引或長度超出了原字串的範圍,會拋出 ArgumentOutOfRangeException 例外。

string bread = “Hello, World!”; // 索引: 0123456789…
// Substring(起始索引):從指定的索引位置開始,一直截取到字串的結尾。

string part1 = bread.Substring(7);
Console.WriteLine(part1); // 輸出: World!

// Substring(起始索引, 長度):從指定的索引位置開始,截取指定長度的字串。
string part2 = bread.Substring(0, 5);
Console.WriteLine(part2); // 輸出: Hello
# 搭配IndexOf可以加上+ word.Length,可以包含最後搜索的文字

Split():萬能的分隔刀
比喻:Split() 就像一把萬能的分隔刀。你指定一個分隔符號(例如空格、逗號),它就會根據這個符號,把麵包切成好幾塊。

// 使用空格作為分隔符
string sentence = “Hello World C#”;
string[] wordList = sentence.Split(‘ ‘);
Console.WriteLine(wordList[1]); // 輸出: World

// 使用多個分隔符
string data = “姓名:小明;年齡:20”;
char[] separators = { ‘:’, ‘;’ };
string[] dataParts = data.Split(separators);
Console.WriteLine(dataParts[1]); // 輸出: 小明
# Split() 回傳的是一個字串陣列,所以你需要用陣列的索引來存取切割後的結果。

—- 字串修改

# string 是不可變的,代表不會修改原來的字串,而是會回傳一個新的字串。

Replace():替換工具
比喻:Replace() 就像一個替換筆,你告訴它把「A」字換成「B」字,它就會將所有符合條件的「A」都換掉。

string message = “Hello, C#!”;
string newMessage = message.Replace(“C#”, “World”);
Console.WriteLine(newMessage); // 輸出: Hello, World!

Trim():修剪工具
比喻:Trim() 就像一把剪刀,它可以剪掉頭尾多餘(空格、換行、Tab 等)的部分。

string input = ” Hello World! “;
string trimInput = input.Trim();
Console.WriteLine($“‘{trimInput}'”); // 輸出: ‘Hello World!’
# TrimStart() 移除開頭的空白,TrimEnd() 移除結尾的空白。

Remove():移除工具
比喻:Remove() 就像一個橡皮擦,你告訴它從哪裡開始,擦掉多長的範圍,它就會將那邊擦掉。

string sentence = “I love C# programming.”;
// 移除 ” programming.”
string newSentence = sentence.Remove(8);
Console.WriteLine(newSentence); // 輸出: I love C#
// 從索引 2 開始,移除 4 個字元 (移除 “love”)
string shortSentence = sentence.Remove(2, 5);
Console.WriteLine(shortSentence); // 輸出: I C# programming.

Insert():插入工具
比喻:Insert() 就像一個黏土刀,你可以在黏土的某個位置,插入一塊新的黏土。

string original = “Hello World!”;
// 在索引 6 (World!) 之前插入 “, C#”
string newString = original.Insert(6, “, C#”);
Console.WriteLine(newString); // 輸出: Hello, C# World!

PadLeft() 和 PadRight():填充工具
比喻:PadLeft() 和 PadRight() 就像是填充機,你可以將黏土填充到指定的長度。

// 填充到總長度 10,不足的部分在左邊補上 ‘*’
string paddedName2 = name.PadLeft(10, ‘*’);
Console.WriteLine($“‘{paddedName2}'”); // 輸出: ‘******John’

// 填充到總長度 10,不足的部分在右邊補上空格
string paddedName3 = name.PadRight(10);
Console.WriteLine($“‘{paddedName3}'”); // 輸出: ‘John ‘

Random亂數宣告

Random 變數名稱= new Random();
變數名稱.Next(最小值,最大值),不包括最大值

Math.Round():四捨五入,五成雙

  • 如果 5 前一位是偶數,則捨去。 // 4.5 –> 4

  • 如果 5 前一位是奇數,則進位。 // 3.5 –> 4

Console.WriteLine(Math.Round(4.5)); // 4 是偶數,捨去 -> 輸出: 4
Console.WriteLine(Math.Round(5.5)); // 5 是奇數,進位 -> 輸出: 6
Console.WriteLine(Math.Round(4.4)); // 輸出: 4
Console.WriteLine(Math.Round(4.6)); // 輸出: 5

Math.Ceiling():無條件進位,天花板效應

用途:當你需要確保結果永遠大於或等於原始值時,
例如計算商品分批運送所需的箱子數量,即使只有一個零頭,也需要一個完整的箱子。

Console.WriteLine(Math.Ceiling(4.1)); // 輸出: 5
Console.WriteLine(Math.Ceiling(4.9)); // 輸出: 5
Console.WriteLine(Math.Ceiling(4.0)); // 輸出: 4
Console.WriteLine(Math.Ceiling(-4.1)); // 輸出: -4

Math.Floor():無條件捨去,地板效應

用途:當你需要確保結果永遠小於或等於原始值時,
例如計算一個物件可以放入多少個完整的包裝盒。

Console.WriteLine(Math.Floor(4.1)); // 輸出: 4
Console.WriteLine(Math.Floor(4.9)); // 輸出: 4
Console.WriteLine(Math.Floor(4.0)); // 輸出: 4
Console.WriteLine(Math.Floor(-4.1)); // 輸出: -5

Math.Truncate():單純地截斷

用途:當你只需要取得數字的整數部分,且不關心四捨五入或進位時。

Console.WriteLine(Math.Truncate(4.1)); // 輸出: 4
Console.WriteLine(Math.Truncate(4.9)); // 輸出: 4
Console.WriteLine(Math.Truncate(-4.1)); // 輸出: -4

id[i] – ‘0’ 數字字元轉整數數字

‘0’ ~ ‘9’ 的 ASCII 碼是 48~ 57

‘5’ – ‘0’ 的運作原理 : 

當你寫下 id[i] – ‘0’ 這段程式碼時,電腦會這樣做:
id[i] 會先取出字串中的一個字元,例如 ‘5’。

電腦會將這個字元轉換成它對應的 ASCII 碼,也就是 53。
接著,它會將字元 ‘0’ 也轉換成它對應的 ASCII 碼,也就是 48。
最後,電腦執行減法運算:53 – 48,得到的結果就是 5。

這樣,我們就成功地把一個代表數字的字元 ‘5’,轉換成了我們可以用來做數學運算的整數 5!

算術運算子 (Arithmetic Operators)
+   加法 a + b 執行加法運算。
–   減法 a – b 執行減法運算。
   乘法 a * b 執行乘法運算。
/   除法 a / b 執行除法運算。如果兩個整數相除結果是整數,小數會被捨去。
取餘數 a % b 傳回兩個數相除後的餘數。

++ 遞增 a++ 將變數值增加 1。
# ++放後面,先做敘述在做運算,++放前面,先做運算在做敘述

遞減 a– 將變數值減少 1。
# –放後面,先做敘述在做運算,–放前面,先做運算在做敘述

比較運算子 (Relational Operators)

這些運算子用來比較兩個值,並傳回一個布林值 true 或 false。

== 等於 a == b 檢查 a 是否等於 b。
!=  不等於 a != b 檢查 a 是否不等於 b。
>   大於 a > b 檢查 a 是否大於 b。
<  小於 a < b 檢查 a 是否小於 b。

  • 每個字元都有一個Unicode,例如a的Unicode為97,b為98。

  • 當要比較兩個字元的時候,實際上是比較他們的Unicode,因此b大於a(98大於97)

>= 大於或等於 a >= b 檢查 a 是否大於或等於 b。
<= 小於或等於 a <= b 檢查 a 是否小於或等於 b。

邏輯運算子 (Logical Operators)

&&  且 (AND) a && b 只有當 a 和 b 都為 true 時,結果才為 true。
||    或 (OR) a || b 當 a 或 b 為 true 時,結果為 true。
!      非 (NOT) !a 將 a 的布林值反轉。

複合指定運算子 (Compound Assignment Operators)

+=   加後指定 a += b a = a + b
-=    減後指定 a -= b a = a – b
*=   乘後指定 a *= b a = a * b
/=   除後指定 a /= b a = a / b
%=  取餘後指定 a %= b a = a % b

三元運算子 ? :

a > b ? a : b 簡化 if-else 判斷式,
如果條件為真,傳回第一個值a,否則傳回第二個值b

try…catch 是用來處理程式碼中錯誤和例外(Exception)的強大工具

try 區塊:把可能出錯的程式碼放進來
catch 區塊:當錯誤發生時的應對方案

Console.WriteLine(“請輸入一個數字:”);
string input = Console.ReadLine();

try
{
    int number = int.Parse(input);
   // 如果數字太大,也會拋出例外
    if (number > 1000)
    {
        throw new OverflowException(“數字超過了範圍。”);
    }
}

catch (FormatException)
{
   // 只捕捉當輸入格式錯誤時的例外
    Console.WriteLine(“錯誤:輸入的不是數字。”);
}
catch (OverflowException)
{
    // 只捕捉當數字太大時的例外
    Console.WriteLine(“錯誤:數字太大,超過了允許的範圍。”);
}
catch (Exception ex)
{
    // 捕捉所有其他類型的例外
    Console.WriteLine($“發生了未知錯誤:{ex.Message}”);
}
finally
{
    // 不論是否發生錯誤,finally 區塊的程式碼都會被執行
    Console.WriteLine(“程式執行結束。”);
}

—- switch 語法就像是一個「非範圍值多重選擇器」

使用時機:

當你需要根據一個單一變數的值來做多重判斷時。
當判斷的條件是離散的常數值,而不是一個範圍時(例如 day == 1 而不是 day > 5)。

switch 語法的使用方式:
switch:後面放上你要比對的變數。
case:定義當變數等於某個值時,要執行的程式碼區塊。
default:當所有 case 都沒有匹配時,程式會執行的預設區塊。
break:當匹配成功後要跳出switch,如果沒有 break,編譯器會報錯。

char grade = ‘B’;
switch (grade)
{
    case ‘A’:
    case ‘B’:
    case ‘C’:
    Console.WriteLine(“及格!”); // 結束這個 case,跳出 switch
    break;
    case ‘D’:
    case ‘F’:
    Console.WriteLine(“不及格!”);
    break;
    default:
    Console.WriteLine(“成績錯誤!”);
    break;
}
// 輸出: 及格!

————————————————————

—- 新版本 (C# 8.0 之後) switch 表達式寫法,可使用範圍值

int score = 85;

string grade = score switch
{
    >= 90 => “A”,
    >= 80 => “B”,
    >= 70 => “C”,
    _ => “D” // _ 代表所有其他情況 (就像 default)
};

Console.WriteLine(grade); // 輸出: B

使用 switch 表達式搭配 when
when:是 case 後面的額外條件過濾器,它讓你可以在一個 case 區塊裡,再加入更精準的判斷邏輯。

int age = 25;
string gender = “male”;

string category = (age, gender) switch
{
    // 如果年齡在 18~30 之間,並且性別是 “male”
    (>= 18 and <= 30, “male”) when gender == “male” => “年輕男性”,

    // 如果年齡大於 30,並且性別是 “male”
    (> 30, “male”) when gender == “male” => “中年男性”,

    // 如果年齡在 18~30 之間,並且性別是 “female”
    (>= 18 and <= 30, “female”) when gender == “female” => “年輕女性”,

    // … 其他情況
    _ => “其他”
};
Console.WriteLine(category); // 輸出: 年輕男性

for 迴圈:重複執行的自動化機器

for (int i = 1; i <= 5; i++)
{
  // 在這個大括號 {} 裡面的程式碼會被重複執行
  // 在迴圈外的程式碼區塊中無法存取迴圈中宣告的變數
    Console.Write(i);
}
// 1 2 3 4 5

if-else 判斷式:做出選擇的邏輯大腦

int score = 75;
if (score >= 60)
{
  Console.WriteLine(“B 等級”);
}
else if (score >= 90)
{
  Console.WriteLine(“A 等級”);
}
else
{
  Console.WriteLine(“不及格”);
}
// 輸出: B 等級

while 迴圈:條件成立時就一直運作的機器

int number = -1; // 給一個不為 0 的起始值

while (number != 0)
{
    Console.WriteLine(“請輸入一個數字 (輸入 0 結束):”);
    string input = Console.ReadLine();
    // 嘗試將輸入轉換為數字
    if (int.TryParse(input, out number))
    {
    Console.WriteLine($“你輸入了: {number}”);
    }
    else
    {
    Console.WriteLine(“輸入無效,請重新輸入。”);
    }
}
Console.WriteLine(“迴圈已結束。”);

 

Do-While Loops : 迴圈與 while 迴圈類似,但有一個關鍵區別:它保證程式碼區塊至少執行一次,無論條件如何。

do-while 循環的語法包括兩個部分:

  • The loop begins with the keyword do, followed by a code block enclosed in curly braces {}.
    迴圈以關鍵字 do 開始,後面跟著用花括號 {} 括起來的程式碼區塊。
  • After the code block, the keyword while and the condition are specified.
    程式碼區塊之後,指定關鍵字 while 和條件。

int counter = 1;
do
{
   Console.WriteLine(counter);
   counter++;
} while (counter < 10);

—陣列 (Array):已知資料數量同質的空間,預設值為null

比喻:陣列就像是一個有編號、固定大小的鞋盒櫃。每個格子只能放同一種東西,而且在櫃子做好後,格子的數量就不能變動了。

特性:
資料結構:它是一個用來儲存多個相同型態資料的容器。
連續記憶體:陣列的資料在記憶體中是連續存放的,這使得存取速度非常快。
固定大小:一旦宣告陣列,它的長度就固定了,不能隨意增加或減少。

型態關係:
陣列本身不是基本型態,但它是一種複合型態。它的型態由它所儲存的元素的型態決定,例如 int[](整數陣列)或 string[](字串陣列)。

遍歷陣列方法
int[] numbers = { 1, 2, 3, 4, 5 };
// 使用 for 迴圈
// numbers.Length 會自動取得陣列的長度(這裡是 5)

for (int i = 0; i < numbers.Length; i++)
{
    Console.WriteLine(numbers[i]);

  // 修改陣列內容
    int number = 0;
    int.TryParse(Console.ReadLine(), out number);
    numbers[i] = number;
}
// 由小至大排序內容
Array.Sort(numbers);

// 使用join來格式化成字串
Console.Write(string.Join(” “, numbers));

// 使用 foreach 迴圈(更簡潔,但只能讀取不能修改)
foreach (int number in numbers)
{
    Console.WriteLine(number);
}

—– 列表(List):彈性資料數量的空間,預設值為null

比喻:列表就像是一個彈性購物袋。你可以隨時往裡面添加或取出東西,袋子的容量會自動擴充,非常靈活。

特性:
彈性長度:可以動態地增加或減少元素。
方便操作:提供了許多內建方法,如新增、刪除、搜尋等。
效能稍差:相較於陣列,因為需要動態調整大小,操作速度會稍微慢一點,但對於大多數應用程式來說,這個差異微乎其微。

型態單一:和陣列一樣,只能儲存同一種型態的資料。

List 的使用方法
使用 List 通常分為幾個步驟:宣告、新增、存取和遍歷。

1. 宣告與初始化使用 new List<Type>() 來宣告一個列表。

// 宣告一個名為 scores 的整數列表
List<int> scores = new List<int>();

// 宣告並直接初始化,裡面包含一些水果
List<string> fruits = new List<string> { “蘋果”, “香蕉”, “橘子” };

2.新增與刪除元素
List<string> shoppingList = new List<string>();

// 使用 .Add() 新增一個元素到列表的最後
shoppingList.Add(“牛奶”);
shoppingList.Add(“麵包”);
// 現在列表為 [“牛奶”, “麵包”]

// 使用 .Insert() 在指定位置插入一個元素
shoppingList.Insert(1, “果汁”);
// 現在列表為 [“牛奶”, “果汁”, “麵包”]

// 使用 .Remove() 刪除第一個匹配的元素
shoppingList.Remove(“麵包”);
// 現在列表為 [“牛奶”, “果汁”]

// 使用 .RemoveAt() 刪除指定索引的元素
shoppingList.RemoveAt(0);
// 現在列表為 [“果汁”]

3.存取與搜尋元素
List<string> fruits = new List<string> { “蘋果”, “香蕉”, “橘子” };

// 存取列表中的第一個元素
Console.WriteLine(fruits[0]); // 輸出: 蘋果

// 取得列表的長度
Console.WriteLine(fruits.Count); // 輸出: 3

// 檢查某個元素是否存在於列表中
bool hasBanana = fruits.Contains(“香蕉”);
Console.WriteLine(hasBanana); // 輸出: True

// 找到某個元素的索引
int index = fruits.IndexOf(“橘子”);
Console.WriteLine(index); // 輸出: 2

4. 遍歷列表
List<int> numbers = new List<int> { 10, 20, 30, 40 };

// 使用 foreach 迴圈
foreach (int number in numbers)
{
    Console.WriteLine(number);
}
// 使用 for 迴圈
for (int i = 0; i < numbers.Count; i++)
{
    Console.WriteLine(numbers[i]);
}

—- 列舉 (Enum):有意義的清單,預設值為0

比喻:列舉就像是一份有意義的、有限的清單。例如,一份「星期」的清單,裡面只有七個選項:Monday、Tuesday…。你不能在裡面加入 Friday,因為它不屬於這份清單。

特性:
定義常數:它用來定義一組具名、相關的整數常數。這讓程式碼更具可讀性,你不需要記住 1 代表什麼,直接用 DayOfWeek.Monday 就可以了。
強型別:列舉是一種值型別。
有限選項:它只能表示有限個、事先定義好的值。

型態關係:
列舉是一種使用者定義的型態。你可以把它看作是一種特殊的整數型態,但它賦予了數字更明確的意義。

列舉通常有三個步驟:宣告、賦值和使用

// 宣告一個名為 DayOfWeek 的列舉
步驟一:宣告列舉,你可以把它放在類別的外面,這樣整個程式都可以使用它。
public enum DayOfWeek
{
    Sunday, // 預設值為 0
    Monday, // 預設值為 1
    Tuesday, // 預設值為 2
    Wednesday, // 預設值為 3
    Thursday, // 預設值為 4
    Friday, // 預設值為 5
    Saturday // 預設值為 6
}
# 列舉的成員預設從 0 開始,可以手動指定它們的值,例如 Monday = 1。

步驟二:為變數賦予列舉值。
// 宣告一個 today 變數,並將它設為 DayOfWeek.Wednesday
DayOfWeek today = DayOfWeek.Wednesday;

步驟三:在程式碼中使用列舉。
if (today == DayOfWeek.Wednesday)
{
  Console.WriteLine(“今天星期三,好好享受這一天吧!”);
}

// 列舉也可以被轉換成整數
int dayNumber = (int)today;
Console.WriteLine($“星期三是第 {dayNumber} 天。”); // 輸出: 星期三是第 3 天。

List<T>:最常用的彈性陣列

比喻:一個可以動態伸縮的購物袋,你可以隨時往裡面添加或移除東西。
用途:如果你需要一個可以動態增加長度的陣列,且資料有順序,List 就是首選。

// 建立一個只能裝整數的 List
List<int> scores = new List<int>();

// .Add():新增元素
scores.Add(90);
scores.Add(85);
scores.Add(78);

// 索引修改元素
scores[0] = 100; // 將第一個元素的值改為 100

// .Count:取得元素數量
Console.WriteLine($“列表中有 {scores.Count} 個分數。”); // 輸出: 3

// 用索引存取
int firstScore = scores[0];
Console.WriteLine($“第一個分數是:{firstScore}”); // 輸出: 100

// .Remove():移除元素
scores.Remove(85); // 移除第一個 85

// .RemoveAt():移除索引的元素
scores.RemoveAt(0); // 移除索引為 0 的元素 100

// foreach:遍歷集合中的每一個元素
foreach (int score in scores)
{
    Console.WriteLine($“分數為: {score}”); // 輸出: 78
}

// for 迴圈搭配索引來遍歷
for (int i = 0; i < scores.Count; i++)
{
    Console.WriteLine($“第 {i} 筆資料為 {scores[i]}。”); // 輸出: 第 0 筆資料為 78。
}

Dictionary<TKey, TValue>:智慧型字典

比喻:一本可以用「關鍵字」(Key)來快速查「解釋」(Value)的字典。
用途:如果你需要用一個唯一的識別碼來快速存取資料,Dictionary 就是最好的選擇。

// 建立一個 Dictionary,鍵(key) 是 string,值(value) 是 int
Dictionary<string, int> studentScores = new Dictionary<string, int>();

// .Add():新增鍵值對
studentScores.Add(“John”, 95);
studentScores.Add(“Mary”, 88);

// 用鍵來存取值,非常快速
int johnScore = studentScores[“John”];
Console.WriteLine($“John 的分數是:{johnScore}”);

// 索引修改元素
studentScores[“John”] = 100; // 將 “John” 的分數改為 100

// Remove():移除資料
studentScores.Remove(“Mary”); // 移除鍵為 “Mary” 的項目

// .ContainsKey():檢查鍵是否存在
if (studentScores.ContainsKey(“Mary”))
{
    Console.WriteLine(“列表中有 Mary 的分數。”);
   // 不會執行,因為 “Mary” 被移除了
}

// foreach:遍歷集合中的每一個元素
foreach (var student in studentScores)
{
    Console.WriteLine($“姓名: {student.Key}, 分數: {student.Value}”);
}
// 輸出:
// 95
// 姓名: John, 分數: 100

HashSet<T>:不重複的清單

比喻:一個篩選器,它只會放不重複的東西。
用途:當你需要儲存一組不重複的資料,並且需要快速檢查某個項目是否存在時。

// 建立一個只能裝字串的 HashSet

HashSet<string> uniqueNames = new HashSet<string>();
uniqueNames.Add(“Apple”);
uniqueNames.Add(“Banana”);
uniqueNames.Add(“Apple”); // 這裡不會被新增,因為 “Apple” 已存在

// .Count:只計算不重複的數量
Console.WriteLine($“列表中有 {uniqueNames.Count} 個名字。”);

// .Remove()
names.Remove(“Apple”); // 移除一個元素

// .Contains():檢查是否存在,非常快速
if (uniqueNames.Contains(“Banana”))
{
    Console.WriteLine(“列表中有 Banana。”);
}

foreach (string name in uniqueNames)
{
    Console.WriteLine($“現在有的水果有: {name}”);
}
// 輸出:
// 2
// 列表中有 Banana。
// 現在有的水果有: Banana

Stack<T> stack (堆疊):

比喻:Stack<T> 就像一疊疊起來的盤子。你只能從最上面放盤子,也只能從最上面拿盤子。
核心原則:後進先出 (LIFO – Last-In, First-Out)。最後放進去的,會是第一個被拿出來的。

常用方法

stack.Push(T item):
用途:將一個新項目放到堆疊的最頂端。
比喻:把一個新盤子疊到盤子堆的最上面。

stack.Pop():
用途:將堆疊最頂端的項目移除並回傳。
比喻:從盤子堆最上面拿走一個盤子。

stack.Peek():
用途:查看堆疊最頂端的項目,但不移除它。
比喻:查看盤子堆最上面的盤子是什麼,但不動它。

stack.Count
用途:取得堆疊中項目的總數。

stack.Contains():
用途:檢查是否包含某個元素,常用於搭配 if 設立條件

Queue<T> queue (佇列):

比喻:Queue<T> 就像人們在排隊。最先排進隊伍的,會是第一個被服務的;新來的人,則只能排在隊伍的最後面。

核心原則:先進先出 (FIFO – First-In, First-Out)。最先放進去的,會是第一個被拿出來的。

常用方法

queue.Enqueue(T item):
用途:將一個新項目放到佇列的尾端
比喻:讓一個人排到隊伍的最後面。

queue.Dequeue():
用途:將佇列最前端的項目移除並回傳
比喻:讓排在最前面的人離開隊伍。

queue.Peek():
用途:查看佇列最前端的項目,但不移除它。
比喻:查看隊伍最前面的人是誰,但不動他。

queue.Count
用途:取得佇列中項目的總數

queue.Contains():
用途:檢查是否包含某個元素,常用於搭配 if 設立條件

File 類別(小檔案):文件管理的靜態工具箱可以不用建立物件就能直接使用。

# 不用擔心 Close() using 的問題。
# 無法進行精細的控制,例如逐行讀取、讀取特定字元,或在讀取過程中暫停。

File.ReadAllText():一次性讀取整個文字檔,回傳一個 string。
string content = File.ReadAllText(“my_file.txt”);

File.ReadAllLines():一次性讀取所有文字行,回傳一個 string[]。
string[] lines = File.ReadAllLines(“my_file.txt”);

File.WriteAllText():一次性將整個字串寫入檔案,如果檔案已存在,會覆蓋舊內容。
File.WriteAllText(“output.txt”, “這是一段新的文字。”);

File.AppendAllText():將整個字串寫入檔案,不會自動換行,如果要換行,要在字串中手動加上 \n 或 \r\n。不會覆蓋舊內容。
File.AppendAllText(“log.txt”, “新的日誌訊息”);

File.AppendAllLines():這個方法會將一個字串集合(例如 string[] 或 List<string>)追加到檔案的末尾。
特性:它會遍歷集合中的每一個字串,並在寫入每個字串後,自動添加一個換行符號。

// 檢查檔案是否存在
bool exists = File.Exists(“my_file.txt”);

// 複製檔案
File.Copy(“source.txt”, “destination.txt”);

// 刪除檔案
File.Delete(“old_file.txt”);

# BinaryReader 類別:
用來讀取二進位檔案,例如圖片、音訊、影片等非文字檔案。
它會以位元組(Byte)的形式讀取資料,而不是文字。

StreamReader 資料讀取(大檔案) & StreamWriter 資料修改

StreamReader 類別:

ReadLine():讀取一行文字,並回傳一個 string。當讀到檔案末尾時,會回傳 null。
ReadToEnd():一次性讀取所有的文字,直到檔案結尾,並回傳一個包含所有文字的 string。
Read():讀取一個字元,並回傳它的整數值(ASCII 碼)。

# 要先建立StreamReader物件
# using確保檔案資源在使用後能夠自動關閉(就像python的with)。
# 若是不使用using要使用reader.Close()來關閉檔案。

// 假設 myfile.txt 的內容是:
// Line 1
// Line 2

while 迴圈一行一行讀取

using (StreamReader reader = new StreamReader(“myfile.txt”, Encoding.GetEncoding(“utf8”)))
{
    string line;
    // 當 ReadLine() 回傳的內容不是 null 時,迴圈會一直執行
    while ((line = reader.ReadLine()) != null)
  {
        Console.WriteLine(line);
  }
}
// 輸出:
// Line 1
// Line 2

一次性讀取所有內容
using (StreamReader reader = new StreamReader(“myfile.txt”))
{
    string content = reader.ReadToEnd();
    Console.WriteLine(content);
}
// 輸出:
// Line 1
// Line 2

StreamWriter類別:

Write()寫入內容,不換行。
WriteLine():寫入內容,並在最後換行。
Append():在檔案的末尾添加內容。

using (StreamWriter writer = new StreamWriter("output.txt"))
{
    writer.WriteLine("Hello, World!");
    writer.Write("這是一段測試文字。");
    writer.Append("這是在最後加上的文字。");
}
// 輸出:
// Hello, World!
// 這是一段測試文字。
// 這是在最後加上的文字。

—–結構 (Struct):輕量級的打包工具,預設值為所有成員的預設值

比喻:結構就像是一個輕量級的萬用包,你可以把不同型態的東西打包在一起,變成一個新的「物品」。
例如,一個 Point 結構,可以同時包含一個 int x 和一個 int y,資料不允許為 null。

特性:
值型別:結構是值型別。這代表當你傳遞結構時,它會複製一份新的副本,而不是傳遞記憶體位址。
多種型態:結構可以包含不同型態的成員(變數、方法)。
效能高:結構是一種值型別。這意味著它在記憶體中直接儲存在「堆疊(Stack)」上,而不是「堆積(Heap)」上。

型態關係:
結構也是一種使用者定義的型態。你可以把它看成是一個用來組織和打包相關資料的藍圖,它本身就是一個新的型態。

1. 宣告結構,結構裡面可以包含屬性(Property)和方法(Method),就像類別一樣。

// 宣告一個名為 Point 的結構,用來儲存座標
public struct Point
{
  // 結構的屬性
    public double X;
    public double Y;
    // 結構的建構子,用來初始化屬性
    public Point(double x, double y)
    {
      X = x;
      Y = y;
    }
}

2. 實例化結構
// 使用建構子來建立一個 Point 結構
Point myPoint = new Point(10.5, 20.3);

// 或者直接宣告,屬性會被賦予預設值 (0.0)
Point anotherPoint;

3. 存取結構的成員,使用點運算子 . 來存取結構裡面的屬性。
// 存取 myPoint 的 X 和 Y 屬性
Console.WriteLine($“我的點座標是:({myPoint.X}, {myPoint.Y})”);
// 輸出: 我的點座標是:(10.5, 20.3)

// 也可以直接修改結構的屬性
myPoint.X = 50.0;
Console.WriteLine($“新的 X 座標是:{myPoint.X}”);
// 輸出: 新的 X 座標是:50

static 類別

static 類別:只能當作工具箱的類別,可以把 static 類別想像成一個「工具箱」。

比喻:這個工具箱被固定在牆上,你不需要去買一個新的工具箱(物件),就可以直接從上面拿工具來用。
用法:static 類別只能包含 static 成員(方法、屬性、欄位),它不能被實例化,也就是你不能用 new 關鍵字來創建它的物件。

public static class MathCalculator
{
    // 靜態方法,可以直接呼叫
    public static double Add(double a, double b)
    {
        return a + b;
    }
}

// 在主程式中
// 不能 new MathCalculator(),會報錯
double result = MathCalculator.Add(10, 20); // 直接呼叫靜態方法

使用時機:
當你有一組相關的工具方法或常數,且這些方法不依賴任何物件狀態時。

static void Main(string[] args)
當你執行這個程式時,編譯器會直接找到這行。
Main:它就是程式的「起點」,它不屬於任何物件,也只需要執行程式,不需要回傳任何東西。
void:它執行完畢後不會回傳任何值(return),它的任務就是讓程式跑起來,若是有回傳值一定要有 return 和正確的傳回型別。

static 成員:不屬於任何物件的工具
static 成員可以存在於一般的(非靜態)類別中。它們就像是一個「共用的工具」,雖然放在類別裡,但它們不屬於任何一個類別裡的「物件」。

比喻:static 成員就像是汽車設計圖上的一個「通用工具」。無論你製造出紅色的車子還是藍色的車子,這個工具都是一樣的,而且你可以直接從設計圖上拿來用,不需要先製造出車子。

用法:你可以直接透過類別名稱來存取它,不需要先建立物件。

public class Car
{
    // 這是靜態屬性,紀錄所有 Car 類別被建立的次數
    public static int CarCount = 0;

    // 這是構函式
    public Car()
        {
            CarCount++; // 每建立一個物件,就讓 CarCount + 1
        }
}

// 在主程式中
Car car1 = new Car();
Car car2 = new Car();

// 直接透過類別名稱來讀取靜態屬性
Console.WriteLine($“總共有 {Car.CarCount} 輛車子被建立。”);

使用時機:
當你需要一個不依賴於物件狀態的方法或屬性時。
當你需要一個全域變數,所有物件都共用這一個變數,例如上面的 CarCount。
static void Main 就是最常見的靜態方法,因為它是程式的進入點,不需要任何物件就能啟動。

靜態建構函式 (Static Constructor)
你可以把靜態建構函式想像成一個「類別的初始化器」。它的主要任務,就是在這個類別被第一次使用時,執行一些一次性的初始化工作

比喻:它就像是電影開拍前的「場地佈置」。在任何演員(物件)進場之前,靜態建構函式會先處理好所有共用的場景道具和設定。這個佈置工作只會做一次,之後所有進來的演員都可以共用。

用途:初始化靜態成員:給靜態欄位(static field)賦予初始值。

執行一次性任務:例如讀取一個設定檔,或建立一個全域的共用資源,這些任務只需要在程式啟動時執行一次。

特性:
不能有存取修飾詞:靜態建構函式不能是 public、private 等。
不能有參數:它不能接收任何參數。
只會被呼叫一次:不管你建立多少個物件,或直接呼叫多少次靜態成員,靜態建構函式都只會被執行一次。

範例:
public class MyLogger
{
    // 靜態欄位,用來儲存日誌檔名
    private static string logFileName;
    // 靜態建構函式
    static MyLogger()
    {
      // 程式第一次使用 MyLogger 類別時,會執行這段程式碼
      // 只會執行一次
        logFileName = $“log_{DateTime.Now.ToString(“yyyyMMdd”)}.txt“;
        Console.WriteLine($“日誌系統已啟動,日誌檔名為: {logFileName}”);
  }
    public void LogMessage(string message)
    {
    Console.WriteLine($“[{logFileName}] {message}”);
    }
}

在上面的例子中,static MyLogger() 只會在程式第一次使用 MyLogger 類別時被呼叫,之後再建立多少個 MyLogger 物件,都不會再執行。

靜態擴充方法 (Static Extension Method)
它讓你可以「擴充」現有類別的功能,而不需要修改該類別的程式碼。這就像是給你的汽車裝上一個「額外的功能」,例如一個可以自動偵測路況的雷達。

比喻:它就像一個「通用工具」,但它設計得像專為某個類別量身打造的。

this 在擴充方法中的用法:

擴充方法的第一個參數必須以 this 關鍵字開頭。this 後面跟的型別,就是你要擴充的那個類別。
當你呼叫這個方法時,C# 編譯器會自動將呼叫它的物件,作為第一個參數傳入。

與直接使用方法的差異:

直接使用:需要這樣呼叫 MyTool.DoSomething(myObject)。
擴充方法:可以直接用點運算子來呼叫 myObject.DoSomething(),讓程式碼看起來更自然、更像這個類別本身就有的功能。

為什麼要使用:

程式碼更具可讀性:它讓程式碼看起來更流暢,符合物件導向的習慣。
不污染原始類別:你可以為一個你無法修改的類別(例如 C# 內建的 string 類別)添加新功能,而不需要去動原始程式碼。
提高可重用性:你可以將常用的功能封裝成擴充方法,讓所有符合條件的類別都能使用。

範例:
給 string 類別添加一個擴充方法,讓它可以檢查是否是有效的電話號碼。

// 宣告一個靜態類別來存放擴充方法
public static class StringExtensions
{
    // 擴充方法必須是靜態的
    // this 後面跟著你要擴充的型別
    public static bool IsPhoneNumber(this string s)
    {
        // 這裡可以放電話號碼的驗證邏輯
        return System.Text.RegularExpressions.Regex.IsMatch(s, @”^\d{10}$”);
    }
}
// 在 Main 方法中
string phoneNumber = “0912345678”;
string notPhoneNumber = “abc”;
// 直接使用點運算子來呼叫擴充方法

bool isPhone = phoneNumber.IsPhoneNumber(); 
Console.WriteLine(isPhone); // 輸出: True

bool isNotPhone = notPhoneNumber.IsPhoneNumber();
Console.WriteLine(isNotPhone); // 輸出: False

建構子 (Constructor):負責初始化物件,確保物件一被創造就能用。

比喻:當你買了一台新手機,你希望它一開箱就能用,而不是要你自己去組裝零件。建構子就像是這台手機的出廠設定,它確保手機一出廠就已經組裝完成,且所有基本設定都已到位。

用途:

初始化物件:為物件的屬性設定初始值。
執行一次性任務:例如建立必要的資源,確保物件可以正常運作。

如何分辨:

名稱:建構子的名稱必須和類別名稱完全一樣。
回傳值:它沒有回傳型別(連 void 都沒有)。

public class Car
{
  // 屬性
    public string Color
    public int TireCount;
  // 建構子:在建立 Car 物件時會自動被呼叫
    public Car(string color)
        {
            // 初始化 Color 屬性
            this.Color = color;
            // 初始化 TireCount 屬性,確保每台車都有 4 個輪胎
            this.TireCount = 4;
            Console.WriteLine($“一台 {this.Color} 的車子被製造了。”);
      }
}
// 在 Main 方法中
Car myCar = new Car(“紅色”); // 呼叫建構子,傳入顏色參數

存取值 (Accessors):get 和 set,負責控制物件的屬性,提供一個安全且可控的介面,來讀取和寫入資料。

比喻:get 和 set 就像是汽車的儀表板和控制面板。
get 就像儀表板上的「車速表」,你只能看(讀取),不能直接去改車速表的數字。
set 就像「油門」,你可以控制它來設定車速,但油門後面連接著複雜的引擎系統,你不需要知道它的運作細節。

用途:
get:定義如何讀取屬性值。
set:定義如何寫入屬性值,你可以在這裡加入驗證或邏輯。

get:這個區塊就像是讀取器。
它定義了當你讀取這個屬性時,會得到什麼值。你可以把它想成「給我這個屬性的值」。

set:這個區塊就像是寫入器。
它定義了當你寫入這個屬性時,要執行什麼操作。你可以把它想成「將這個值設定進去」。

class Car
{
    // 這是一個私有欄位 (private field),只能在類別內部存取
    private int speed;
    // 這是一個公開屬性 (public property)
    // 透過它來控制對 speed 的存取
    public int Speed
    {
        // get 區塊:當你讀取 Speed 時,回傳 speed 的值
        get { return speed; }
        // set 區塊:當你寫入 Speed 時,將新值存入speed
        // value 這個關鍵字代表你寫入的值
        set
        {
        // 可以在這裡加入邏輯,例如限制速度不能超過 200
            if (value > 200)
            {
                speed = 200;
            }
            else
           {
                speed = value;
          }
       }
    }
}

Car myCar = new Car(); // 創造車子物件

// 每個物件都有自己獨立的屬性,使用 set 屬性寫入值,會觸發 set 區塊的邏輯
myCar.Speed = 250; // 實際 speed 只會被設為 200

// 使用 get 屬性讀取值
Console.WriteLine($“我的車速是:{myCar.Speed} km/h”); // 輸出:我的車速是:200 km/h

代表什麼?
它代表我們將資料(speed)和操作資料的邏輯(set 區塊裡的判斷)綁定在一起,形成一個獨立的單元。外部使用者不需要知道 speed 是如何被儲存和限制的,他們只需要透過 Speed 這個屬性來與類別互動。

為什麼要這樣做?

隱藏實作細節:
1.外部程式碼不需要知道你內部是使用 speed 變數還是其他方式來儲存速度。
2.可以在不影響外部程式碼的情況下,隨時修改類別內部的實作。

保護資料:它讓我們可以對資料的寫入進行驗證和控制。

提供彈性:你可以單獨提供 get 或 set,來實現唯讀(只有 get)或唯寫(只有 set)的屬性。

—- 存取修飾詞 (Access Modifiers) —-

這些關鍵字就像是程式碼世界的「門禁卡」,它們決定了類別、方法、屬性等成員,能夠被誰、從哪裡存取。

public 開放的大門

public:開放的大門

比喻:public 就像是家裡的大門。任何人(無論是家人、鄰居還是郵差)都可以直接走到門口,看到門牌,甚至按門鈴。

存取範圍:無限制。public 成員可以從任何地方被存取,包括同一個專案、不同的專案,以及所有類別。

作用:用來公開提供類別的服務。當一個成員需要被廣泛使用時,就應該宣告為 public。

範例:

public class House
{
    public string Address = “台北市信義區”; // 任何地方都能看到這個地址
}

protected:家庭成員限定

比喻:protected 就像是家裡的「客廳」。只有家人和從這個家裡搬出去、自己也成為一個家的人(繼承類別)才能進入。鄰居或陌生人則不能。

存取範圍:只有該類別本身,以及其所有衍生類別(繼承者)可以存取。

作用:用來在繼承體系中共享成員。它允許子類別存取父類別的特定成員,同時又保護這些成員不被外部的非相關類別存取。

範例:

public class Animal // 父類別
{
    protected string species = “狗”; // 只有 Animal 及其子類別能看到
}
public class Dog : Animal // 子類別
{
    public void ShowSpecies()
    {
        Console.WriteLine($“這個物種是:{species}”); // 可以存取 protected 成員
    }
}

private:自己的秘密

比喻:private 就像是「你的個人日記」。只有你自己(該類別本身)可以閱讀和修改。即使是住在同一個家裡的家人(繼承類別),都不能看。

存取範圍:最嚴格的存取權限。private 成員只能在宣告它的類別內部被存取。

作用:用來隱藏實作細節,保護資料不被外部直接存取,這就是「封裝」的核心。

範例:
public class Person
{
    private int ssn = 12345; // 只有 Person 類別內部可以存取
}
public class Student : Person
{
    public void GetSsn()
    {
        // Console.WriteLine(ssn); // 這裡會報錯,因為 ssn 是 private
    }
}

internal:社區內通行證

比喻:internal 就像是社區的「游泳池」。只有住在這個社區裡的人(同一個專案中的類別)可以自由使用。隔壁社區的人則不能。

存取範圍:只有同一個組件 (assembly) 內的類別可以存取。在 Visual Studio 中,一個組件通常就是一個專案。

作用:用來在同一個專案內共享成員,同時又保護這些成員不被其他專案使用。這對於模組化程式碼非常有用。

範例:

// 在 ProjectA 專案中
public class Car
{
    internal string EngineType = “V8引擎”; // 只有 ProjectA 內的類別可以存取
}
// 在 ProjectB 專案中
// new Car().EngineType; 會報錯,因為 EngineType 是 internal

protected internal:社區內親友限定

比喻:protected internal 就像是「社區內的家庭派對」。你可以邀請所有社區裡的鄰居(同一個專案內的類別),也可以邀請從你家搬出去的親友(衍生類別),即使他們不住在這個社區也行。

存取範圍:protected 或 internal 的聯集。它比 protected 或 internal 的範圍都更廣。

作用:用來在繼承體系中共享成員,同時也允許同專案內的類別存取。

範例:

// 在 ProjectA 專案中
public class Family
{
    protected internal string FamilySecret = “食譜”;
    // ProjectA 內都可存取,或繼承 Family 的類別也行

}
// 在 ProjectB 專案中
public class MyFamily : Family
{
    public void GetSecret()
    {
        Console.WriteLine($“家庭食譜:{FamilySecret}”);
        // 即使在不同專案,因為是繼承,所以可以存取
    }
}

private protected:自己家裡的私密區域

比喻:private protected 就像是「你家臥室裡的保險箱」。只有你自己(該類別本身)和從你家繼承來的直系親屬(衍生類別),而且還必須在同一個屋簷下(同一個專案中),才能打開它。

取範圍:protected 和 private 的交集。它比 protected 或 internal 的範圍都更窄。

作用:這是一種很罕見的存取修飾詞,用來將成員的存取權限限制在同一個組件內的繼承體系中。

範例:

// 在 ProjectA 專案中
public class Father
{
    private protected string password = “123”;
   // 只有 Father 或在 ProjectA 內繼承 Father 的子類別可以存取

}

public class Son : Father
{
    public void ShowPassword()
    {
        Console.WriteLine(password); // 可以存取,因為同專案且是繼承
    }
}

// 在 ProjectB 專案中
public class Stranger : Father
{
    public void ShowPassword()
    {
        Console.WriteLine(password); // 這裡會報錯,因為不在同一個專案
    }
}

抽象(Abstraction)是一種「由上而下」的設計思維,它要求你在開始寫程式之前,先思考事物的共通點和本質,並將不重要的細節忽略。

繼承(Inheritance)是一種「由下而上」的具體實現,它是指一個類別(子類別)從另一個類別(父類別)那裡繼承了它的屬性(Properties)和方法(Methods)。

抽象是設計的起點定義了規範,繼承是實現的過程遵守規範

abstract class Animal // 建立抽象類別
{
    // 抽象方法,沒有實作內容,所以一定要建立實作的方法
    public abstract void MakeSound();
    public abstract void Move();
}


class Dog : Animal // Dog 繼承了 Animal,並實作 (override) 了它的方法
{
    public override void MakeSound()
    {
        Console.WriteLine(“汪汪!”);
    }
    public override void Move()
    {
        Console.WriteLine(“用四隻腳奔跑。”);
    }
}

ref 的用法:傳遞「地址」而非「內容」
想像一下,你有一個文件櫃,裡面放了一份文件。

一般的方法呼叫(傳值)這就像是你把這份文件的影本交給朋友。
朋友在影本上做了任何修改,都不會影響到你文件櫃裡的原件,這就是「Call By Value」(傳值)。

使用 ref 的方法呼叫(傳參考):這就像是你把文件櫃的鑰匙和地址交給朋友。
朋友拿到鑰匙和地址後,可以直接去你的文件櫃裡找到原件,並在上面直接做修改。
當朋友還給你時,文件櫃裡的原件已經被改動了,這就是「Call By Reference」(傳參考),也就是 ref 的核心概念。

// Call By Value (傳值)
static void AddByValue(int number)
{
    number = number + 10;
    Console.WriteLine($“在方法內:number = {number}”);
}

// Call By Reference (傳參考)
static void AddByRef(ref int number)
{
    number = number + 10;
    Console.WriteLine($“在方法內:number = {number}”);
}
// 主程式
static void Main(string[] args)
{
    int originalNumber = 5;

    // 1. 使用傳值呼叫
    AddByValue(originalNumber);
    Console.WriteLine($“傳值呼叫後:originalNumber = {originalNumber}”);
    // 輸出: 傳值呼叫後:originalNumber = 5

    // 2. 使用傳參考呼叫
    AddByRef(ref originalNumber);
    Console.WriteLine($“傳參考呼叫後:originalNumber = {originalNumber}”);
    // 輸出: 傳參考呼叫後:originalNumber = 15
}

使用時機:需要在一個方法內部直接修改傳入的變數,並讓這個修改保留到方法外部時。

例如,當用到 Swap 方法來交換兩個變數的值,這時就必須使用 ref。

當你需要從一個方法中回傳多個值時,ref 也是一個選擇。

為什麼要使用:

直接修改:可以避開「函式只回傳一個值」的限制,直接修改傳入的變數。
效能考量:對於大型的結構(Structs),傳遞 ref 比複製整個結構的內容更有效率,因為你只需要傳遞一個記憶體位址。

使用 ref 來回傳多個值

static void CalculateStats(int[] numbers, ref int sum, ref double average)
{
    sum = 0;
    for (int i = 0; i < numbers.Length; i++)
    {
        sum += numbers[i];
    }
    average = (double)sum / numbers.Length;
}

static void Main(string[] args)
{
    int[] myNumbers = { 1, 2, 3, 4, 5 };
    int totalSum = 0;
    double avg = 0.0;
   
    CalculateStats(myNumbers, ref totalSum, ref avg);
    Console.WriteLine($“總和:{totalSum}”);
    Console.WriteLine($“平均:{avg}”);
}
 // 總和:15 // 平均:3

namespace 命名空間用處就像將相同名稱的資料存在不同的資料夾一樣,當需要只用其中一個的時候就只需要從存放的那邊取用就好。

兩個步驟:宣告和引用

1. 宣告:使用 namespace 關鍵字來宣告一個命名空間,並將程式碼放在空間內。就像是幫程式碼放入一個專屬的資料夾(地址),即使名稱都相同,但是因為地址不同所以不會有衝突。

// 宣告一個名為 “MyProject.Models” 的命名空間
namespace MyProject.Models
{
  // 在這個命名空間內的類別
    public class User
    {
        // …
    }
}

// 宣告另一個名為 “MyProject.Services” 的命名空間
namespace MyProject.Services
{
    // 在這個命名空間內的類別
    public class UserService
    {
        // …
    }
}

2.引用:就像是找到那個資料夾(引用命名名稱),然後可以透過using使用裡面的成員
using:可以用來簡化程式碼,如果沒有 using,就必須把整個名稱都寫出來,例如 MyProject.Services.UserService

// 引用 MyProject.Models 命名空間
using MyProject.Models;

// 在程式中使用它
namespace MyProject
{
    class Program
    {
        static void Main(string[] args)
        {
            // 因為引用了 MyProject.Models,所以可以直接使用User 類別
            User user = new User();
        }
    }
}

IComparable 介面

如果自定義類別想要使用排序時呢?那麼就需要實作 IComparable 介面。

ompareTo 方法:是 IComparable 介面中唯一定義好的方法名稱。
# 根據回傳值來判斷大小關係,若是字串則是利用ASCII 或 Unicode 值來進行比較

回傳 負數:
表示「我比你小」。

回傳 0:表示「我跟你一樣大」。
回傳 正數:表示「我比你大」。

// 1. 實作 IComparable<T> 介面

public class Person : IComparable<Person>
{
  // public string Name { get; set; } 寫法稱為自動實作屬性 (Auto-Implemented Property)」
  // 建立一個隱藏的私有欄位,例如 private string <Name>k__BackingField;。
  // 將 get 和 set 區塊的邏輯,直接指向這個隱藏的欄位。
    public string Name { get; set; } // 屬性Name
    public int Age { get; set; } // 屬性Age

// 2.實作 CompareTo 方法,定義比較規則

public int CompareTo(Person other)
{
   // 按照 Age 來比較
  // this.Age 會自動呼叫 Age 屬性的 get 區塊,來取得 Age 的值
    return this.Age.CompareTo(other.Age);
  }
}

// 3.使用 List.Sort() 方法來排序 Person 物件(會遵守IComparable的排序規定)
List<Person> people = new List<Person>
{
    new Person { Name = “Bob”, Age = 30 },
    new Person { Name = “Alice”, Age = 25 },
};

people.Sort(); // 程式會自動呼叫 CompareTo 方法來排序

foreach (var person in people)
{
    Console.WriteLine($“{person.Name} 的年齡是 {person.Age}”);
}

// 輸出會是:
// Alice 的年齡是 25
// Bob 的年齡是 30

# 執行寫入 (new Person { Name = “Bob”, Age = 30 }) 時,set會將數值寫入到隱藏的私有欄位中當使用到Sort()時,會自動呼叫CompareTo方法去比較和排序,然後foreach迴圈要讀取person時,get會讀取隱藏私有欄位的數值

迭代器IEnumerable 方法

用途:IEnumerable 讓你可以用 foreach 迴圈來遍歷一個集合,而不用去管這個集合底層是陣列、列表還是字典。

使用時機:當你需要對一個集合進行查詢、過濾、排序或轉換,但又不想修改原始集合時。
延遲執行 (Deferred Execution): 使用Where() 和 Select()時,並不會立即執行,而是在你真正需要結果(例如在 foreach 迴圈中)時,才去遍歷資料。這可以節省大量的效能。

// 建立一個 List 集合,它實作了 IEnumerable<int> 介面

List<int> numbers = new List<int> { 10, 20, 30, 40, 50 };

Where():過濾資料

// 找出所有大於 25 的數字

IEnumerable<int> largeNumbers = numbers.Where(n => n > 25);
Console.WriteLine(“大於 25 的數字有:”);

foreach (int num in largeNumbers)
{
    Console.Write($“{num} “); // 輸出: 30 40 50
}

Select():轉換資料

// 將每個數字都乘以 2

IEnumerable<int> multipliedNumbers = numbers.Select(n => n * 2);
Console.WriteLine(“乘以 2 後的數字有:”);

foreach (int num in multipliedNumbers)
{
    Console.Write($“{num}  “); // 輸出: 20 40 60 80 100
}

Take():取前 n 筆資料

// 只取前 3 個數字
IEnumerable<int> firstThree = numbers.Take(3);
Console.WriteLine(“前 3 個數字有:”);

foreach (int num in firstThree)
{
    Console.Write($“{num} “); // 輸出: 10 20 30
}

yield :它讓你可以寫一個方法,這個方法在被呼叫時,並不會立即執行完畢,而是像一個暫停/播放的機器,每次只產生一個結果,然後暫停,等待下一次呼叫。
回傳型別:只要方法的回傳型別是 IEnumerable、IEnumerator、IAsyncEnumerable 等任何可被迭代的介面,你就可以在裡面使用 yield。

Lambda 表達式 (Expression Lambda)

語法:參數 => 表達式
用途:通常用於回傳一個值。

// 宣告一個委派 (Delegate),它是一個可以指向函式的方法指標
Func<int, bool> isEven = (number) => number % 2 == 0;

// 呼叫這個 Lambda 表達式
Console.WriteLine(isEven(4)); // 輸出: True
Console.WriteLine(isEven(5)); // 輸出: False

Lambda 陳述式 (Statement Lambda)

語法:參數 => { 程式碼區塊 }
用途:用於執行一個或多個操作。

// 宣告一個委派,它沒有回傳值
Action<int> printSquare = (number) =>
{
    int square = number * number;
    Console.WriteLine($“數字 {number} 的平方是 {square}”);
};

// 呼叫這個 Lambda 表達式
printSquare(5); // 輸出: 數字 5 的平方是 25

Action「只執行指令、不回傳結果」

# Action 指向的方法沒有回傳值(void)
public static void PrintMessage(string message)
{
    Console.WriteLine(message);
}

// Action<string> 宣告了一個可以指向「接收一個 string 參數,沒有回傳值」的方法
Action<string> myAction = PrintMessage;

// 呼叫這個委派
myAction(“Hello, World!”);

Func「執行指令,並回傳結果」

# Func 指向的方法必須有回傳值(void 以外的任何型別)。
public static int Add(int a, int b)
{
    return a + b;
}

// Func<int, int, int> 宣告了一個可以指向「接收兩個 int 參數,回傳一個 int 值」的方法
// 最後一個 int 是回傳值的型別
Func<int, int, int> myFunc = Add;

// 呼叫這個委派
int result = myFunc(10, 20);

Console.WriteLine(result); // 輸出: 30

Predicate「只負責判斷真假」

# Predicate 指向的方法回傳值固定為 bool。
public static bool IsEven(int number)
{
    return number % 2 == 0;
}

// Predicate<int> 宣告了一個可以指向「接收一個 int 參數,回傳 bool 值」的方法
Predicate<int> myPredicate = IsEven;

// 呼叫這個委派
bool isFourEven = myPredicate(4);

Console.WriteLine(isFourEven); // 輸出: True

過濾資料 (Filtering)

Where():選出符合特定條件的資料
List<int> scores = new List<int> { 85, 60, 92, 45, 75 };

// 找出大於等於80的分數
var passingScores = scores.Where(s => s >= 80);

// 找出索引為偶數的成績
var evenIndexScores = scores.Where((s,index) => index % 2 ==0 );

foreach (int score in passingScores)
{
    Console.WriteLine(score); // 輸出:// 85 // 92
}
foreach (int even in evenIndexScores)
{
     Console.WriteLine(even); // 輸出:// 85 // 92 // 75
}

轉換資料 (Projecting)

Select():對集合中的每一個元素進行轉換,並產生一個新的集合
var students = new[]
{
    new { Name = “Alice”, Score = 90 },
    new { Name = “Bob”, Score = 80 },
    new { Name = “Charlie”, Score = 70 }
};

// 將每個物件,轉換成只包含姓名的字串
var studentNames = students.Select(s => s.Name);

// 將每個物件搭配索引值,轉換成新的字串
var indexNames = students.Select((s,index) =>$“第 {index + 1} 個名字是 {s}”

foreach (string name in studentNames)
{
    Console.WriteLine(name);
    // 輸出: // Alice // Bob // Charlie
}

foreach (num in indexNames)
{
  Console.WriteLine(num);
   // 輸出: // 第 1 個名字是 Alice // ………….
}

排序資料 (Ordering)

OrderBy():對集合中的資料進行由小到大的排序
OrderByDescending():對集合中的資料進行由大到小的排序

List<string> names = new List<string> { “Charlie”, “Alice”, “Bob” };

// 依照姓名字母順序排列
var sortedNames = names.OrderBy(n => n);
Console.WriteLine(“排序後的姓名有:”);
foreach (string name in sortedNames)
{
    Console.WriteLine(name);
}
// 輸出:
// Alice
// Bob
// Charlie

分組資料 (Grouping)

GroupBy()將集合中的資料,根據某個屬性進行分組
var employees = new[]
{
    new { Name = “Alice”, Department = “IT” },
    new { Name = “Bob”, Department = “HR” },
    new { Name = “Charlie”, Department = “IT” }
};

// 根據部門分組
var groups = employees.GroupBy(e => e.Department);
foreach (var group in groups)
{
    Console.WriteLine($“部門: {group.Key}”);
    foreach (var employee in group)
  {
        Console.WriteLine($” – {employee.Name}”);
  }
}
// 輸出:
// 部門: IT
// – Alice
// – Charlie
// 部門: HR
// – Bob

聚合運算 (Aggregating)

Count()、Sum()、Average()、Max():對集合中的數值進行各種運算。
List<int> numbers = new List<int> { 10, 20, 30, 40, 50 };

// 計算總數
int count = numbers.Count;
Console.WriteLine($“總數是: {count}”); // 輸出: 5

// 計算總和
int sum = numbers.Sum();
Console.WriteLine($“總和是: {sum}”); // 輸出: 150

// 計算平均
double average = numbers.Average();
Console.WriteLine($“平均是: {average}”); // 輸出: 30

// 計算最大值
int max = numbers.Max();
Console.WriteLine($“最大值是: {max}”); // 輸出: 50

Join 用法的四個核心參數:

var result = outerCollection.Join(
    innerCollection, // 要合併的第二個集合
    outerKeySelector, // 外部集合的連接鍵
    innerKeySelector, // 內部集合的連接鍵
    resultSelector // 合併後要產生什麼樣的結果
);

// 第一份名單:學生名單
var students = new[]
{
  new { StudentID = 1, Name = “Alice” },
  new { StudentID = 2, Name = “Bob” },
};

// 第二份名單:成績名單
var scores = new[]
{
  new { StudentID = 1, Score = 95 },
  new { StudentID = 2, Score = 88 },
};

// 使用 LINQ 的 Join 方法來合併
var studentScores = students.Join(
    scores, // 合併 scores 集合
    student => student.StudentID,
// outerKeySelector: 學生名單的連接鍵是 StudentID

    score => score.StudentID,
// innerKeySelector: 成績名單的連接鍵也是 StudentID

    (student, score) => new
// resultSelector: 合併後要產生的結果

  {
     Name = student.Name,
    Score = score.Score
  }
);

// 遍歷結果並輸出
foreach (var student in studentScores)
{
    Console.WriteLine($“姓名: {student.Name}, 分數: {student.Score}”);
}
// 輸出:
// 姓名: Alice, 分數: 95
// 姓名: Bob, 分數: 88

—- 提取資料方法

List<int> numbers = new List<int> { 10, 20, 30, 40, 50 };
List<int> emptyList = new List<int>();


Take(n) 從開頭取 n 個元素
numbers.Take(3) // 10, 20, 30

TakeLast(n) 從結尾取 n 個元素,排序依照原元素排序
numbers.TakeLast(2) // 40, 50

Skip(n) 從開頭跳過 n 個元素
numbers.Skip(2)  // 30, 40, 50

SkipLast(n) 從結尾跳過 n 個元素
numbers.SkipLast(2) // 10, 20, 30

First() 取得第一個元素,集合為空會拋出例外
numbers.First() // 10

FirstOrDefault() 取得第一個元素,若為空則回傳預設值
emptyList.FirstOrDefault() // 0

Single() 取得唯一的元素,若不是唯一會拋出例外
oneElementList.Single() // 25

SingleOrDefault() 取得唯一元素,若無則回傳預設值
emptyList.SingleOrDefault() // 0

—- 檢查集合狀態

List<int> numbers = new List<int> { 10, 20, 30, 40, 50 };
List<int> emptyList = new List<int>();

Any() 檢查集合是否包含任何元素
numbers.Any() // True

All() 檢查集合中所有元素是否都符合某條件
numbers.All(n => n > 5) // True

Contains() 檢查集合是否包含某個相同類型的元素
numbers.Contains(30) // True

jQuery + Remote 驗證
這種方法是利用 jQuery 提供的驗證套件,來自動處理表單驗證。

jQuery 驗證套件
這是 jQuery ValidationjQuery Unobtrusive Validation 這兩個套件的組合。

優點
開發快速:你只需要在 HTML 標籤上設定 required、minlength 等屬性,套件就會自動處理驗證邏輯和錯誤訊息的顯示。
程式碼簡潔:你不需要寫任何 JavaScript 程式碼來處理驗證,這能讓你的程式碼非常簡潔。
使用者體驗好:錯誤訊息會在使用者輸入時即時顯示,不需要等到按下提交按鈕後才發送請求,這讓使用者體驗更流暢。

缺點
有依賴:你的專案需要依賴 jQuery 和驗證套件,這會增加檔案大小和載入時間。
控制有限:對於驗證的邏輯,你無法像手動撰寫 JavaScript 那樣,擁有完全的控制權。

常見用法與使用時機
資料註解 (Data Annotations):這是最常見的用法。你在後端的 ViewModel 中使用 [Required]、[StringLength] 等屬性來定義驗證規則,ASP.NET Core 會自動將這些規則轉換為前端驗證所需的 HTML 屬性。
使用時機:當你處理的是 ASP.NET Core MVC 專案,並且表單數量多、驗證規則複雜時,使用這種方法能大幅提高開發效率。

Remote 驗證
Remote 驗證是一個專門的 ValidationAttribute,它的作用是在前端發送一個 AJAX 請求到後端,來驗證欄位的值是否符合要求。

優點
即時後端驗證:使用者輸入時,程式就能立即發送請求到伺服器,檢查帳號是否已經存在,而不需要等待表單提交。
使用者體驗好:驗證過程是無感的,不會導致頁面重新載入,這讓使用者體驗更順暢。

缺點
增加伺服器負擔:每一次按鍵輸入都可能發送一個請求到伺服器,這會增加伺服器的負擔。
有網路依賴:如果使用者沒有網路,遠端驗證就會失敗。

常見用法與使用時機
驗證帳號名稱:在註冊頁面中,即時檢查使用者輸入的帳號名稱是否已被使用。
驗證優惠券代碼:檢查使用者輸入的優惠券代碼是否有效。

使用時機
表單驗證 (Form Validation):當使用者填完帳號或密碼,你可以在不跳轉頁面的情況下,即時檢查這個帳號是否已經被註冊。
動態載入內容 (Dynamic Content Loading):當你滑動網頁到最底部時,自動載入下一頁的文章或商品,這就是我們常看到的「無限滾動」。
即時搜尋 (Live Search):當你在搜尋框輸入文字時,下拉選單會即時顯示相關的搜尋結果。
部分內容更新 (Partial View Updates):例如,臉書的按讚功能,你點了「讚」之後,只有那個數字會變動,整個頁面都不會動。

Asynchronous programming 非同步編程:

多個任務可以同時運行,從而提高效率和應用程式回應能力。

優點:

  • 非阻塞操作:使用者可以在後台任務(如資料提取)進行時與應用程式互動。
  • 效能提升:多個任務可以並行運行,從而實現更快的操作和更流暢的使用者體驗(對話窗不用卡著也可以回訊息)。
  • 與後端服務更好地整合:非同步程式允許同時從多個來源更有效率地傳輸資料。

缺點:

  • 並發管理:處理多個任務而不引起衝突可能很複雜,特別是當任務相互依賴時。
  • 調試困難:非同步操作會使追蹤錯誤變得更加困難,因為任務獨立於主流程運行。
  • 程式碼可讀性:由於許多操作同時運行,追蹤何時發生的情況會使程式碼維護變得複雜。

Asynchronous Processing 非同步處理

using System;
using System.Threading.Tasks;

class Program
{
   static async Task Main()
   {
       Console.WriteLine(“Starting API request…”);
       string result = await FetchDataAsync(“Single Request”);
       Console.WriteLine(result);
   }
  
static async Task<string> FetchDataAsync(string requestName)
   {
        await Task.Delay(2000); // 模擬請求延遲
       return “API request completed.”;
   }
}

Synchronous programming 同步編程任務按順序運行,並且每個任務必須完成才能開始下一個任務。

定義非同步方法

1.使用 async 關鍵字將方法標記為非同步。

public async Task GetDataAsync()
{
   // Method logic here
}

2.使用 Await 關鍵字

var data = await GetDataFromApi();

3.設定回傳類型 : 對於傳回值的方法,使用 Task<T> ;對於 void 方法,使用 Task 。

public async Task<string> GetDataAsync()
{
   var data = await GetDataFromApi();
   return data;
}

處理非同步方法中的異常

public class Program

{
   public async Task DownloadDataAsync()
   {
       try
       {
           Console.WriteLine(“Download started…”);
           throw new InvalidOperationException(“Simulated download error.”);
           await Task.Delay(3000);
           Console.WriteLine(“Download completed.”);
       }
       catch (Exception ex)
       {
           Console.WriteLine(“An error occurred: ” + ex.Message);
       }
    }

   public async Task DownloadDataAsync2()
   {
       Console.WriteLine(“Download 2 started…”);
       await Task.Delay(2000);
       Console.WriteLine(“Download 2 completed.”);
    }

   public static async Task Main(string[] args)
   {
       Program program = new Program();
       Task task1 = program.DownloadDataAsync();
       Task task2 = program.DownloadDataAsync2();
       await Task.WhenAll(task1, task2);
       Console.WriteLine(“All downloads completed.”);
   }
}

發佈留言