C#.Net築基-基礎知識

安木夕發表於2024-05-09

image.png


01、C#基礎概念

1.1、C#簡介

C# (讀作C Sharp)是由微軟公司開發的一種物件導向、型別安全、高效且簡單的程式語言,最初於 2000 年釋出,並隨後成為 .NET 框架的一部分。所以學習C#語言的同時,也是需要同步學習.NET框架的,不過要要注意C#與.NET的對應版本。

image.png

C#語言和Java類似,是一門簡單易用、應用廣泛的高階程式語言。結合了物件導向程式設計、事件驅動、泛型程式設計、非同步程式設計等眾多現代化程式設計概念,屬於編譯性語言。主要特點:

  • 物件導向:封裝(類與物件)、繼承(類繼承、介面繼承)、多型等(類繼承、多介面繼承實現)。
  • 型別安全:強型別安全,在編譯時檢測,提高程式碼可靠性。
  • 互動性,易於各種語言互動,如VB、F#、C++、JavaScript、Python等。
  • GC管理:自動記憶體管理,C# 採用垃圾回收機制,無需申請、釋放記憶體,減少記憶體洩漏風險。
  • 開源跨平臺:.NETCore框架是開源跨平臺的,支援多種作業系統。
  • 強大的標準庫,C#擁有豐富的標準類庫(.NET Framework或.NET Core),內建各種功能和工具。
  • 宇宙第一開發IDE: Visual Studio 提供了強大的開發、除錯和設計工具。

image.png

.NET Framework最高支援C#語法版本是C#7.3.NET Standard 2.1,可以基於該版本學習,後面的版本可以根據需要學習新增特性即可。

image.png

圖來源:C#.NET體系圖文概述

1.2、開發環境

  • 執行環境:安裝.NET SDK:下載 .NET下載.NET Framework
  • 開發環境:開發IDE工具安裝 Visual Studio ,內建很多開發套件,及多個版本的SDK。

📢 推薦安裝Enterprise 企業版!功能最全。開發工具瞭解:《Visual Studio工具使用入門

image.png

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#程式碼以行為單位,(半形)分號;結尾,花括號{ 程式碼塊 }為一個獨立的程式碼區域。

image

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_NAMEUserName
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 xList<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.PIInt.MaxValue,用於一些已知的、不會改變的值。

  • 申明常量的同時必須賦初始化值,不可修改,在編譯時值會內聯到程式碼中。
  • 常量只能用於C#內建的值型別、列舉,及字串。
  • 常量值支援表示式,不過僅限於簡單的運算,要能在編譯時計算出確定的值。
  • 列舉其實也是常量。
  • 當用定義const 欄位時,該常量欄位就和靜態欄位一樣,屬於類本身,直接使用。
const double r = 5.0;
const double rs = 2 * Pi * r;

📢 要注意常量(包括列舉)在編譯時是把值內聯到IL程式碼中的,因此如果跨程式集引用時,必須一起更新,否則就會出Bug。

image.png

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就會迴圈執行
dowhile(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 丟擲一個異常
checkedunchecked 對整數運算語句進行溢位檢查、不檢查,如果檢查溢位會丟擲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;,保留原始堆疊資訊。

image

	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];
		}
	}
}

📢 從.Net6開始,C#專案會根據專案型別隱式包含一些using引用,比如SystemSystem.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》

©️版權申明:版權所有@安木夕,本文內容僅供學習,歡迎指正、交流,轉載請註明出處!原文編輯地址-語雀