C#
程式設計規範
命名規則
- 程式設計符必須以字母或下劃線開頭
- 識別符號可以包含Unicode字元、十進位制數字字元、Unicode連線字元、Unicode組合字元或Unicode格式字元
可以在識別符號上使用@字首宣告與C#關鍵字匹配的識別符號。eg.@if宣告為if的識別符號
命名約定
型別名稱、名稱空間和所有公共成員使用PascalCase
- 介面名稱以I開頭,屬性型別以Attribute結尾,對變數、方法和類使用有意義的描述名稱
- 列舉型別對非標記使用單數名詞,對標記使用複數名詞,識別符號不應包含兩個連續的下劃線_字元,這些名稱保留給編譯器生成
- 清晰>簡潔,類名和方法名稱採用PascalCase,常量名包括欄位和區域性變數也是
- 對方法引數、區域性變數使用駝峰式大小寫,專用例項欄位以下劃線_開頭,其餘文字為駝峰式大小寫,靜態欄位以s_開頭
- 避免在名稱中使用縮寫或首字母,廣為人知的除外,使用遵循反向域名錶示法的名稱空間
- S用於結構,C用於類,M用於方法,v用於變數,p用於引數,r用於ref引數
編碼約定
字串
- 使用內插連線短字串
string displayName = $"{first}, {second}";
- 迴圈追加字串,尤其是大量文字,使用stringbuilder
var a = "aaaaaaaaaaaaaaaaaaa";
var sb = new StringBuilder();
foreach (var i in a)
{
sb.Append(i);
}
陣列
- 宣告行上初始化陣列時,使用簡介的語法
// 不能使用var
int[] v1 = {1, 2, 3, 4, 5};
// 顯式
var v2 = new string[]{"a, b, c, d"};
委託
使用Func<>和Action<>,而不是委託在類中定義委託方法
Action<string> a1 = x => Console.WriteLine($"x is:{x}");
Action<string, string> a2 = (x, y) => Console.WriteLine($"x is:{x}, y is {y}");
Func<string, int> f1 = x => Convert.ToInt32(x);
Func<int, int, int> f2 = (x, y) => x + y;
// 使用Func<> 或 Action<>委託定義的簽名呼叫方法
a1("string for x");
a2("string for x", "string for y");
Console.WriteLine($"The value is {f1("1")}");
Console.WriteLine($"The sum is {f2(1, 2)}");
多播委託
// 委託是一種宣告,類似於一個介面,帶有關鍵字,後面跟著返回型別和引數列表
public delegate void MyDelegate(string message);
public class Program
{
public static void Main()
{
MyDelegate printDelegate = new MyDelegate(PrintMessage);
printDelegate += PrintUpperCase;
printDelegate("Hello World");
}
public static void PrintMessage(string message)
{
Console.WriteLine(message);
}
public static void PrintUpperCase(string message)
{
Console.WriteLine(message.ToUpper());
}
}
釋放資源
// 顯示釋放資源
Font bodyStyle = new Font("Arial", 10.0f);
try
{
byte charset = bodyStyle.GdiCharSet;
}
finally
{
if (bodyStyle != null)
{
((IDisposable)bodyStyle).Dispose();
}
}
// 使用using,在離開作用域時自動呼叫Dispose方法,即使出現異常任然可以釋放掉資源
using Font normalStyle = new Font("Arial", 10.0f);
byte charset3 = normalStyle.GdiCharSet;
new關鍵字
// 建立物件時下列兩種方法等效
var user = new User();
User user2 = new();
索引
^類似於取反,..類似於python中的:,可以切片。String、Span 和 ReadOnlySpan。List 支援索引,但不支援範圍。
在陣列中獲取範圍是從初始陣列複製的陣列而不是引用的陣列,修改生成的值不會更改陣列中的值
string[] words = [
// index from start index from end
"The", // 0 ^9
"quick", // 1 ^8
"brown", // 2 ^7
];
// brown
Console.WriteLine(words[^1]);
// uick
Console.WriteLine(words[1][1..]);
// ui
Console.WriteLine(words[1][1..3]);
// brown
Console.WriteLine(words[words.Length - 1]);
Base
類比理解為Java中的extend和implement,具有幾個不同的用途,都與繼承有關
// 呼叫基類建構函式
public class DerivedClass: BaseClass{
public DerivedClass(): base(){} // 呼叫基類建構函式
public DerivedClass(): base(value){} // 呼叫且傳參
}
// 訪問基類成員
public class BaseClass{
public void PrintMessage() {
CW("...");
}
}
public class Derived: BaseClass{
public void PrintMessage() {
CW("...");
base.PrintMessage(); // 呼叫基類PrintMessage方法
}
}
// 指定基類作為強制轉換目標
public class BaseClass{}
public class Derived: BaseClass{
public void Method() {
BaseClass bc = base; // 將派生類物件轉換為基類型別
}
}
?.條件運算子
類似Java裡面的optional,左側為null則表示式為null,不為null則走後續流程,避免空指標
if pwd == auth?.pwd
??合併運算子
類似Python的海象符,如果左邊為null則返回右邊的數
string result = str ?? "default";
Using()
Using是資源管理語句,常用於需要顯式釋放的非託管資源,如檔案流、網路連線、資料庫連線等。
- 當
using
塊內的程式碼執行完畢時,無論是正常完成還是因為異常而退出,using
語句都會自動呼叫每個物件的Dispose
方法。這樣可以確保釋放
// base64編碼的字串轉換成位元組陣列
using(var mStream = new MemoryStream(Convert.FromBase64String(source)));
// 使用解密器對資料解密
using (var cryptoStream = new CryptoStream(mStream,
DesHandler.CreateDecryptor(DesHandler.Key, DesHandler.IV), CryptoStreamMode.Read));
// 建立物件讀取解密後的文字資料
using (var reader = new StreamReader(cryptoStream));
readonly
類比Java中的finall,初始化後不能修改
- 不能用於修飾靜態欄位、修飾類或介面
- 欄位或區域性變數使用使用了readonly則不需要const
// 欄位,必須在宣告時或建構函式中初始化,不能修改
public readonly int Field;
// 變數,必須在宣告時初始化,不能修改
public void Func() {
readonly int local = 10;
}
// 方法,不能包含任何可變引數,不可丟擲異常
public readonly void MyMethod() {
// pass
}
// 委託,不能更改
public readonly Action MyDelegate;
// 屬性,get訪問器必須返回一個常量,而set訪問器不能被實現
public readonly int Property{get; private set;}
out關鍵字
用於宣告的一個輸出引數,方法呼叫時透過引數透過引用傳遞,如果使用out引數則必須在方法體內為每個out引數賦值。我的理解是,就是將帶有out關鍵字的引數return出去,
public void Example(int a, out int b) {
b = a * 2;
}
int result;
Example(5, out result); // result = 10
匿名型別
將只讀資料封裝到單個物件中,而無需顯示定義一個型別
var v = new {Amount = 108, Message = 'Hello'};
通常用在查詢表示式的select子句中(LINQ),其用來初始化的屬性不能為null、匿名函式或指標型別。匿名型別是class型別,直接派生自object,且無法強制轉換為除object外的任何型別,支援採用with表示式形式的非破壞性修改,類似於Java中的Set不過只能修改已存在的屬性。匿名型別會重寫ToString方法
var apple = new {Item = "apple", Price = 1.35};
var onSale = apple with {Price=0.79};
Console.WriteLine(apple);
Console.WriteLine(onSale);
record 記錄關鍵字
類似Java中的final,預設實現不可變性(不可更改物件的任何屬性或欄位值)、Equals和GetHashCode方法及ToString方法,自動提供了建構函式、屬性的比較和字串表示的功能
- 值相等性:record 型別自動擁有一個經過最佳化的
Equals
方法,該方法比較兩個 record 例項的欄位值是否相等。 - 簡潔的宣告式語法:record 允許使用更簡潔的語法來宣告只包含資料的類。
- 不可變性:record 型別的例項一旦建立,其狀態就不能更改(除非顯式地使用可變記錄)。
- 繼承:record 可以繼承自其他 record 或 class,並且可以新增額外的成員。
以下情況考慮使用記錄:
- 定義依賴值相等性的資料模型
- 定義物件不可變型別
關係模式
string WaterState(int temp) => temp switch
{
(>32) and (<212) => "liquid",
< 32 => "solid",
> 212 => "gas",
32 => "solid/liquid transition",
212 => "liquid/gas transition",
_ => throw new ArgumentOutOfRangeException()
};
判空
str
string.IsNullOrEmpty(s); // 如果是空串判不出來
string.IsNullOrWhiteSpace(s); // 空串判的出來
List
if (myList == null || !myList.Any()){} // 集合為null或沒有元素
if (myList?.Any() == false){} // 集合為null或沒有元素
物件
if (obj == null) {} // 物件為空
if (obj is null) {} // 物件為空
Object.ReferenceEquals(obj1, obj2); // 判斷物件引用地址是否一樣
object.Equals(str1, str2); // 判斷兩物件值是否相等,需要重寫Equals方法
object DefaultObj = obj ?? new object(); // 使用??運算子提供預設值
值引用
int? myInt = null;
if (!myInt.HasValue) {}
int defaultInt = myInt ?? 0; // 使用??運算子提供預設值
主建構函式
- 初始化屬性
// 只讀情況下
public readonly struct Distance(double dx, double dy)
{
public readonly double Magnitude { get; } = Math.Sqrt(dx * dx + dy * dy);
public readonly double Direction { get; } = Math.Atan2(dy, dx);
}
// 兩種方法初始化物件效果一致
public readonly struct Distance
{
public readonly double Magnitude { get; }
public readonly double Direction { get; }
public Distance(double dx, double dy)
{
Magnitude = Math.Sqrt(dx * dx + dy * dy);
Direction = Math.Atan2(dy, dx);
}
}
// 非只讀情況下,
public struct Distance(double dx, double dy)
{
public readonly double Magnitude => Math.Sqrt(dx * dx + dy * dy);
public readonly double Direction => Math.Atan2(dy, dx);
public void Translate(double deltaX, double deltaY)
{
dx += deltaX;
dy += deltaY;
}
public Distance() : this(0,0) { }
}
LINQ
Where
從資料來源中篩選出元素
from city in cities where city.Pop is < 200 and >100 select city;
排序
OrderBy
可按升序或降序排列,例子中以Area為主,population為輔
var orderedEnumerable = from country in countries
orderby country.Area, country.Population descending select country;
ThenBy
按升序執行次要排序
Reverse
反轉集合中的元素
Join
將資料來源中元素於另一個資料來源元素進行關聯和/或合併,連線序列之後必須使用select或group語句指定儲存在輸入序列中的元素。示例關聯Category屬性與categories字串陣列中一個類別匹配的prod物件
var cateQuery = from cat in categories
join prod in products on cat equals prod.Category
select new
{
Category = cat,
Name = prod.Name
};
Let
使用let將結果儲存在新範圍變數中
from name in names select names
let firstName = name.Split(" ")[0]
select firstName;
多查詢
var query = from student in students
// 按照student.Year分組
group student by student.Year
// 為分組定義別名,後續使用別名進行分組
into studentGroup
select new
{ // 每個分組的鍵,學生的年級
Level = studentGroup.Key,
// 每個分組中,所有學生的平均成績
HighestScore = (from student2 in studentGroup select student2.ExamScores.Average()).Max()
};
查詢物件
var entity = from o in InComingOrders
where o.OrderSize > 5
select new Customer { Name = o.Name, Phone = o.Phone };
// LINQ寫法
var entity2 = InComingOrders.Where(e => e.OrderSize > 5)
.Select(e => new Customer { Name = e.Name, Phone = e.Phone });
作為資料表示式(Lambda)
結合out關鍵字返回查詢
void QueryMethod(int[] ints, out List<string> returnQ) =>
returnQ = (from i in ints where i < 4 select i.ToString()).ToList();
int[] nums = [0, 1, 2, 3, 4, 5, 6, 7];
QueryMethod(nums, out List<string> result);
foreach (var item in result)
{
Console.WriteLine(item);
}
eg
// 普通方法編寫查詢總分資料
var studentQuery1 = from student in studnets
let totalScore = student.Scores[0] + student.Scores[1] + student.Scores[2] + student.Scores[3]
select totalScore;
// 使用Linq方法編寫查詢總分資料
var studentQuery2 = studnets.Select(e => e.Scores[0] + e.Scores[1] + e.Scores[2] + e.Scores[3]);
// 統計平均分
double average = studentQuery1.Average();
// 將大於平均分的學生資料對映為物件
var query1 =
from student in studnets
let x = student.Scores[0] + student.Scores[1] +
student.Scores[2] + student.Scores[3]
where x > average
select new { id = student.ID, score = x };
// 使用Linq寫法
var query2 = studnets.Where(e => e.Scores[0] + e.Scores[1] + e.Scores[2] + e.Scores[3] > average).Select(e =>
new { id = e.ID, score = e.Scores[0] + e.Scores[1] + e.Scores[2] + e.Scores[3] });
// Linq簡潔寫法
var query3 = studnets.Select(e => new { id = e.ID, score = e.Scores[0] + e.Scores[1] + e.Scores[2] + e.Scores[3] })
.Where(e => e.score > average);
foreach (var item in query1)
{
Console.WriteLine("Student ID: {0},Score: {1}", item.id, item.score);
}
投影運算
SelectMany
多個from子句投影字串列表中每個字串中的單詞
List<string> phrases = ["an apple a day", "the quick brown fox"];
// 普通寫法
var query = from phrase in phrases from word in phrase.Split(' ') select word;
// Linq寫法
var query2 = phrases.SelectMany(e => e.Split(' '));
Zip列表壓縮元組,類似python
// An int array with 7 elements.
IEnumerable<int> numbers = [1, 2, 3, 4, 5, 6, 7];
// A char array with 6 elements.
IEnumerable<char> letters = ['A', 'B', 'C', 'D', 'E', 'F'];
foreach (var (first, second, third) in numbers.Zip(letters, emoji))
{
Console.WriteLine($"Number:{first} is zipped with letter: {second} and emoji {third}");
}
Set集合操作
去重
string[] words = ["the", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "dog"];
// 去重
var query = from word in words.Distinct() select word;
// 根據條件去重
var query2 = from word in words.DistinctBy(e => e.Length)select word;
差集
string[] words1 = ["the", "quick", "brown", "fox"];
string[] words2 = ["jumped", "over", "the", "lazy", "dog"];
// console:queik、brown、fox,輸出1在2中沒有的元素
IEnumerable<string> query = from word in words1.Except(words2) select word;
// expectBy同理,根據自定義欄位進行操作
var result = new List<Person> { new Person { Name = "Alice" }, new Person { Name = "Bob" } }
.ExceptBy(person => person.Name,
new List<Person> { new Person { Name = "Alice" }, new Person { Name = "Charlie" } });
// result 將包含 { new Person { Name = "Bob" } },因為 "Alice" 在兩個集合中都存在,而 "Bob" 和 "Charlie" 只在第一個集合中。
交集
string[] words1 = ["the", "quick", "brown", "fox"];
string[] words2 = ["jumped", "over", "the", "lazy", "dog"];
// 輸出the
IEnumerable<string> query = from word in words1.Intersect(words2) select word;
var list = words1.Intersect(words2).Select(e => e.ToUpper()).ToList();
// 透過比較名稱生成 Teacher 和 Student 的交集
(Student person in
students.IntersectBy(
teachers.Select(t => (t.First, t.Last)), s => (s.FirstName, s.LastName)))
並集
string[] words1 = ["the", "quick", "brown", "fox"];
string[] words2 = ["jumped", "over", "the", "lazy", "dog"];
// 使用UnionBy
(var person in
students.Select(s => (s.FirstName, s.LastName)).UnionBy(
teachers.Select(t => (FirstName: t.First, LastName: t.Last)), s => (s.FirstName, s.LastName)))
// 輸出:the quick brown fox jumped over lazy dog
var query = (from word in words1.Union(words2) select word).ToList();
var list = words1.Union(words2).ToList();
限定符
- All():所有
- Any():任何
- Contains():正好
IEnumerable<string> names = from student in students
where student.Scores.Contains(95)
select $"{student.FirstName} {student.LastName}: {string.Join(", ", student.Scores.Select(s => s.ToString()))}";
- Skip():跳過序列中指定位置之前的元素
- SkipWhile():基於謂詞函式跳過元素,直到元素不符合條件
- Take():獲取序列中指定位置之前的元素
- TakeWhile():同上操作
- Chunk():將序列元素拆分為指定最大大小的區塊
var resource = Enumerable.Range(0, 8);
// 012
foreach (var i in resource.Take(3)){ }
// 345678
foreach (var i in resource.Skip(3)){ }
// 012345
foreach (var i in resource.TakeWhile(e=>e<5)){ }
// 678
foreach (var i in resource.SkipWhile(e=>e<5)){ }
// 平均分塊,將資料分成三塊,123、456、78
int chunkNum = 1;
foreach (int[] chunk in Enumerable.Range(0, 8).Chunk(3))
{
Console.WriteLine($"Chunk {chunkNum++}:)");
foreach (int item in chunk)
{
Console.WriteLine($" {item}");
}
Console.WriteLine();
}
資料型別轉換
- AsEnumerable - 返回型別轉化為IEnumerable
的輸入 - AsQueryable - 泛型IEnumerable轉換為泛型IQueryable
- Cast - 集合中的元素轉換為指定型別
- OfType - 轉換為指定型別的能力篩選值
- ToArray - 集合轉換為陣列(強制執行查詢)
- ToDictionary - 根據鍵選擇器函式將元素放入 Dictionary。 此方法強制執行查詢
- ToList - 集合轉換為List
- ToLookUp - 根據鍵選擇器函式將元素放入 Lookup(一對多字典,強制執行查詢)
連表
- Join - 根據鍵選擇函式Join兩個序列並提取對 - join...in...on...equals
- GroupJoin - 根據鍵選擇器函式Join兩個序列,並對每個元素的結果匹配項分組 - join...in...on...equals...into...
單鍵
Teacher和Department匹配,TeacherId與該Teacher相匹配
var query = from department in departments
join teacher in teachers on department.TeacherID equals teacher.ID
select new
{
DepartmentName = department.Name,
TeacherName = $"{teacher.First} {teacher.Last}"
};
// Linq
var query = teachers
// 主表連線副表,parameter2、3是查詢條件
.Join(departments, teacher => teacher.ID, department => department.TeacherID,
// lambda表示式,定義連線結果,建立匿名型別物件
(teacher, department) =>new { DepartmentName = department.Name, TeacherName = $"{teacher.First} {teacher.Last}" });
組合鍵
IEnumerable<string> query =
from teacher in teachers
join student in students on new
{
FirstName = teacher.First,
LastName = teacher.Last
} equals new
{
student.FirstName,
student.LastName
}
select teacher.First + " " + teacher.Last;
// Linq寫法
IEnumerable<string> query = teachers
.Join(students,
teacher => new { FirstName = teacher.First, LastName = teacher.Last },
student => new { student.FirstName, student.LastName },
(teacher, student) => $"{teacher.First} {teacher.Last}"
);
多聯結
var query = from student in students
join department in departments on student.DepartmentID equals department.ID
join teacher in teachers on department.TeacherID equals teacher.ID
select new {
StudentName = $"{student.FirstName} {student.LastName}",
DepartmentName = department.Name,
TeacherName = $"{teacher.First} {teacher.Last}"
};
// Linq
var query = students
.Join(departments, student => student.DepartmentID, department => department.ID,
(student, department) => new { student, department })
.Join(teachers, commonDepartment => commonDepartment.department.TeacherID, teacher => teacher.ID,
(commonDepartment, teacher) => new
{
StudentName = $"{commonDepartment.student.FirstName} {commonDepartment.student.LastName}",
DepartmentName = commonDepartment.department.Name,
TeacherName = $"{teacher.First} {teacher.Last}"
});
分組
- GroupBy - 對共享通用屬性進行分組 - group...by
- ToLookup - 將元素插入基於鍵選擇器函式的Lookup(一對多字典)
demo
List<int> numbers = [35, 44, 200, 84, 3987, 4, 199, 329, 446, 208];
IEnumerable<IGrouping<int, int>> groupBy1 = from number in numbers group number by number % 2;
// Linq
IEnumerable<IGrouping<int, int>> groupBy2 = numbers.GroupBy(e => e % 2);
foreach (var i in groupBy1)
{
Console.WriteLine(i.Key == 0 ? "\nEven numbers:" : "\nOdd numbers:");
foreach (var i1 in i)
{
Console.WriteLine(i1);
}
}
值分組
var groupByFirstLetterQuery =
from student in students
let firstLetter = student.LastName[0]
group student by firstLetter;
// Linq
var groupByFirstLetterQuery = students
.GroupBy(student => student.LastName[0]);
範圍分組
static int GetPercentile(Student s)
{
double avg = s.Scores.Average();
return avg > 0 ? (int)avg / 10 : 0;
}
var groupByPercentileQuery =
from student in students
let percentile = GetPercentile(student)
group new
{
student.FirstName,
student.LastName
} by percentile into percentGroup
orderby percentGroup.Key
select percentGroup;
// Linq
var groupByPercentileQuery = students
.Select(student => new { student, percentile = GetPercentile(student) })
.GroupBy(student => student.percentile)
.Select(percentGroup => new
{
percentGroup.Key,
Students = percentGroup.Select(s => new { s.student.FirstName, s.student.LastName })
})
.OrderBy(percentGroup => percentGroup.Key);
比較分組
// 匿名型別中的屬性將成為Key成員的屬性
var groupByHighAverageQuery =
from student in students
group new
{
student.FirstName,
student.LastName
} by student.Scores.Average() > 75 into studentGroup
select studentGroup;
// Linq
var groupByHighAverageQuery = students
.GroupBy(student => student.Scores.Average() > 75)
.Select(group => new
{
group.Key,
Students = group.AsEnumerable().Select(s => new { s.FirstName, s.LastName })
});
按匿名型別分組
// 第一個鍵值是首字母,第二個鍵值是布林值,
//指定該學生再第一次考試中額得分是否超過85
var groupByCompoundKey =
from student in students
group student by new
{
FirstLetterOfLastName = student.LastName[0],
IsScoreOver85 = student.Scores[0] > 85
} into studentGroup
orderby studentGroup.Key.FirstLetterOfLastName
select studentGroup;
// LINQ
var groupByCompoundKey = students
.GroupBy(student => new
{
FirstLetterOfLastName = student.LastName[0],
IsScoreOver85 = student.Scores[0] > 85
})
.OrderBy(studentGroup => studentGroup.Key.FirstLetterOfLastName);
巢狀
var nestedGroupsQuery =
(from student in students
group student by student.Year into newGroup1)
from newGroup2 in
(from student in newGroup1
group student by student.LastName)
group newGroup2 by newGroup1.Key;
// Linq
var nestedGroupsQuery =
students
.GroupBy(student => student.Year)
.Select(newGroup1 => new
{
newGroup1.Key,
NestedGroup = newGroup1
.GroupBy(student => student.LastName)
});
非同步
核心是Task
和Task<T>
物件與關鍵字async和await支援
- I/O繫結程式碼,等待一個在
async
方法中返回Task或Task的操作 - 如果會“等待”某些內容,則選擇I/O繫結,eg.資料庫資料,使用async和await
- 對於CPU繫結程式碼,等待使用Task.Run方法在後臺執行緒啟動的操作
- 需要執行開銷大的計算且重視響應能力,則選擇CPU繫結,在另一個執行緒上使用Task.Run生成工作。如適合併發和並行,還應考慮任務並行庫