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