01、C#基礎概念
1.1、C#簡介
C# (讀作C Sharp)是由微軟公司開發的一種物件導向、型別安全、高效且簡單的程式語言,最初於 2000 年釋出,並隨後成為 .NET 框架的一部分。所以學習C#語言的同時,也是需要同步學習.NET框架的,不過要要注意C#與.NET的對應版本。
C#語言和Java類似,是一門簡單易用、應用廣泛的高階程式語言。結合了物件導向程式設計、事件驅動、泛型程式設計、非同步程式設計等眾多現代化程式設計概念,屬於編譯性語言。主要特點:
- 物件導向:封裝(類與物件)、繼承(類繼承、介面繼承)、多型等(類繼承、多介面繼承實現)。
- 型別安全:強型別安全,在編譯時檢測,提高程式碼可靠性。
- 互動性,易於各種語言互動,如VB、F#、C++、JavaScript、Python等。
- GC管理:自動記憶體管理,C# 採用垃圾回收機制,無需申請、釋放記憶體,減少記憶體洩漏風險。
- 開源跨平臺:.NETCore框架是開源跨平臺的,支援多種作業系統。
- 強大的標準庫,C#擁有豐富的標準類庫(.NET Framework或.NET Core),內建各種功能和工具。
- 宇宙第一開發IDE: Visual Studio 提供了強大的開發、除錯和設計工具。
.NET Framework最高支援C#語法版本是C#7.3
、.NET Standard 2.1
,可以基於該版本學習,後面的版本可以根據需要學習新增特性即可。
圖來源:C#.NET體系圖文概述
1.2、開發環境
- 執行環境:安裝.NET SDK:下載 .NET, 下載.NET Framework
- 開發環境:開發IDE工具安裝 Visual Studio ,內建很多開發套件,及多個版本的SDK。
📢 推薦安裝
Enterprise
企業版!功能最全。開發工具瞭解:《Visual Studio工具使用入門》
1.3、Hello World
using System; //引用using
namespace ConsoleApp_Net48 //申明名稱空間
{
internal class Program //定義類
{
static void Main(string[] args) //方法,控制檯入口函式
{
Console.WriteLine("Hello World!"); //控制檯列印輸出
Console.ReadLine();
}
}
}
- using 引用名稱空間資源。
- namespace 名稱空間 :一組程式碼資源(類、結構、列舉、委託等)的集合。
- class 類:定義一個類,C#中最常用的程式碼組織單元。
- 方法:特定功能的程式碼塊,有輸入和輸出(也可為空)。
02、基礎語法
C#程式碼以行為單位,(半形)分號;
結尾,花括號{ 程式碼塊 }
為一個獨立的程式碼區域。
2.1、變數申明
變數型別 變數名 = 值
,變數就是物件值的名字,就像人的名字一樣,透過變數來訪問具體的物件值。變數可以是區域性變數、引數、欄位、陣列、物件例項、委託等。
- 申明變數、賦值可以一次性,也可分開,也可以一次性申明多個變數。
- 變數的使用前必須初始化(賦值),使用未賦值的變數會引發異常。
- 同一作用域內,一個變數名只能申明一次,不可重複。
- 字串用
“雙引號”
,單個字元用'單引號'
。
也可以用
var
申明,編譯器透過值型別推斷其具體變數型別,因此申明時必須賦值,var是一個語法糖。
int age; //先申明,後賦值
age = 12;
float weight = 55.55f;
double height = 188.88d; //末尾可以不用帶d,預設就是double
var name = "sam";
var lastName = 'T';
string f1, f2, f3 = "F3"; //申明瞭3個變數,對f3賦值了
var user = new User(); //建立一個User物件例項
User user2 = new User(); //建立一個User物件例項
2.2、程式碼風格
C#程式碼的命名風格大多為駝峰命名為主,相對比較統一,不像前端那麼麻煩,HTML、CSS、JS、URL各不相同。
- 區分大小寫,字母、數字、下劃線組成,不能數字開頭,不能是關鍵字。C#中的關鍵字還是挺多的,參考 C# 關鍵字。
- 駝峰命名:
- 檔名、類名、介面、方法等都是大駝峰:
UserName
。 - 區域性變數為小駝峰:
userName
。 - 欄位:下劃線+小駝峰/大駝峰都可以
_userName
、_UserName
,或者"m_
"開頭,按照團隊規範即可。 - 常量:全大寫(下劃線分割),或者大駝峰都可以,
USER_NAME
、UserName
。
- 檔名、類名、介面、方法等都是大駝峰:
public string UserName { get => _UserName; set => UserName = value; }
public string _UserName;
public const int Max=100;
public static int MaxAge =100;
private static int _MinAge = 20;
public void Sum(int a, int b)
{
int sum = a + b;
}
2.3、註釋://
- 單行註釋:
//
開頭。 - 多行註釋:
/*
多行註釋*/
(同css) - XML註釋:
///
用於型別定義、方法、屬性、欄位等成員的XML註釋,參考:《C#文件XML註釋》
/// <summary>
/// XML註釋,計算和
/// </summary>
public void Sum(int a, int b)
{
//單行註釋
int sum = a + b;
/*
多行註釋
輸出結果
*/
Console.WriteLine(sum);
}
2.4、作用域
變數的作用域就是指變數的有效範圍,C#中的作用域可以簡單理解為 花括號{ 程式碼塊 }
的範圍,可以是類、方法、控制邏輯(for、while等),或者就一個單純的{}
。
- 一個花括號
{}
內程式碼為一個獨立的程式碼區域,有獨立的作用域,變數在該作用域內有效。 - 花括號
{}
作用域可以多級巢狀,比如類中包含方法,方法內包括控制邏輯,子作用域可以訪問父級的變數(欄位、屬性、方法、具備變數)。簡單理解就是:子級可以訪問父級的成員。
private int x = 1; //類欄位
void Main()
{
var y = 1 + x; //私有變數
if (y > 0)
{
int z = x + y + 1; //可以訪問父級成員
Console.WriteLine(z);
{
int w = x+y+z+1; //可以訪問父級成員,及父級的父級
Console.WriteLine(w);
}
}
}
📢一般情況下,變數的作用域是由程式碼的詞法環境(就是編寫程式碼的位置)來決定的,這比較容易理解。例外情況就是C#中的閉包,常見於動態函式、委託。
03、申明語句
申明變數 | 說明 |
---|---|
Type v | 申明指定型別的變數,int x ,List<int> list |
var | 隱式匿名型別var ,用var 申明變數,編譯器根據值推斷出型別變數,因此要求必須賦初始值。 |
const | 申明一個常量,申明時必須賦初始值,且不可修改 |
ref | reference 變數是引用另一個變數(稱為引用)的變數,可以看做是其別名(分身) |
void Main()
{
int x =100;
List<int> list = new List<int>();
List<int> list2 = new(); //前面已知了型別,後面可省略
int[] arr = [1,2,3]; //C#12的集合表示式,方便的建立陣列、集合
List<int> arr2 = [1,2,3];
var n = 1; //匿名型別,自動推斷型別
var list3 = new List<int>;
ref int n2 = ref n; //ref另一個變數的別名,n2、n實際指向同一個值,是等效的
const int max =100; //常量
var (name,age) = ("sam",18); //多個變數一起申明、賦值,這只是一種簡化的語法糖
(x, n) = (n, x); //還可以用該語法交換變數值,非常優雅
}
3.1、const常量
const 常量,顧名思義就是值永遠不會改變的“變數”,可用於區域性變數、欄位。比如Math.PI
,Int.MaxValue
,用於一些已知的、不會改變的值。
- 申明常量的同時必須賦初始化值,不可修改,在編譯時值會內聯到程式碼中。
- 常量只能用於C#內建的值型別、列舉,及字串。
- 常量值支援表示式,不過僅限於簡單的運算,要能在編譯時計算出確定的值。
- 列舉其實也是常量。
- 當用定義
const
欄位時,該常量欄位就和靜態欄位一樣,屬於類本身,直接使用。
const double r = 5.0;
const double rs = 2 * Pi * r;
📢 要注意常量(包括列舉)在編譯時是把值內聯到IL程式碼中的,因此如果跨程式集引用時,必須一起更新,否則就會出Bug。
3.2、ref 引用(別名/分身)
ref 關鍵字的核心點就是引用另一個變數的地址,可看做是其別名(分身),指向同一地址。作用和指標操作比較相似,int* y = &x;
,不過ref
更安全、更方便。
具體在使用上有以下一些場景:
使用場景 | 說明 |
---|---|
引用傳遞引數 | 方法呼叫時傳遞引用引數,方法內可修改引數值 ,Foo(ref int number) |
ref return | 返回一個ref 變數,public ref int Foo(ref int n){return ref n;} |
ref 變數 | 引用另一個區域性變數,ref int y = ref x |
ref 條件表示式 | ref 用在三元表示式條件? ref (true):ref (fasle) 中,返回引用 |
ref struct | 讓struct 完全分配在棧上、不能裝箱,只能用於區域性變數、引數,一些高效能的場景 |
int x = 1;
ref int y = ref x; //x、y其實同一個變數
Console.WriteLine($"{x},{y}"); //1,1
x++;
Console.WriteLine($"{x},{y}"); //2,2
y++;
Console.WriteLine($"{x},{y}"); //3,3
//換個陣列
int[] arr = new int[] { 0, 1, 2};
ref int a = ref arr[0];
a=100;
Console.WriteLine(arr); //100 1 2
ref readonly
:所指向的變數不能修改值,但可以用ref
重新分配一個reference
變數。ref
返回值:用於一個方法的返回值,返回一個變數的引用(別名)
void Main()
{
var arr = new int[] { 1, 2, 3 };
ref int f = ref GetFirst(arr);
f = 100;
Console.WriteLine(arr); //100 2 3
}
private ref int GetFirst(int[] arr)
{
return ref arr[0];
}
🔊 在某些場景使用
ref
可以避免值型別在傳遞時的複製操作,從而提高效能,不過不同場景不同,需要具體分析、經過效能測試再確定。
04、常用(控制)語句
語句 | 說明 |
---|---|
if |
條件語句,if(true){ 執行 } |
if ...else |
條件語句,if(true){} else(){} |
if ...else if ...else |
同上,中間可以接多個else if ,不過這個時候一般建議重構下,比如用你switch 模式匹配 |
switch ...case |
根據條件處理多個分支:switch (條件){ case }。case 命中後,注意break 結束,否則會繼續執行 |
while (true){} |
迴圈:條件為true就會迴圈執行 |
do while (true) |
迴圈:先執行後判斷條件 |
for 迴圈 |
迴圈:for 條件迴圈,支援多個語句逗號隔開。for(int i =0; i<max; i++) |
foreach in |
迴圈元素:foreeach(int item in items) ,實現了IEnumerable,或有無引數 GetEnumerator() |
await foreach |
foreach 的 非同步版本 |
List.ForEach () |
List<T> 自帶的迴圈執行方法,list.ForEach(s=> s.Dump()); |
break |
跳出迴圈語句,for、foreach、while、switch、do。跳出最近的語句塊,如果多層巢狀只會對最近的有效 |
continue |
繼續下一次迴圈,只是後面的程式碼不執行了,應用條件同break |
return |
結束方法/函式並返回結果(若有),注意是針對函式的。 |
goto |
跳轉語句到指定標籤,單獨標籤或者case 值,一般不建議使用,goto 可讀性不太好 |
throw |
丟擲異常,不再執行後面的程式碼 |
try.catch.finally |
異常處理,throw 丟擲一個異常 |
checked、unchecked | 對整數運算語句進行溢位檢查、不檢查,如果檢查溢位會丟擲OverflowException |
fixed | 申明指標固定一個可移動(回收)變數,防止被GC回收,在unsafe 程式碼中執行 |
stackalloc | 在堆疊上分配記憶體,int* ptr = stackalloc int[10] |
lock | 互斥鎖 Monitor 的語法糖,保障同時只有一個執行緒訪問共享資源 lock(obj){ } |
using | 引用名稱空間,釋放IDisposable , |
yield | 用於迭代器中返回一個迭代值yield return value ,或表示迭代結束yield break 。 |
📢 switch 在C#8以上的更多特性,參考後文《C#的模式匹配》
4.1、try-catch異常處理
一個標準的異常處理流程:
- try:功能程式碼,需要捕獲異常的地方。
- catch:捕獲異常,處理異常。支援多個
catch
語句,捕獲不同的異常,多個catch
按照順序執行。catch
後面可以用when
表示式新增更多篩選條件。 - finally:最後執行的程式碼,無論是否有異常發生都會執行,多用於最後的清理工作。
- throw:可以丟擲一個新的異常,也可以在
catch
直接throw;
,保留原始堆疊資訊。
try
{
//功能程式碼
throw new ArgumentException("引數name為null");
}
//用when新增更詳細的篩選條件
catch (ArgumentException e) when (e.InnerException ==null)
{
//處理異常,如記錄日誌
}
catch (Exception e)
{
//處理異常
throw; //直接throw,保留原始堆疊資訊
}
finally
{
//最後執行的程式碼,無論是否有異常發生都會執行,多用於最後的清理工作
}
📢非同步(執行緒)中的異常一般不會丟擲到呼叫執行緒(或主執行緒),只會在
await
,或獲取Task.Result
時才會被丟擲來,更多可檢視非同步程式設計相關章節。
4.2、using 的5種用法
using 在C#中有很多中用途,常用來引用名稱空間、簡化釋放資源。
using 用途 | 說明 |
---|---|
using namespace | 引用名稱空間,比較常用,基本每個類都會使用。 |
global using | 專案全域性引用,避免每個類都重複using 相同的名稱空間。 |
using 別名 | 用using 來建立名稱空間或型別的別名,簡化程式碼中的使用。 |
using static | 引入一個型別的靜態成員、巢狀型別,程式碼中直接使用引入的靜態成員。 |
using 語句 | using 語句可確保正確使用 IDisposable 例項,using(var r){} ,簡化後無需括號 |
📢 名稱空間 namespace 用於組織程式碼(作用域)的主要方式,用關鍵字
namespace
來命名,可巢狀。C#10 中可以用檔案範圍名稱空間,減少一層括號巢狀。
global using
的最佳實現是一般建立一個公共的類檔案“Usings.cs
”,專門放置專案中全域性的公共using
。- 用
using
來建立名稱空間別名,使用時需要用到運算子::
來訪問下級。 using
可建立任意型別的別名,包括陣列、泛型、元祖、指標。
global using System.Text; //全域性引用名稱空間
using System.Text; //引用名稱空間
using json = System.Text.Json.JsonSerializer; //型別別名
using NumberList = double[]; //型別別名:陣列
using Point = (int X, int Y); //型別別名:元祖ValueTuple<int, int>
using jsons = System.Text.Json; //空間別名
//namespace myspace; 效果同下,簡化寫法,可節省一對大括號
namespace myspace
{
public class Program
{
void Main()
{
json.Serialize(new Object());
jsons::JsonSerializer.Serialize(new Object()); //這用到運算子::
NumberList arr = [1,2,3];
}
}
}
📢 從
.Net
6開始,C#專案會根據專案型別隱式包含一些using
引用,比如System
、System.Text
。
using static
,引入一個型別的靜態成員、巢狀型別,程式碼中直接使用引入的靜態方法。
using static System.Math;
void Main()
{
var a = Abs(-2 * PI ); //直接使用Math下的靜態成員
}
🔸using 語句確保物件在using
語句結束時被釋放(呼叫Dispose
)。也可以直接用using
申明變數,不用大括號{}
,這是一種簡化的寫法,會在作用域(方法、語句塊)結束時釋放。
using (StreamReader reader = File.OpenText("numbers.txt"))
{
Console.WriteLine("do read...");
}
// 簡化寫法,效果和上面一樣,直接用using修飾 變數申明
using StreamReader reader2 = File.OpenText("numbers.txt");
//編譯後的程式碼:
StreamReader reader = File.OpenText ("numbers.txt");
try
{
Console.WriteLine ("do read...");
}
finally
{
if (reader != null)
{
((IDisposable)reader).Dispose ();
}
}
📢
using
語句是一種語法糖,會自動生成try...finally
程式碼。
參考資料
- C#DotNet資料導航
- C#.NET體系圖文概述—2024總結
- C# 語言文件
- 《C#8.0 In a Nutshell》
©️版權申明:版權所有@安木夕,本文內容僅供學習,歡迎指正、交流,轉載請註明出處!原文編輯地址-語雀