面向?qū)ο?OOP)
c語言
是面向過程。
c++
是面向過程+面向?qū)ο蟆?/p>
c#
是純粹的面向?qū)ο? 核心思想是以人的思維習(xí)慣來分析和解決問題。萬物皆對象。
面向?qū)ο箝_發(fā)步驟:
-
分析對象
- 特征
- 行為
- 關(guān)系(對象關(guān)系/類關(guān)系)
-
寫代碼:
特征–>成員變量
方法–>成員方法
-
實(shí)例化–具體對象
Note(補(bǔ)充知識(shí)):
類=模板
(類我們一般用于定義新的數(shù)據(jù)類型)
(定義好的類 = 新的數(shù)據(jù)類型,故可以用于定義對應(yīng)類型變量)
類的成員分為普通成員和靜態(tài)成員
類間關(guān)系:
泛化(Generalization):
實(shí)現(xiàn)(Realization):
關(guān)聯(lián)(Association):
聚合(Aggregation):
組合(Composition):
依賴(Dependency):
.Net(framework)和C#
兩者的關(guān)系:.Net Framework 的地位類似于 Java中的JVM.
c#語言的編譯過程:
Note:
C#的語言編譯器:
csc(c sharp compiler)
,其與.Net框架安裝在一起,即在同一個(gè)安裝路徑下。例如,在我本機(jī)的路徑是:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe
數(shù)據(jù)類型
c# partial關(guān)鍵字
在 C# 中,您可以使用 partial 關(guān)鍵字在多個(gè) .cs 文件中拆分類、結(jié)構(gòu)、方法或接口的實(shí)現(xiàn)。
編譯程序時(shí),編譯器將自動(dòng)合并來自多個(gè) .cs 文件的所有實(shí)現(xiàn)。
部分類:
示例如下:
EmployeeProps.cs
public partial class Employee
{
public int EmpId { get; set; }
public string Name { get; set; }
}
EmployeeMethods.cs
public partial class Employee
{
//constructor
public Employee(int id, string name)
{
this.EmpId = id;
this.Name = name;
}
public void DisplayEmpInfo()
{
Console.WriteLine(this.EmpId + " " this.Name);
}
}
上面,EmployeeProps.cs 包含了 Employee 類的屬性,EmployeeMethods.cs 包含了 Employee 類的所有方法。
這些將被合并編譯為一個(gè) Employee 類。
Employee
public class Employee
{
public int EmpId { get; set; }
public string Name { get; set; }
public Employee(int id, string name)
{
this.EmpId = id;
this.Name = name;
}
public void DisplayEmpInfo()
{
Console.WriteLine(this.EmpId + " " this.Name );
}
}
部分類的規(guī)則:
- 所有部分類定義必須在相同的程序集和命名空間中。
- 所有部分類必須具有相同的可訪問性,例如公共或私有等。
- 如果任何部分聲明為抽象、密封或基類型,則整個(gè)類聲明為相同類型。
- 不同的部分類必須有相同的基類型。
- Partial 修飾符只能出現(xiàn)在關(guān)鍵字 class、struct 或 interface 之前。
- 允許嵌套的部分類。
什么時(shí)候會(huì)用到部分類:
通常是一個(gè)類太大,或者有一些類我們是用代碼生成的。
然后我們自己的方法代碼不希望影響到開源軟件生成的代碼 ,即我們不希望影響到舊的代碼。這樣維護(hù)的時(shí)候很麻煩。
部分方法拆分為分部類或結(jié)構(gòu)的兩個(gè)單獨(dú)的 .cs 文件。
兩個(gè) .cs 文件之一包含方法的簽名,另一個(gè)文件可以包含這個(gè)部分方法的實(shí)現(xiàn)。且 方法的聲明和實(shí)現(xiàn)都必須有 partial 關(guān)鍵字。
EmployeeProps.cs
public partial class Employee{
public Employee(){
GenerateEmpId();
}
public Guid EmpId{get;set;}
public string name{get;set;}
partial void GenerateEmpId();
}
EmployeeMethods.cs
public partial class Employee{
partial void GenerateEmpId(){
this.EmpId = Guid.NewGuid();
}
}
部分方法的規(guī)則:
部分方法必須使用 partial 關(guān)鍵字
部分方法如果不是void的話,則必須加訪問修飾符。
部分方法可以使用 in, ref, out 來修飾參數(shù)。
部分方法是隱式私有方法,因此不能是虛擬的。
不支持virtual
部分方法可以是靜態(tài)方法。
部分方法可以是泛型
c# using關(guān)鍵字
在 C# 中,using
關(guān)鍵字有兩個(gè)主要的用途:
1.資源管理(Resource Management): using
用于在代碼塊執(zhí)行完畢后釋放資源,確保對象在離開作用域時(shí)被正確清理。這通常用于實(shí)現(xiàn) IDisposable
接口的對象,如文件流、數(shù)據(jù)庫連接等。
using (FileStream fs = new FileStream("example.txt", FileMode.Open))
{
// 使用文件流進(jìn)行操作
// 在這個(gè)代碼塊結(jié)束時(shí),F(xiàn)ileStream 會(huì)被自動(dòng)關(guān)閉和釋放資源
}
2.導(dǎo)入命名空間(Namespace Import): using
也用于導(dǎo)入命名空間,使得在代碼中可以直接使用命名空間中的類型,而不需要使用完全限定名。提高代碼的可讀性.
//導(dǎo)入命名空間
using NamespaceName;
注意:
using
關(guān)鍵字用于導(dǎo)入命名空間,但它只導(dǎo)入指定的命名空間,不會(huì)自動(dòng)導(dǎo)入該命名空間下的所有子命名空間。例如,如果你使用了
using System;
導(dǎo)入了System
命名空間,那么只有System
命名空間下的類型會(huì)被導(dǎo)入,而System.Threading
、System.IO
等子命名空間下的類型不會(huì)自動(dòng)導(dǎo)入。如果需要使用子命名空間的類型,你需要額外使用
using
導(dǎo)入相應(yīng)的子命名空間。這樣可以根據(jù)需要有選擇地導(dǎo)入所需的命名空間,而不會(huì)導(dǎo)入整個(gè)命名空間的所有內(nèi)容,從而保持代碼的簡潔性和可維護(hù)性。
c# var和dynamic關(guān)鍵字
var關(guān)鍵字
var
是 C# 中的一個(gè)關(guān)鍵字,用于在聲明變量時(shí)讓編譯器根據(jù)初始化表達(dá)式的類型來自動(dòng)推斷變量的類型。
使用 var
關(guān)鍵字可以使代碼更為簡潔,尤其是在處理復(fù)雜的類型或匿名類型時(shí)。
- 基本用法:
var number = 42; // 推斷為 int 類型
var name = "John"; // 推斷為 string 類型
var price = 19.99; // 推斷為 double 類型
- 在集合中使用:
var numbers = new List<int> { 1, 2, 3, 4, 5 };
foreach (var num in numbers)
{
// num 的類型被推斷為 int
Console.WriteLine(num);
}
- 匿名類型(Anonymous Types):
var person = new { Name = "John", Age = 30 };
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
- LINQ 查詢中的匿名類型:
var result = from p in people
where p.Age > 21
select new { p.Name, p.Age };
- 在方法中使用:
var result = Add(3, 5);
int Add(int a, int b)
{
return a + b;
}
- LINQ 方法語法查詢中的 var:
var adults = people.Where(p => p.Age >= 18).ToList();
注意事項(xiàng):
-
var
不是一種動(dòng)態(tài)類型,而是在編譯時(shí)確定類型的隱式類型。 -
在聲明變量時(shí)使用
var
時(shí),必須在同一行進(jìn)行初始化。var i = 10;
否則 C# 編譯器會(huì)報(bào)錯(cuò):必須初始化隱式類型的變量。
var i; // 編譯錯(cuò)誤: 隱式類型化的變量必須已初始化 Implicitly-typed variables must be initialized i = 100;
-
使用
var
時(shí),編譯器會(huì)根據(jù)初始化表達(dá)式的類型進(jìn)行類型推斷。 -
不允許使用一個(gè)var 定義多個(gè)變量
var i = 100, j = 200, k = 300; // Error: 隱式類型化的變量不能有多個(gè)聲明符
-
var不能用在方法的形參參數(shù)上面
void Display(var param) //編譯錯(cuò)誤 { Console.Write(param); }
-
var 可以用在for 和 foreach的循環(huán)當(dāng)中
for(var i = 0;i < 10;i++){ Console.WriteLine(i); }
-
var 還可以被用在 linq的查詢當(dāng)中
特別是在linq查詢當(dāng)中如果返回類型是匿名類型的時(shí)候,也只能用var了。
var stringList = new List<string>(){ "C# 教程", "VB.NET 教程", "Learn C++", "MVC 教程" , "Java" }; //Linq查詢語法,(跟sql很像) var result = from s in stringList where s.Contains("教程") select s;
dynamic關(guān)鍵字
在C#中,dynamic
是一種在運(yùn)行時(shí)執(zhí)行類型檢查的類型。使用 dynamic
聲明的變量可以在運(yùn)行時(shí)改變其類型,而不是在編譯時(shí)確定。這種靈活性使得 dynamic
在處理與運(yùn)行時(shí)相關(guān)的場景時(shí)非常有用。
- 基本用法:
dynamic dynamicVariable = 10;
Console.WriteLine(dynamicVariable); // 輸出 10
dynamicVariable = "Hello";
Console.WriteLine(dynamicVariable); // 輸出 Hello
在這個(gè)例子中,dynamicVariable
可以在運(yùn)行時(shí)切換為不同的類型。
- 與反射結(jié)合使用:
Assembly assembly = Assembly.LoadFile("Example.dll");
dynamic instance = assembly.CreateInstance("Example.MyClass");
// 調(diào)用 MyClass 中的方法
instance.MyMethod();
dynamic
可以與反射結(jié)合使用,使得在運(yùn)行時(shí)訪問和調(diào)用類型的成員更為方便。
- 作為方法的參數(shù)或返回類型:
public dynamic GetDynamicResult()
{
return DateTime.Now.Second % 2 == 0 ? 42 : "Hello";
}
dynamic result = GetDynamicResult();
Console.WriteLine(result); // 輸出 42 或 Hello
dynamic
可以作為方法的參數(shù)或返回類型,使得方法的行為在運(yùn)行時(shí)更加靈活。
需要注意的是,dynamic
是一種犧牲了編譯時(shí)類型檢查的靈活性,因此在使用時(shí)需要小心,并確保在運(yùn)行時(shí)不會(huì)導(dǎo)致類型錯(cuò)誤。
在大多數(shù)情況下,應(yīng)盡量使用靜態(tài)類型以便在編譯時(shí)發(fā)現(xiàn)潛在的錯(cuò)誤。
var和dynamic的對比
對于var關(guān)鍵字:
編譯時(shí)類型推斷:
var
用于在編譯時(shí)根據(jù)初始化表達(dá)式的類型推斷變量的類型。靜態(tài)類型: 一旦類型確定,變量就成為該類型的靜態(tài)類型,后續(xù)不能更改。
局部變量:
var
通常用于聲明局部變量,如方法內(nèi)的變量。不能用于聲明成員變量:
var
不能用于聲明類的成員變量,因?yàn)関ar需要初始化表達(dá)式,而類的成員變量不會(huì)初始化表達(dá)式。
對于dynamic關(guān)鍵字:
- 運(yùn)行時(shí)類型推斷:
dynamic
用于在運(yùn)行時(shí)推斷變量的類型。類型檢查是在運(yùn)行時(shí)而不是在編譯時(shí)進(jìn)行的。- 動(dòng)態(tài)類型:
dynamic
聲明的變量是動(dòng)態(tài)類型的,可以在運(yùn)行時(shí)更改其類型。- 可用于聲明成員變量:
dynamic
可以用于聲明類的成員變量,因?yàn)樗灰笤诼暶鲿r(shí)就指定類型和初始化表達(dá)式。
c# new關(guān)鍵字
c#中的new
關(guān)鍵字作用:
-
要么可以在實(shí)例化一個(gè)對象時(shí)使用,如下示例:
int[] numbers = new int[5];// 使用 new 初始化整數(shù)數(shù)組,默認(rèn)值為 0
-
要么可以隱藏基類(父類)成員時(shí)使用:
在 C# 中,
new
關(guān)鍵字用于明確表示
你正在故意隱藏父類的成員。不加
new
也可以隱藏父類的成員,但會(huì)產(chǎn)生編譯器警告,提醒你可能是無意中隱藏了父類的成員。class Parent { public void Display() { Console.WriteLine("Parent Display"); } } class Child : Parent { //不加new,編譯器會(huì)發(fā)出警告,因?yàn)?Child 類中的 Display 方法沒有使用 new 或 override 關(guān)鍵字,可能會(huì)無意隱藏了父類的成員。然而,程序仍然可以編譯和運(yùn)行。 public void Display() { Console.WriteLine("Child Display"); } } ---------------------------------------------------- class Parent { public void Display() { Console.WriteLine("Parent Display"); } } class Child : Parent { //使用了new //在這種情況下,使用了 new 關(guān)鍵字,明確表示是有意隱藏父類的成員。 //編譯器將不再發(fā)出警告,并且程序可以正常編譯和運(yùn)行。 public new void Display() { Console.WriteLine("Child Display"); } }
c# out關(guān)鍵字
使用 out
關(guān)鍵字的主要原因是為了允許方法返回多個(gè)值。在 C# 中,方法只能返回一個(gè)值。
但是,如果你需要從方法中返回多個(gè)值,你可以將其中一個(gè)或多個(gè)值作為 out
參數(shù)傳遞給該方法。
總之,out
關(guān)鍵字的作用是允許方法返回多個(gè)值,因?yàn)樗试S在方法內(nèi)部修改參數(shù)的值,并在方法完成后將修改后的值傳遞回調(diào)用方。
以下是一個(gè)簡單的示例,展示了如何使用 out
關(guān)鍵字:
public void Calculate(int input, out int output)
{
// 在這里進(jìn)行計(jì)算
output = 42; // 將計(jì)算結(jié)果保存在輸出參數(shù)中
}
// 調(diào)用 Calculate 方法
int result;
Calculate(10, out result);
Console.WriteLine(result); // 輸出 42
//在上面的示例中,Calculate 方法接受一個(gè)整數(shù)作為輸入,并將計(jì)算結(jié)果保存在 output 參數(shù)中。
//在調(diào)用該方法時(shí),我們將一個(gè)變量作為 out 參數(shù)傳遞給該方法,以便在方法完成后獲取計(jì)算結(jié)果。
我的理解: 使用out關(guān)鍵字后,傳遞進(jìn)去的實(shí)參經(jīng)過方法內(nèi)部修改后也會(huì)更新保存到這個(gè)實(shí)參中。
//這個(gè)例子中,CalculateSumAndDifference 方法接受兩個(gè)整數(shù) a 和 b,并使用兩個(gè) out 參數(shù) sum 和 difference 返回它們的和和差。在調(diào)用這個(gè)方法時(shí),傳遞給方法的 sum 和 difference 參數(shù)實(shí)際上是用于接收輸出的變量。
//通過這種方式,你可以使用 out 參數(shù)在一個(gè)方法中返回多個(gè)值。
//注意,out 參數(shù)在調(diào)用方法前不需要初始化,因?yàn)榉椒〞?huì)在內(nèi)部為這些參數(shù)賦值。
class Program
{
static void Main()
{
int inputA = 5;
int inputB = 3;
int sum;
int difference;
//調(diào)用時(shí)方法要傳遞out
CalculateSumAndDifference(inputA, inputB, out sum, out difference);
Console.WriteLine($"Sum: {sum}, Difference: {difference}");
}
//聲明時(shí)方法也要傳遞out
static void CalculateSumAndDifference(int a, int b, out int sum, out int difference)
{
sum = a + b;
difference = a - b;
}
}
屬性
- 在
c#
中,類中的屬性和方法, 若沒有定義public等權(quán)限修飾符時(shí),默認(rèn)是private,只能在類內(nèi)進(jìn)行訪問。 - 若要跨類訪問,可以修改為public的權(quán)限修飾符。如:
public float size;
但直接修改為public屬性的缺點(diǎn)是外部的類可以任意修改一個(gè)類的屬性值,所以遇到這種情況,在Java中的解決方案是外部類利用類內(nèi)的getter和setter方法去訪問和修改類的成員變量,同時(shí)類內(nèi)的getter和setter方法也可在方法中進(jìn)行一定的邏輯處理和限制,從而達(dá)到不讓外部類隨便改類內(nèi)字段值的目的。 - 在
C#
中,則是利用訪問器/屬性
解決這個(gè)問題,其思想也類似于Java的POJO類中的getter和setter。
常規(guī)屬性
什么是常規(guī)屬性?
答: 在類中,為了安全起見,一般把數(shù)據(jù)成員設(shè)置為private
即先定義一個(gè)private
的私有的字段,然后再為這個(gè)私有字段封裝一個(gè)public
公開的屬性,在屬性中實(shí)現(xiàn) get 和 set 兩個(gè)方法,這種方式叫做常規(guī)屬性
注意: private權(quán)限的變量命名一般首字母小寫。
? 而public權(quán)限的變量命令一般首字母大寫。
常規(guī)屬性有什么作用?
答:C#
中常規(guī)屬性用于對類中成員變量的取值或賦值進(jìn)行限制過濾。
示例如下:
class demo{
//先定義一個(gè)私有的字段
private float size;
//然后再為這個(gè)私有字段封裝一個(gè)公開的屬性,在屬性中實(shí)現(xiàn) get 和 set 兩個(gè)方法
//常規(guī)屬性用于對類中成員變量的取值或賦值進(jìn)行限制過濾。**
public float Size{
get{return this.size};
set{this.size = value};
}
}
//上面的示例中,我們將`size`這個(gè)字段的權(quán)限修飾符由`public`改為`private`,同時(shí)定義了一個(gè)屬性代碼`public float Size{}`,里面 的`get{}`代碼段用于返回屬性值,其中的`set{}`代碼段用于設(shè)置值,且其中的`value`是保留字,其代表了外界賦給這個(gè)字段的值
自動(dòng)屬性
什么情況下使用自動(dòng)屬性?
在某些情況下,屬性的 get 和 set 只是完成字段的取值和賦值操作,而不包含任何附加的邏輯代碼,這個(gè)時(shí)候可以使用自動(dòng)屬性。
什么是自動(dòng)屬性?
答: 是微軟提供的一種快速簡潔建立屬性的方式??偟膩碚f,是一種相比常規(guī)屬性而言更加簡潔的寫法。
如:public int Age { get; set; }
其相當(dāng)于get{}代碼段里面默認(rèn)寫好了return某個(gè)字段,set{}代碼段里面默認(rèn)寫好了this.xx字段=value。
同時(shí),我們使用自動(dòng)屬性的時(shí)候,就不需要再寫對應(yīng)的私有字段了,C#編譯器會(huì)自動(dòng)給我們的自動(dòng)屬性提供一個(gè)對應(yīng)的字段,即不用手工聲明一個(gè)私有的字段。
注意:
如果是想只能只讀/只能只寫,
自動(dòng)屬性代碼
如何寫?如下:
加個(gè)private關(guān)鍵字
從而達(dá)到限制的目的。
自動(dòng)屬性同時(shí)實(shí)現(xiàn)get和set訪問器,不能使用自動(dòng)屬性只實(shí)現(xiàn)其中的一種,
如
public String Name { get; }
這種寫法會(huì)報(bào)錯(cuò)的。在vs IDE中輸入
prop
,點(diǎn)擊兩次tab會(huì)生成一個(gè)默認(rèn)的自動(dòng)屬性;
兩種屬性對比
對比常規(guī)屬性
和自動(dòng)屬性
:
-
自動(dòng)屬性寫起來的更快,且不用自己寫get,set方法,直接使用默認(rèn)的;
-
兩者來說,可能自動(dòng)屬性對比常規(guī)屬性,可能會(huì)少些兩句代碼,但不如常規(guī)屬性自己寫的方法靈活,按需使用。
字段和屬性的異同
-
訪問方式:
-
字段(Field): 是類中的變量,直接存儲(chǔ)數(shù)據(jù)。
可以通過類的實(shí)例直接訪問,但一般來說不鼓勵(lì)直接暴露字段,因?yàn)檫@會(huì)破壞封裝性。
class MyClass { public int myField; // 字段 }
-
屬性(Property): 提供了一種更加控制和靈活的方式來訪問或修改類的
私有字段的值
。屬性具有讀取器(getter)和寫入器(setter),可以在讀取和寫入數(shù)據(jù)時(shí)執(zhí)行自定義邏輯。
class MyClass { private int myField; // 私有字段 public int MyProperty // 屬性 { get { return myField; } set { myField = value; } } }
-
-
封裝性:
- 字段(Field): 通常是類的內(nèi)部實(shí)現(xiàn)細(xì)節(jié),可以通過直接暴露為 public 或 internal 字段來使其對外可見,但這樣做會(huì)破壞封裝性。
- 屬性(Property): 提供了更好的封裝性,允許類作者在保護(hù)類內(nèi)部實(shí)現(xiàn)的同時(shí),對外提供更安全和靈活的訪問接口。
-
使用場景:
- 字段(Field): 適用于不需要添加邏輯的簡單數(shù)據(jù)存儲(chǔ),或者在某些情況下需要對字段進(jìn)行直接訪問的場景。
- 屬性(Property): 適用于需要在讀取或?qū)懭霐?shù)據(jù)時(shí)執(zhí)行一些邏輯、驗(yàn)證或計(jì)算的場景。它提供了更靈活的控制和更好的封裝性。
在實(shí)際的編程中,一般建議使用屬性而不是直接暴露字段,以便更好地控制對類內(nèi)部實(shí)現(xiàn)的訪問。
C# 中的自動(dòng)屬性(Auto-implemented Properties)提供了一種更簡潔的語法來聲明屬性,省去了手動(dòng)實(shí)現(xiàn) get 和 set 方法的步驟。
class MyClass
{
public int MyProperty { get; set; } // 自動(dòng)屬性
}
//在這個(gè)例子中,`MyProperty` 是一個(gè)公共的自動(dòng)屬性,編譯器會(huì)`自動(dòng)生成相應(yīng)的私有字段以及默認(rèn)的 get 和 set 方法`。
委托
what
-
在C#中,委托是一種能夠存儲(chǔ)對方法的引用的類型。
它允許您
將方法作為參數(shù)傳遞給其他方法
或將方法存儲(chǔ)在變量
中,委托
可以被認(rèn)為是函數(shù)指針
。 -
通過委托,你可以像傳遞變量一樣傳遞方法, 它可以使你像傳遞參數(shù)一樣傳遞方法。
-
主要用于實(shí)現(xiàn)回調(diào)機(jī)制、事件處理和委托鏈等。
委托定義了一個(gè)方法簽名,它指定了
可以由該委托引用的方法的返回類型和參數(shù)列表
。
委托定義的方法簽名定義了它所能代表的方法種類。
可以將任何符合該方法簽名的方法分配給該委托,使其成為它的實(shí)例。
一旦將方法分配給委托,您可以像調(diào)用方法一樣調(diào)用該委托,以便在調(diào)用委托時(shí)執(zhí)行該方法。
使用委托的作用:
1、避免核心方法中存在大量的if…else…語句(或swich開關(guān)語句);
2、滿足程序設(shè)計(jì)的OCP原則;
3、使程序具有擴(kuò)展性;
4、綁定事件;
5、結(jié)合Lambda表達(dá)式,簡化代碼,高效編程;
6、實(shí)現(xiàn)程序的松耦合(解耦),這個(gè)在事件(event)中體現(xiàn)比較明顯;
**委托是一個(gè)類,它定義了方法的類型,使得可以將方法當(dāng)作另一個(gè)方法的參數(shù)來進(jìn)行傳遞,這種將方法動(dòng)態(tài)地賦給參數(shù)的做法,可以避免在程序中大量使用If-Else(Switch)**語句,同時(shí)使得程序具有更好的可擴(kuò)展性。
委托是對函數(shù)的引用,如果我們調(diào)用委托,實(shí)際是調(diào)用的委托引用的函數(shù)。委托擁有一個(gè)函數(shù)或一組函數(shù)的所有必要信息,包括簽名和返回值類型。
靜態(tài)設(shè)計(jì)時(shí),當(dāng)我們不知道具體哪個(gè)函數(shù)會(huì)被調(diào)用時(shí),我們可以使用委托。
定義
用delegate關(guān)鍵字
定義委托,定義委托,它定義了可以代表的方法的類型。
(注意,委托是沒有方法體的,類似接口里面的方法)
在定義委托前,必須明確兩個(gè)問題:
1、委托將要綁定的方法;
2、委托的形參類型,形參個(gè)數(shù)和委托的返回值必須與將要綁定的方法的形參類型,形參個(gè)數(shù)和返回值一致;
public delegate 委托返回類型 委托名(形參)
// 定義委托
delegate string MyDelegate(string message);
調(diào)用
可以使用 Invoke() 方法或使用 () 運(yùn)算符調(diào)用委托。
如下示例:
public delegate void MyDelegate(string message);//聲明一個(gè)委托
class Program{
// 跟MyDelegate有相同參數(shù)和返回值的方法 稱為簽名相同
static void MethodA(string message)
{
Console.WriteLine(message);
}
static void Main(string[] args){
//把函數(shù)傳給委托
MyDelegate md = new MyDelegate(MethodA);
//或者直接賦值給委托
MyDelegate md2 = MethodA;
//使用Lambda表達(dá)式
MyDelegate md3 = (string message) => Console.WriteLine(message);
//設(shè)置目標(biāo)方法后,可以使用 Invoke() 方法或使用 () 運(yùn)算符調(diào)用委托。
md.Invoke("hello");
md2("world");
md3("hello world");
}
}
操作/種類
注意:
在 C# 中,如果一個(gè)委托變量沒有綁定任何具體的方法,它的值將為
null
。
1、單播委托綁定方法:綁定單個(gè)方法
- 直接傳遞方法名稱綁定對應(yīng)的方法和調(diào)用委托,如下:
//定義 委托
public delegate void GreetingDelegate(string name);
//具體方法1
private static void EnglishGreeting(string name) {
Console.WriteLine("Morning, " + name);
}
//具體方法2
private static void ChineseGreeting(string name) {
Console.WriteLine("早上好, " + name);
}
//調(diào)用委托的方法,注意此方法,它接受一個(gè)GreetingDelegate類型的方法作為參數(shù)
private static void GreetPeople(string name, GreetingDelegate MakeGreeting) {
MakeGreeting(name);
}
//直接傳遞方法名稱調(diào)用委托(如同調(diào)用方法)
GreetPeople("Jimmy Zhang", EnglishGreeting);
GreetPeople("張子陽", ChineseGreeting);
-
先聲明委托變量,再使用
=
給委托變量賦值具體方法最后傳遞委托變量來調(diào)用委托, 如下:
//定義 委托
public delegate void GreetingDelegate(string name);
//具體方法
private static void EnglishGreeting(string name) {
Console.WriteLine("Morning, " + name);
}
//具體方法
private static void ChineseGreeting(string name) {
Console.WriteLine("早上好, " + name);
}
//注意此方法,它接受一個(gè)GreetingDelegate類型的方法作為參數(shù)
private static void GreetPeople(string name, GreetingDelegate MakeGreeting) {
MakeGreeting(name);
}
//聲明委托變量
GreetingDelegate delegate1, delegate2;
//委托變量賦值:委托變量綁定具體方法
delegate1 = EnglishGreeting;
delegate2 = ChineseGreeting;
//調(diào)用委托(如同調(diào)用方法)
GreetPeople("Jimmy Zhang", delegate1);
GreetPeople("張子陽", delegate2);
- 也可以繞過GreetPeople方法,通過委托來直接調(diào)用EnglishGreeting和ChineseGreeting:
//定義 委托
public delegate void GreetingDelegate(string name);
//具體方法
private static void EnglishGreeting(string name) {
Console.WriteLine("Morning, " + name);
}
//具體方法
private static void ChineseGreeting(string name) {
Console.WriteLine("早上好, " + name);
}
//聲明委托變量
GreetingDelegate delegate1;
//委托變量賦值
delegate1 = EnglishGreeting; // 先給委托類型的變量賦值
delegate1 += ChineseGreeting; // 給此委托變量再綁定一個(gè)方法
//調(diào)用委托
//將先后調(diào)用 EnglishGreeting 與 ChineseGreeting 方法
delegate1 ("Jimmy Zhang");
2、多播委托綁定方法:利用+=
綁定多個(gè)方法
可以將多個(gè)方法賦給同一個(gè)委托,或者叫將多個(gè)方法綁定到同一個(gè)委托,當(dāng)調(diào)用這個(gè)委托的時(shí)候,將依次調(diào)用其所綁定的方法。
注意:
- 綁定多個(gè)方法時(shí),委托范圍類型必須為void類型,否則只返回最后一個(gè)綁定的值。
- 使用委托可以將多個(gè)方法綁定到同一個(gè)委托變量,當(dāng)調(diào)用此變量時(shí)(這里用“調(diào)用”這個(gè)詞,是因?yàn)榇俗兞看硪粋€(gè)方法),可以依次調(diào)用所有綁定的方法。
- 可利用第一個(gè)方法用
=
形式,其余方法用+=
的形式來進(jìn)行多播委托,如下:
注意這里,第一次用的“=”,是賦值的語法;第二次,用的是“+=”,是綁定的語法。
如果第一次就使用“+=”,將出現(xiàn)“使用了未賦值的局部變量”的編譯錯(cuò)誤。
//定義 委托
public delegate void GreetingDelegate(string name);
//具體方法
private static void EnglishGreeting(string name) {
Console.WriteLine("Morning, " + name);
}
//具體方法
private static void ChineseGreeting(string name) {
Console.WriteLine("早上好, " + name);
}
//調(diào)用委托: 注意此方法,它接受一個(gè)GreetingDelegate類型的方法作為參數(shù)
private static void GreetPeople(string name, GreetingDelegate MakeGreeting) {
MakeGreeting(name);
}
//聲明委托變量
GreetingDelegate delegate1;
//委托變量綁定具體方法
//注意這里,第一次用的“=”,是賦值的語法;第二次,用的是“+=”,是綁定的語法。
//如果第一次就使用“+=”,將出現(xiàn)“使用了未賦值的局部變量”的編譯錯(cuò)誤。
delegate1 = EnglishGreeting;// 先用=給委托類型的變量賦值一個(gè)方法
delegate1 += ChineseGreeting;// 再用+=給此委托變量再綁定一個(gè)方法
//調(diào)用委托(如同調(diào)用方法)
//將先后調(diào)用 EnglishGreeting 與 ChineseGreeting 方法
GreetPeople("Jimmy Zhang",delegate1);
- 也可以使用下面的代碼來這樣簡化其中的多播委托過程:
GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);//先利用類似構(gòu)造函new委托變量形式傳遞綁定一個(gè)方法
delegate1 += ChineseGreeting; // 給此委托變量再綁定一個(gè)方法
-----------------------------------------------------------------------------------------------
你不禁想到:上面第一次綁定委托時(shí)不可以使用“+=”的編譯錯(cuò)誤,或許可以用這樣的方法來避免:
GreetingDelegate delegate1 = newGreetingDelegate();
delegate1 += EnglishGreeting; // 這次用的是 “+=”,綁定語法。
delegate1 += ChineseGreeting; // 給此委托變量再綁定一個(gè)方法
但實(shí)際上,這樣會(huì)出現(xiàn)編譯錯(cuò)誤: 因?yàn)椤癎reetingDelegate”所代表方法沒有采用“0”個(gè)參數(shù)的重載。有的話還可以嘗試下。
所以之后總的代碼就變成,如下:
//定義 委托
public delegate void GreetingDelegate(string name);
//具體方法
private static void EnglishGreeting(string name) {
Console.WriteLine("Morning, " + name);
}
//具體方法
private static void ChineseGreeting(string name) {
Console.WriteLine("早上好, " + name);
}
//調(diào)用委托: 注意此方法,它接受一個(gè)GreetingDelegate類型的方法作為參數(shù)
private static void GreetPeople(string name, GreetingDelegate MakeGreeting) {
MakeGreeting(name);
}
//先利用類似構(gòu)造函new委托變量形式傳遞綁定一個(gè)方法
GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);
//給此委托變量再綁定一個(gè)方法
delegate1 += ChineseGreeting;
//調(diào)用委托(如同調(diào)用方法)
//將先后調(diào)用 EnglishGreeting 與 ChineseGreeting 方法
GreetPeople("Jimmy Zhang",delegate1);
3、解綁方法:利用-=
解綁方法
//定義 委托
public delegate void GreetingDelegate(string name);
//具體方法1
private static void EnglishGreeting(string name) {
Console.WriteLine("Morning, " + name);
}
//具體方法2
private static void ChineseGreeting(string name) {
Console.WriteLine("早上好, " + name);
}
//調(diào)用委托: 注意此方法,它接受一個(gè)GreetingDelegate類型的方法作為參數(shù)
private static void GreetPeople(string name, GreetingDelegate MakeGreeting) {
MakeGreeting(name);
}
//先利用類似構(gòu)造函new委托變量形式傳遞綁定一個(gè)方法
GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);
//給此委托變量再綁定一個(gè)方法
delegate1 += ChineseGreeting;
//調(diào)用委托(如同調(diào)用方法)
//將先后調(diào)用 EnglishGreeting 與 ChineseGreeting 方法
GreetPeople("Jimmy Zhang",delegate1);
//利用-=解綁方法
delegate1 -= EnglishGreeting; //取消對EnglishGreeting方法的綁定
// 將僅調(diào)用 ChineseGreeting
GreetPeople("張子陽", delegate1);
普通委托
示例demo1.cs:
namespace test{
//定義委托,它定義了可以代表的方法的類型
public delegate void GreetingDelegate(string name);
//新建的GreetingManager類
public class GreetingManager{
//包含調(diào)用委托的方法
public void GreetPeople(string name, GreetingDelegate MakeGreeting) {
MakeGreeting(name);//調(diào)用委托
}
}
class Program{
//具體方法1
private static void EnglishGreeting(string name) {
Console.WriteLine("Morning, " + name);
}
//具體方法2
private static void ChineseGreeting(string name) {
Console.WriteLine("早上好, " + name);
}
//Main
static void Main(string[] args){
//創(chuàng)建類的實(shí)例對象
GreetingManager greetingManager = new GreetingManager();
//給調(diào)用委托的方法傳入具體的方法
greetingManager.GreetPeople("Jimmy Zhang",EnglishGreeting);
greetingManager.GreetPeople("章三",ChineseGreeting);
}
}
}
現(xiàn)在,假設(shè)需要將多個(gè)具體方法綁定到同一個(gè)委托變量,該如何做呢?再次改寫代碼,如下:
namespace test{
//定義委托,它定義了可以代表的方法的類型
public delegate void GreetingDelegate(string name);
//新建的GreetingManager類
public class GreetingManager{
//包含調(diào)用委托的方法
public void GreetPeople(string name, GreetingDelegate MakeGreeting) {
MakeGreeting(name);//調(diào)用委托
}
}
class Program{
//具體方法1
private static void EnglishGreeting(string name) {
Console.WriteLine("Morning, " + name);
}
//具體方法2
private static void ChineseGreeting(string name) {
Console.WriteLine("早上好, " + name);
}
//Main(客戶端)
static void Main(string[] args){
//創(chuàng)建類的實(shí)例對象
GreetingManager greetingManager = new GreetingManager();
//聲明委托變量
GreetingDelegate delegate1;
//給委托變量進(jìn)行多播委托綁定方法
delegate1 = EnglishGreeting;
delegate1 += ChineseGreeting;
//調(diào)用委托(相當(dāng)于調(diào)用方法)
greetingManager.GreetPeople("Jimmy Zhang",delegate1);
}
}
}
既然可以聲明委托類型的變量(在上例中是delegate1),為何不將這個(gè)變量封裝到 GreetManager類中?
這樣客戶端就不用去手動(dòng)聲明委托變量了,改寫代碼后的結(jié)果如下:
namespace test{
//定義委托,它定義了可以代表的方法的類型
public delegate void GreetingDelegate(string name);
//新建的GreetingManager類
public class GreetingManager{
//聲明委托變量
public GreetingDelegate delegate1;
//包含調(diào)用委托的方法
public void GreetPeople(string name, GreetingDelegate MakeGreeting) {
MakeGreeting(name);//調(diào)用委托
}
}
class Program{
//具體方法1
private static void EnglishGreeting(string name) {
Console.WriteLine("Morning, " + name);
}
//具體方法2
private static void ChineseGreeting(string name) {
Console.WriteLine("早上好, " + name);
}
//Main(客戶端)
static void Main(string[] args){
//創(chuàng)建類的實(shí)例對象
GreetingManager greetingManager = new GreetingManager();
//給類中的委托變量屬性進(jìn)行多播委托綁定方法
greetingManager.delegate1 = EnglishGreeting;
greetingManager.delegate1 += ChineseGreeting;
//調(diào)用委托(相當(dāng)于調(diào)用方法)
greetingManager.GreetPeople("Jimmy Zhang",greetingManager.delegate1);
}
}
}
盡管這樣做沒有任何問題,但我們發(fā)現(xiàn)這條語句寫起來很奇怪。
即在調(diào)用greetingManager.GreetPeople方法的時(shí)候,傳遞了greetingManager的delegate1字段。
既然如此,我們修改 GreetingManager 類成這樣:
//新建的GreetingManager類
public class GreetingManager{
//聲明委托變量
public GreetingDelegate delegate1;
//包含調(diào)用委托的方法
public void GreetPeople(string name) {
if(delegate1!=null){ //如果有方法注冊委托變量
delegate1(name); //通過委托調(diào)用方法
}
}
然后修改后的最終代碼如下:
namespace test{
//定義委托,它定義了可以代表的方法的類型
public delegate void GreetingDelegate(string name);
//新建的GreetingManager類
public class GreetingManager{
//聲明委托變量
public GreetingDelegate delegate1;
//包含調(diào)用委托的方法
public void GreetPeople(string name) {
if(delegate1!=null){ //如果有方法注冊委托變量
delegate1(name); //通過委托調(diào)用方法
}
}
class Program{
//具體方法1
private static void EnglishGreeting(string name) {
Console.WriteLine("Morning, " + name);
}
//具體方法2
private static void ChineseGreeting(string name) {
Console.WriteLine("早上好, " + name);
}
//Main(客戶端)
static void Main(string[] args){
//創(chuàng)建類的實(shí)例對象
GreetingManager greetingManager = new GreetingManager();
//給類中的委托變量屬性進(jìn)行多播委托綁定方法
greetingManager.delegate1 = EnglishGreeting;
greetingManager.delegate1 += ChineseGreeting;
//調(diào)用委托(相當(dāng)于調(diào)用方法)
greetingManager.GreetPeople("Jimmy Zhang");
}
}
}
- 普通委托還可以利用lambda表達(dá)式簡化寫法,如下:
//定義委托
delegate void SendMessage(string text);
//原本的一個(gè)具體的函數(shù)(委托要引用的函數(shù))
1 void WriteText(string text)
2 {
3 Console.WriteLine($"Text:{text}");
4 }
//原先委托變量賦值具體方法的寫法1
SendMessage delegate1 = new SendMessage(WriteText);
//原先委托變量賦值具體方法的寫法2
SendMessage delegate2 = WriteText;
//-------------------------------------------
//而利用lambda表達(dá)式簡化寫法后,變?yōu)槿缦碌暮啙崒懛?
SendMessage delegate3 = (text) => {Console.WriteLine($"Text:{text}");};
泛型委托
使用泛型委托可以為方法的參數(shù)類型和返回類型提供更大的靈活性和重用性,尤其是在編寫通用代碼時(shí)非常有用。
C#中定義泛型委托的語法:
delegate <return type> <delegate name><T>(<T parameters>);
//如果委托的定義符合一定的格式規(guī)范,可以省略
delegate
關(guān)鍵字
其中,
<return type>
表示委托所表示方法的返回類型,<delegate name>
是委托的名稱,<T>
表示泛型類型參數(shù),<T parameters>
是泛型方法的參數(shù)列表。
以下是一個(gè)示例,展示如何定義一個(gè)簡單的泛型委托類型:
delegate T MyGenericDelegate<T>(T x);
//這個(gè)泛型委托類型的名稱是 MyGenericDelegate,它表示一個(gè)方法,該方法接受一個(gè)泛型類型的參數(shù) T 并返回一個(gè)相同T類型的值。
下面是一個(gè)使用泛型委托的示例:
// 假設(shè)有一個(gè)泛型委托,用于處理任意類型的數(shù)據(jù)
delegate void ProcessDataDelegate<T>(T data);
// 假設(shè)有一個(gè)類用于處理整數(shù)數(shù)據(jù)
class IntegerProcessor
{
//具體的形參類型和具體方法
public static void ProcessInteger(int number)
{
Console.WriteLine("Processing integer: " + number);
}
}
// 假設(shè)有一個(gè)類用于處理字符串?dāng)?shù)據(jù)
class StringProcessor
{
//具體的形參類型和具體方法
public static void ProcessString(string text)
{
Console.WriteLine("Processing string: " + text);
}
}
//client
class Program
{
static void Main()
{
// 創(chuàng)建泛型委托實(shí)例,指向整數(shù)處理方法
ProcessDataDelegate<int> intDelegate = IntegerProcessor.ProcessInteger;
// 創(chuàng)建泛型委托實(shí)例,指向字符串處理方法
ProcessDataDelegate<string> stringDelegate = StringProcessor.ProcessString;
// 使用泛型委托處理整數(shù)數(shù)據(jù)
intDelegate(10);
// 使用泛型委托處理字符串?dāng)?shù)據(jù)
stringDelegate("Hello, world!");
}
}
//在上面的示例中,首先定義了一個(gè)泛型委托 ProcessDataDelegate<T>,它可以處理任意類型的數(shù)據(jù)。
//然后,通過創(chuàng)建委托實(shí)例并將其指向不同的處理方法,分別處理整數(shù)和字符串?dāng)?shù)據(jù)。
//最后,通過調(diào)用委托實(shí)例,將數(shù)據(jù)傳遞給相應(yīng)的處理方法進(jìn)行處理。
預(yù)定義委托類型
在 C# 中,有一些預(yù)定義的委托類型,它們屬于 System
命名空間。
這些委托類型提供了對常見委托簽名的快捷方式,減少了在聲明委托時(shí)需要手動(dòng)編寫的代碼。
以下是一些常見的預(yù)定義委托類型:
1.Action 委托
Action
委托表示不返回值的方法。它可以接受從 0 到 16 個(gè)輸入?yún)?shù)。
例如,Action<int, string>
表示一個(gè)接受整數(shù)和字符串兩個(gè)參數(shù)的方法,不返回值。
Action<int,string> myAction = (x,s) => {
Console.WriteLine($"Received int:{x},string:{s}");
};
myAction(42,"hello");
2.Func 委托
Func
委托表示有返回值的方法。它可以接受從 0 到 16 個(gè)輸入?yún)?shù),并且最后一個(gè)參數(shù)表示返回值類型。
例如,Func<int, string, bool>
表示一個(gè)接受整數(shù)和字符串兩個(gè)參數(shù)的方法,返回一個(gè)布爾值。
Func<int, string, bool> myFunc = (x, s) =>
{
Console.WriteLine($"Received int: {x}, string: {s}");
return true;
};
bool result = myFunc(42, "Hello");
3.Predicate 委托
Predicate<T>
是 Func<T, bool>
的一個(gè)特殊版本,用于表示只接受一個(gè)參數(shù)并返回布爾值的方法。
通常用于檢查某個(gè)條件是否滿足。
//由于一定返回bool值,故這里只填寫一個(gè)int參數(shù)表明接收的那一個(gè)參數(shù)的類型
Predicate<int> isEven = x => x % 2 == 0;
bool result = isEven(4); // 返回 true
4.Comparison 委托
Comparison<T>
是 Func<T, T, int>
的一個(gè)特殊版本,用于表示比較兩個(gè)對象的方法。通常用于排序算法中。
//這個(gè)例子中,Comparison<int> 表示一個(gè)接受兩個(gè) int 類型參數(shù)的方法,返回一個(gè)整數(shù)。
Comparison<int> compareInts = (x, y) => x.CompareTo(y);
int result = compareInts(5, 3); // 返回 1,表示第一個(gè)參數(shù)大于第二個(gè)參數(shù);-1則第一個(gè)參數(shù)小于第二個(gè)參數(shù)
匿名類型
在C#中,**匿名類型(Anonymous Types)**是一種用于創(chuàng)建臨時(shí)對象
的特殊類型。
匿名類型允許你在不顯式定義類的情況下,創(chuàng)建包含一組只讀屬性的對象。
這些屬性的名稱和類型由編譯器根據(jù)初始化表達(dá)式進(jìn)行推斷。
例子:
var person = new { Name = "John", Age = 30 };
在這個(gè)例子中,person
是一個(gè)匿名類型的對象,它有兩個(gè)只讀屬性 Name
和 Age
,這些屬性的名稱和類型由初始化表達(dá)式 "John"
和 30
推斷而來。
特點(diǎn)和限制:
- 只讀屬性: 匿名類型的屬性是只讀的,無法在初始化后修改。
- 屬性推斷: 屬性的名稱和類型是由初始化表達(dá)式推斷而來的,而不是顯式聲明的。
- 編譯時(shí)類型: 對于不同的初始化表達(dá)式,編譯器會(huì)創(chuàng)建不同的匿名類型,即使屬性的名稱和類型相同。
匿名類型通常在需要在一個(gè)地方使用臨時(shí)數(shù)據(jù)結(jié)構(gòu)而不想專門為之創(chuàng)建一個(gè)類時(shí)很有用
示例1:
例如在LINQ查詢中選擇特定的字段。
//LINQ查詢(聲明語法)
var query = from p in people
select new { p.Name, p.Age };
//在這個(gè)例子中,`query` 是一個(gè)匿名類型的集合,包含了 `people` 集合中每個(gè)對象的 `Name` 和 `Age` 屬性。
示例2:
var person = new { Name = "John", Age = 30 };
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
//這個(gè)例子中,`person` 是一個(gè)匿名類型的對象,可以通過屬性名訪問其屬性值。
匿名函數(shù)
在C#中,匿名函數(shù)是一種無需命名的函數(shù),可以在代碼中定義和使用。
匿名函數(shù)通常用作委托
的實(shí)際參數(shù),以便在運(yùn)行時(shí)將其傳遞給其他函數(shù)或方法。
C#中有兩種類型的匿名函數(shù):lambda表達(dá)式和匿名方法。
匿名方法
匿名方法是一種匿名函數(shù)。
匿名方法是一種使用delegate關(guān)鍵字
定義的無需命名的方法。
它的語法如下:
delegate (parameter_list) { statements }
其中,parameter_list是一個(gè)用逗號(hào)分隔的參數(shù)列表,statements是一系列語句。
例如,以下代碼創(chuàng)建了一個(gè)匿名方法,將兩個(gè)整數(shù)相加并返回結(jié)果:
Func<int, int, int> add = delegate(int x, int y) { return x + y; };//其中的Func<int,int,int>是泛型委托,它表示一個(gè)能夠接受兩個(gè)整數(shù)參數(shù)并返回一個(gè)整數(shù)結(jié)果的委托
//上面的代碼也可寫成: Func<int,int,int> add = delegate(x,y) {return x+y;}
//即可以省略形參的類型,因?yàn)橥ㄓ梦蠪unc中已經(jīng)表明了方法形參的數(shù)據(jù)類型
int result = add(3, 4); // result = 7
lambda表達(dá)式
Lambda表達(dá)式是一種匿名函數(shù),它可以快速地創(chuàng)建一個(gè)委托或表達(dá)式。
lambda 表達(dá)式是從匿名方法演變而來,首先刪除了委托關(guān)鍵字(delegate)和參數(shù)類型,并添加了一個(gè) lambda 運(yùn)算符 =>
。
它的語法如下:
(parameter_list) => {expression}
如果只有一個(gè)參數(shù),參數(shù)列表(parameter_list)的()可以省略。
如果方法體只有一行代碼,方法體{expression}中可省略{}。
如果
=>
之后的方法體中只有一行代碼,且方法有返回值,那么可以省略方法體的{}以及return
其中,parameter_list是一個(gè)用逗號(hào)分隔的參數(shù)列表,expression是一個(gè)返回值的表達(dá)式。
例如,以下代碼創(chuàng)建了一個(gè)lambda表達(dá)式,將兩個(gè)整數(shù)相加并返回結(jié)果:
Func<int, int, int> add = (x, y) => x + y;//其中的Func<int,int,int>是泛型委托,它表示一個(gè)能夠接受兩個(gè)整數(shù)參數(shù)并返回一個(gè)整數(shù)結(jié)果的委托
//可以省略參數(shù)數(shù)據(jù)類型,因?yàn)榫幾g器根據(jù)委托類型推斷出參數(shù)類型用=>引出來方法體
int result = add(3, 4); // result = 7
擴(kuò)展方法
什么是擴(kuò)展方法?
擴(kuò)展方法,顧名思義,是對原有的對象附加一個(gè)新的方法。
可以將擴(kuò)展方法添加到您自己的自定義類、.NET 框架類或第三方類或接口。
如何區(qū)分?jǐn)U展方法?
如上圖,擴(kuò)展方法在visual studio的intellisense中有一個(gè)特殊的符號(hào),這樣你就可以輕松區(qū)分類方法和擴(kuò)展方法。
自己如何寫擴(kuò)展方法
在以下示例中,我在 Malema.net 命名空間下創(chuàng)建了一個(gè) IntExtensions 類。
IntExtensions 類中將包含適用于 int 數(shù)據(jù)類型的所有擴(kuò)展方法。
(可以為命名空間和類使用任何名稱。有時(shí)候我們會(huì)命名成我們項(xiàng)目當(dāng)中都要用到的名字,這樣就不需在每個(gè)類里再引用一次命名空間)
namespace Malema.Extensions
{
public static class IntExtensions
{
}
}
現(xiàn)在,將靜態(tài)方法定義為擴(kuò)展方法,其中擴(kuò)展方法的第一個(gè)參數(shù)指定擴(kuò)展方法所適用的類型。 我們將在 int 類型上使用這個(gè)擴(kuò)展方法。 所以第一個(gè)參數(shù)必須在 int 前面加上 this 修飾符。除了第一個(gè)參數(shù),其他參數(shù)就是真正的方法形參。
namespace Malema.Extensions
{
public static class IntExtensions
{
public static bool IsGreaterThan(this int i, int value)
{
return i > value;
}
}
}
現(xiàn)在,可以在要使用此擴(kuò)展方法的地方 引用 Malema.Extensions 命名空間即可
。
using System;
using Malema.Extensions;
namespace ConsoleApp{
class program{
public static void Main(String[] args){
var i = 10;
bool result = i.IsGreaterThan(100);
Console.WriteLine(result);
}
}
}
寫一個(gè)泛型擴(kuò)展方法
namespace Malema.Extensions{
public static class NumberExtensions{
public static bool IsGreaterThan<T>(this T i,T value) where T:IComparable{
return i.CompareTo(value)?false:true;
}
}
}
總結(jié):
常規(guī)靜態(tài)方法和擴(kuò)展方法之間的唯一區(qū)別是擴(kuò)展方法的第一個(gè)參數(shù)指定要對其進(jìn)行操作的類型,前面是 this 關(guān)鍵字。
- 擴(kuò)展方法是額外的自定義方法,它們最初不包含在類中。
- 可以將擴(kuò)展方法添加到自定義、.NET Framework 或第三方類、結(jié)構(gòu)或接口。
- 擴(kuò)展方法的第一個(gè)參數(shù)必須是擴(kuò)展方法適用的類型,以 this 關(guān)鍵字開頭。
- 通過包含引入擴(kuò)展方法的命名空間,可以在應(yīng)用程序的任何地方使用擴(kuò)展方法。
對象初始化
C#中的對象初始化語法(Object Initializer Syntax)允許你在創(chuàng)建對象的同時(shí)對其屬性進(jìn)行初始化,而不止是單靠構(gòu)造函數(shù),使得代碼更為簡潔。這種語法通常用于設(shè)置對象的屬性值,特別是在構(gòu)造函數(shù)的參數(shù)列表中沒有足夠信息來初始化對象時(shí)。
在C#中,對象初始化語法通常是通過使用對象初始化器(Object Initializer Syntax)完成的,而不是直接調(diào)用構(gòu)造函數(shù)。
這意味著你可以在創(chuàng)建對象的同時(shí)初始化其屬性,而不必使用括號(hào)調(diào)用構(gòu)造函數(shù)。
1. 基本用法:
// 定義一個(gè)類
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
// 使用對象初始化語法創(chuàng)建并初始化對象
Person person = new Person
{
Name = "John Doe",
Age = 30
};
2. 初始化匿名類型:
對象初始化語法不僅適用于具體的類,還可以用于初始化匿名類型。
var person = new { Name = "John Doe", Age = 30 };
3. 嵌套初始化:
如果類中包含其他對象的引用,也可以使用對象初始化語法為嵌套的對象進(jìn)行初始化。
public class Address
{
public string City { get; set; }
public string Country { get; set; }
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Address HomeAddress { get; set; }
}
Person person = new Person
{
Name = "John Doe",
Age = 30,
HomeAddress = new Address
{
City = "New York",
Country = "USA"
}
};
4. 在集合中使用初始化語法:
在集合的初始化中,你也可以使用對象初始化語法來初始化集合中的對象。
List<Person> people = new List<Person>
{
new Person { Name = "John Doe", Age = 30 },
new Person { Name = "Jane Doe", Age = 25 }
};
5. 使用構(gòu)造函數(shù)和初始化語法:
如果類有構(gòu)造函數(shù),你可以在初始化語法中使用構(gòu)造函數(shù),并在大括號(hào)中為屬性賦值。
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
Person person = new Person("John Doe", 30);
總的來說,對象初始化語法提供了一種簡潔而方便的方式來創(chuàng)建和初始化對象。
注意:
- 在Java中,沒有像C#那樣的對象初始化語法,因此對象的初始化主要通過構(gòu)造函數(shù)來完成。
在C#中,對象初始化語句(Object Initialization Syntax)可以使用帶括號(hào)和不帶括號(hào)的方式。
ClassName obj = new ClassName { Property1 = value1, Property2 = value2, // ... };
ClassName obj = new ClassName() { Property1 = value1, Property2 = value2, // ... };
無論你選擇使用括號(hào)還是不使用括號(hào),兩者都會(huì)創(chuàng)建一個(gè)新的對象并進(jìn)行初始化。在實(shí)際使用中,括號(hào)通常是可選的,除非構(gòu)造函數(shù)本身需要參數(shù)。如果構(gòu)造函數(shù)不需要參數(shù),則可以選擇省略括號(hào)。
繼承與多態(tài)
-
多態(tài)的理解: 同一類/類型的多個(gè)實(shí)例對同一個(gè)消息做出不同的反應(yīng)。
在平時(shí)的應(yīng)用中,多態(tài)性就是指,當(dāng)使用
基類對象指向子類實(shí)例
對象時(shí),使用這個(gè)對象去調(diào)用基類和父類中的同名方法
。總的來說,通過基類類型指向派生類對象時(shí),如果方法被重寫,將調(diào)用派生類中的方法;如果方法沒有被重寫,將調(diào)用基類中的方法。這種行為體現(xiàn)了多態(tài)性的概念。
-
關(guān)于c#中
用基類變量聲明子類實(shí)例對象
,在調(diào)用基類和子類中的同名方法
時(shí)的調(diào)用結(jié)果問題:-
case1: 當(dāng)沒碰到重寫時(shí),不管子類中的同名方法上有沒有帶有
new
關(guān)鍵字,用實(shí)例對象.同名方法
調(diào)用的最終都是基類中的同名方法,即方法的隱藏。在這種情況下,派生類的方法會(huì)隱藏自己跟基類中的同名方法,使得只會(huì)調(diào)用基類中的方法。 -
case2: 當(dāng)碰到重寫時(shí),即基類同名方法帶有
virtual
關(guān)鍵字,子類同名方法帶有override
關(guān)鍵字時(shí),此時(shí)用實(shí)例對象.同名方法
調(diào)用的最終都是子類中同名方法
-
case1: 當(dāng)沒碰到重寫時(shí),不管子類中的同名方法上有沒有帶有
-
C#中的繼承僅支持單一繼承。
繼承的語法格式:
當(dāng)前類名:繼承的基類名
-
組合優(yōu)于繼承。
繼承的一個(gè)壞處繼承層次多的時(shí)候。如果你想改動(dòng)底層的一個(gè)方法的時(shí)候。你得考慮子類的依賴性。
-
C#中若不想一個(gè)類被繼承,可以添加
sealed
關(guān)鍵字。 -
C#中所有類型都繼承自
System.Object
(隱式繼承,即沒有明顯的寫出xx類 : System.Object
的形式). -
C#中的
base
關(guān)鍵字類似于Java中的super
關(guān)鍵字。 -
子類重寫基類方法時(shí),會(huì)涉及到
virtual
和override
關(guān)鍵字。其中基類的方法會(huì)添加
virtual
關(guān)鍵字,而子類的同名方法會(huì)添加override
關(guān)鍵字。【而對比
Java
通過@Override
注解實(shí)現(xiàn)方法重寫】
virtual
修飾符:virtual修飾符可以將一個(gè)方法聲明為虛擬方法。即聲明當(dāng)前的方法只是一個(gè)占位符,具體的操作都得靠其派生類來實(shí)現(xiàn)。
虛方法的作用在于允許使用多態(tài)性,提高靈活性和擴(kuò)展性; 允許在派生類中覆蓋基類的方法,提供定制的實(shí)現(xiàn)。
其中
virtual
使用的語法格式:其中
override
使用的語法格式:
抽象類與接口
抽象類:
- 方法用
abstract
關(guān)鍵字修飾,則類也要用abstract
關(guān)鍵字修飾。
接口:
- 接口可以實(shí)現(xiàn)多個(gè)。
- 接口名常以
大寫I
開頭,如:IMyInterface
- 接口的實(shí)現(xiàn)格式:
類名:接口名
注意:
由于繼承和接口實(shí)現(xiàn)都是用
:
的格式,當(dāng)碰到某個(gè)類既要繼承某個(gè)類,還有實(shí)現(xiàn)某個(gè)接口時(shí),此時(shí)繼承類寫在前面,實(shí)現(xiàn)的接口寫在后面,中間以
,
隔開:如:
class Manager:Employee,IMyInterface
因?yàn)橐粋€(gè)類繼承的類只能有一個(gè),但實(shí)現(xiàn)的接口可以有多個(gè)。
抽象類和接口的使用選擇:
- 當(dāng)涉及到繼承/復(fù)用時(shí),考慮抽象類,否則優(yōu)先考慮接口。
C#特性
特性Attribute是用于在運(yùn)行時(shí)傳遞程序中各種元素(比如類、方法、結(jié)構(gòu)、枚舉、組件等)的行為信息的聲明性標(biāo)簽。
其它語言如java中也叫注解 anotation
您可以通過使用特性向程序添加聲明性信息。
一個(gè)聲明性標(biāo)簽是通過放置在它所應(yīng)用的元素前面的方括號(hào)([ ])來描述的。
特性(Attribute)用于添加元數(shù)據(jù),如編譯器指令和注釋、描述、方法、類等其他信息。
.Net 框架提供了兩種類型的特性:預(yù)定義特性和自定義特性。
預(yù)定義特性
Net 框架提供了三種預(yù)定義特性:
- AttributeUsage
- Conditional
- Obsolete
AttributeUsage
預(yù)定義特性 AttributeUsage 描述了如何使用一個(gè)自定義特性類。
它規(guī)定了特性可應(yīng)用到的項(xiàng)目的類型。
當(dāng)我們要自己定義Attribute的時(shí)候我們就需要用到它了。
規(guī)定該特性的語法如下:
[AttributeUsage(validon,AllowMultiple=allowmultiple,Inherited=inherited)]
其中:
- 參數(shù) validon 規(guī)定特性可被放置的語言元素。它是枚舉器 AttributeTargets 的值的組合。默認(rèn)值是 AttributeTargets.All。
- 參數(shù) allowmultiple(可選的)為該特性的 AllowMultiple 屬性(property)提供一個(gè)布爾值。如果為 true,則該特性是多用的。默認(rèn)值是 false(單用的)。
- 參數(shù) inherited(可選的)為該特性的 Inherited 屬性(property)提供一個(gè)布爾值。如果為 true,則該特性可被派生類繼承。默認(rèn)值是 false(不被繼承)。 例如:
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]
條件編譯 Conditional
這個(gè)預(yù)定義特性標(biāo)記了一個(gè)條件方法,其執(zhí)行依賴于指定的預(yù)處理標(biāo)識(shí)符。
它會(huì)引起方法調(diào)用的條件編譯,取決于指定的值,比如 Debug 或 Trace。
例如,當(dāng)調(diào)試代碼時(shí)顯示變量的值。規(guī)定該特性的語法如下:
[Conditional(conditionalSymbol)]
示例:
namespace Malema.net
{
public class Myclass
{
[Conditional("DEBUG")]
public static void Message(string msg)
{
Console.WriteLine(msg);
}
}
class Program
{
static async Task Main(string[] args)
{
Myclass.Message("hello");
Console.ReadLine();
}
}
}
上面的代碼在Debug模式下,會(huì)輸出 hello。在Release模式下則不會(huì)輸出hello
Obsolete
這個(gè)預(yù)定義特性標(biāo)記了不應(yīng)被使用的程序?qū)嶓w。它可以讓您通知編譯器丟棄某個(gè)特定的目標(biāo)元素。
例如,當(dāng)一個(gè)新方法被用在一個(gè)類中,但是您仍然想要保持類中的舊方法,您可以通過顯示一個(gè)應(yīng)該使用新方法而不是舊方法的消息,來把它標(biāo)記為 obsolete(過時(shí)的)。
規(guī)定該特性的語法如下:
[Obsolete(message)]
[Obsolete(message,iserror)]
其中:
- 參數(shù) message,是一個(gè)字符串,描述項(xiàng)目為什么過時(shí)以及該替代使用什么。
- 參數(shù) iserror,是一個(gè)布爾值。如果該值為 true,編譯器應(yīng)把該項(xiàng)目的使用當(dāng)作一個(gè)錯(cuò)誤。默認(rèn)值是 false(編譯器生成一個(gè)警告)。 下面的實(shí)例演示了該特性:
[Obsolete("過期了,請使用YourClass.Abc")]
public class Myclass
{
public static void Message(string msg)
{
Console.WriteLine(msg);
}
}
class Program
{
static async Task Main(string[] args)
{
Myclass.Message("helo");
Console.ReadLine();
}
}
會(huì)看到如下圖這樣的編譯警告提示:
自定義特性
Net 框架允許創(chuàng)建自定義特性,用于存儲(chǔ)聲明性的信息,且可在運(yùn)行時(shí)被檢索。
創(chuàng)建并使用自定義特性包含三個(gè)步驟:
- 聲明自定義特性
- 在目標(biāo)程序元素上應(yīng)用自定義特性
- 通過反射訪問特性 最后一個(gè)步驟包含編寫一個(gè)簡單的程序來讀取元數(shù)據(jù)以便查找各種符號(hào)。元數(shù)據(jù)是用于描述其他數(shù)據(jù)的數(shù)據(jù)和信息。該程序應(yīng)使用反射來在運(yùn)行時(shí)訪問特性。
聲明自定義特性
一個(gè)新的自定義特性應(yīng)派生自 System.Attribute 類。例如:
[AttributeUsage(AttributeTargets.Property)]
public class CsvAttritube:Attribute{
public string name{get;set;}
public CsvAttribute(string name){
this.name = name;
}
}
應(yīng)用這個(gè)特性
上面我們自定義的CsvAttribute類。但是在使用的時(shí)候我們可以把Attribute省略掉。
public class Bar{
[Csv("OpenPrice")
public float Open{get;set};
[Csv("ClosePrice")]
public float Close{get;set;}
}
如何讓這個(gè)特性有效果。得靠反射。
C# 反射
反射這個(gè)東西 就如同照鏡子一樣:鏡子里面反射出了正在照鏡子的人。
反射跟這個(gè)的效果就是一樣的:通過反射程序我們就可以知道程序是什么樣子的。
反射是.Net中獲取運(yùn)行時(shí)類型信息的方式。
.Net的應(yīng)用程序由幾個(gè)部分:程序集(Assembly)
、模塊(Module)
、類型(class)
組成,而反射提供一種編程的方式,讓程序員可以在程序運(yùn)行期獲得這幾個(gè)組成部分的相關(guān)信息.
通過使用反射API :
-
我們可以用反射訪問到
程序集
模塊
類型
與及類型上面的一些元信息 Attribute
,這些統(tǒng)稱為元數(shù)據(jù)
(metadata)。 -
我們還可以使用反射
動(dòng)態(tài)的創(chuàng)建對象
,并對對象的屬性字段
進(jìn)行取值和賦值
,也可以調(diào)用里面的方法
,包括私有方法
。
反射優(yōu)點(diǎn):
- 反射提高了程序的靈活性和擴(kuò)展性。
- 降低耦合性,提高自適應(yīng)能力。
- 它允許程序創(chuàng)建和控制任何類的對象,無需提前硬編碼目標(biāo)類。
反射缺點(diǎn):
-
性能問題:使用反射基本上是一種解釋操作,用于字段和方法接入時(shí)要遠(yuǎn)慢于直接代碼。
因此反射機(jī)制主要應(yīng)用在對靈活性和拓展性要求很高的系統(tǒng)框架上,普通程序不建議使用。
-
使用反射會(huì)模糊程序內(nèi)部邏輯;程序員希望在源代碼中看到程序的邏輯,反射卻繞過了源代碼的技術(shù),因而會(huì)帶來維護(hù)的問題,反射代碼比相應(yīng)的直接代碼更復(fù)雜。
反射用到的主要類:
System.Reflection.Assembly類:
Assembly
類是 C# 中用于處理程序集(Assembly)的主要類之一。
它提供了獲取程序集信息、訪問類型和成員、以及執(zhí)行程序集中的代碼的方法。
以下是 Assembly
類的一些常用方法和示例:
1.加載程序集
:
// 加載當(dāng)前執(zhí)行的程序集
Assembly currentAssembly = Assembly.GetExecutingAssembly();
// 加載指定的程序集
Assembly myAssembly = Assembly.LoadFrom("MyAssembly.dll");
2.獲取程序集信息:
// 獲取當(dāng)前執(zhí)行的程序集信息
Assembly currentAssembly = Assembly.GetExecutingAssembly();
Console.WriteLine($"Assembly Name: {currentAssembly.FullName}");
// 獲取程序集的類型
Type[] types = currentAssembly.GetTypes();
foreach (Type type in types)
{
Console.WriteLine($"Type: {type.FullName}");
}
3.獲取程序集中的類型和成員:
// 獲取當(dāng)前執(zhí)行的程序集
Assembly currentAssembly = Assembly.GetExecutingAssembly();
// 獲取程序集中的類型
Type myType = currentAssembly.GetType("MyNamespace.MyClass");
// 獲取類型中的成員
MethodInfo method = myType.GetMethod("MyMethod");
PropertyInfo property = myType.GetProperty("MyProperty");
4.執(zhí)行程序集中的代碼:
// 獲取當(dāng)前執(zhí)行的程序集
Assembly currentAssembly = Assembly.GetExecutingAssembly();
// 創(chuàng)建對象實(shí)例
object myObject = Activator.CreateInstance(currentAssembly.GetType("MyNamespace.MyClass"));
// 調(diào)用方法:Invoke(object obj, object[] parameters);
//obj:表示要在其上調(diào)用方法的對象實(shí)例;parameters:是一個(gè)數(shù)組,包含傳遞給方法的參數(shù)
MethodInfo method = currentAssembly.GetType("MyNamespace.MyClass").GetMethod("MyMethod");
method.Invoke(myObject, null);
System.Activator
Activator
類是 C# 中的一個(gè)工具類,它提供了在運(yùn)行時(shí)創(chuàng)建對象、調(diào)用構(gòu)造函數(shù)以及創(chuàng)建對象實(shí)例的功能。Activator
類主要包含一些靜態(tài)方法,方便在不知道具體類型的情況下動(dòng)態(tài)地創(chuàng)建對象。
以下是 Activator
類的一些常用方法和示例:
1.CreateInstance
方法: 通過指定類型的名稱或指定類型的Type
對象,創(chuàng)建該類型的實(shí)例
// 通過類型名稱創(chuàng)建對象實(shí)例
object obj1 = Activator.CreateInstance(Type.GetType("System.String"));
// 通過 Type 對象創(chuàng)建對象實(shí)例
Type myType = typeof(int);
object obj2 = Activator.CreateInstance(myType);
2.CreateInstance<T>
泛型方法: 通過指定類型的名稱或 指定類型的Type
對象,創(chuàng)建該類型的實(shí)例,并進(jìn)行強(qiáng)類型轉(zhuǎn)換
// 通過類型名稱創(chuàng)建對象實(shí)例并進(jìn)行強(qiáng)類型轉(zhuǎn)換
string str = Activator.CreateInstance<string>();
// 通過 Type 對象創(chuàng)建對象實(shí)例并進(jìn)行強(qiáng)類型轉(zhuǎn)換
Type myType = typeof(int);
int num = Activator.CreateInstance<int>();
3.CreateInstanceFrom
方法: 從指定程序集文件中創(chuàng)建對象實(shí)例
Assembly myAssembly = Assembly.LoadFrom("MyAssembly.dll");
Type myType = myAssembly.GetType("MyNamespace.MyClass");
object obj = Activator.CreateInstanceFrom("MyAssembly.dll", "MyNamespace.MyClass").Unwrap();
Console.WriteLine(obj.GetType()); // 輸出: MyNamespace.MyClass
}
System.Type類:
System.Type 類對于反射起著核心的作用。
它是一個(gè)抽象的基類,Type有與每種數(shù)據(jù)類型對應(yīng)的派生類,我們使用這個(gè)派生類的對象的方法、字段、屬性查找有關(guān)該類型的所有信息。
獲取給定類型的Type引用有3種常用方式
:
-
使用c# typeof運(yùn)算符
Type t = typeof(string);
-
使用對象的GetType()方法
string s = "hello,c#"; Type t = s.GetType();
-
調(diào)用Type類的靜態(tài)方法GetType()
Type t = Type.GetType(System.String);
上面的三種方式都是獲取string類型的Type引用對象,在取出string類型的Type引用t之后, 就可以通過t來探測string的類型結(jié)構(gòu),如下:
string s = "carson";
Type t = s.GetType();
foreach(MememInfo mi in t.GetMembers()){
Console.WriteLine(mi.Name);
}
Type類的屬性:
Name 數(shù)據(jù)類型名
FullName 數(shù)據(jù)類型的完全限定名(包括命名空間名)
Namespace 定義數(shù)據(jù)類型的命名空間名
IsAbstract 指示該類型是否是抽象類型
IsArray 指示該類型是否是數(shù)組
IsClass 指示該類型是否是類
IsEnum 指示該類型是否是枚舉
IsInterface 指示該類型是否是接口
IsPublic 指示該類型是否是公有的
IsSealed 指示該類型是否是密封類
IsValueType 指示該類型是否是值類型
Type類的方法:
- GetConstructor() : GetConstructors()返回ConstructorInfo類型,用于取得該類的構(gòu)造函數(shù)的信息
- GetEvent(): GetEvents()返回EventInfo類型,用于取得該類的事件的信息
- GetField(): GetFields()返回FieldInfo類型,用于取得該類的字段(成員變量)的信息
- GetInterface(): GetInterfaces()返回InterfaceInfo類型,用于取得該類實(shí)現(xiàn)的接口的信息
- GetMember(): GetMembers()返回MemberInfo類型,用于取得該類的所有成員的信息
- GetMethod(): GetMethods()返回MethodInfo類型,用于取得該類的方法的信息
- GetProperty(): GetProperties()返回PropertyInfo類型,用于取得該類的屬性的信息
- 可以調(diào)用這些成員,其方式是調(diào)用Type的InvokeMember()方法,或者調(diào)用MethodInfo, PropertyInfo和其他類的Invoke()方法。
反射的具體使用
(1)首先定義一個(gè)類用來測試反射的各種常見操作
public class NewClass
{
//定義各個(gè)字段
public string a;
public int b;
public string Name { get; set; }
public int Age { get; set; }
//構(gòu)造函數(shù)1
public NewClass(string a,int b)
{
this.a = a;
this.b = b;
}
//構(gòu)造函數(shù)2
public NewClass()
{
Console.WriteLine("調(diào)用構(gòu)造函數(shù)");
}
//show方法,顯示具體的字段值
public void show()
{
Console.WriteLine("生成一個(gè)對象成功,其中a是:" + a +" b是:"+ b +" Name是:"+ this.Name+" Age是:" + this.Age);
}
}
(2)利用反射查看類中的構(gòu)造方法:
//client
{
//測試類的對象實(shí)例化
NewClass nc = new NewClass();
//獲取Type的引用
Type t = nc.GetType();
//獲取所有的構(gòu)造器
ConstructorInfo[] ci = t.GetConstructors();
//遍歷構(gòu)造器
foreach (var c in ci)
{
Console.WriteLine("count");
//獲取構(gòu)造器中的形參信息
ParameterInfo[] pi = c.GetParameters();
//遍歷形參信息
foreach (ParameterInfo p in pi)
{
//Write輸出不換行
Console.Write("形參類型:" + p.ParameterType.ToString() + " |" + "形參名字:" + p.Name + ",");
}
//WriteLine輸出換行
Console.WriteLine();
}
}
(3)用構(gòu)造函數(shù)動(dòng)態(tài)生成對象
//client
{
//獲取Type的引用
Type t = typeof(NewClass);
//根據(jù)對應(yīng)構(gòu)造器的形參類型,構(gòu)造Types數(shù)組
Type[] types = new Type[2];
types[0] = typeof(string);
types[1] = typeof(int);
//傳入Types數(shù)組,找到對應(yīng)的構(gòu)造函數(shù)
ConstructorInfo constructorInfo = t.GetConstructor(types);
//根據(jù)對應(yīng)構(gòu)造器對應(yīng)的實(shí)參值,構(gòu)造object數(shù)組
object[] objs = new object[2] { "5", 6 };
//傳入object數(shù)組,調(diào)用構(gòu)造函數(shù)生成對象
object o = constructorInfo.Invoke(objs);
//對應(yīng)的對象字段賦值
((NewClass)o).Name = "carson";
((NewClass)o).Age = 20;
//調(diào)用對應(yīng)對象的方法
((NewClass)o).show();
}
(4)用Activator生成對象**【利用Activator的CreateInstance靜態(tài)方法**】
Activator
類在反射中使用時(shí),由于是在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建對象,因此性能可能不如直接使用構(gòu)造函數(shù)。在大部分情況下,最好直接使用類型的構(gòu)造函數(shù)來創(chuàng)建對象,而只在必要時(shí)才使用
Activator
類的功能
//client
{
//獲取相應(yīng)類的Type引用
Type t = typeof(NewClass);
//法一:構(gòu)造函數(shù)的參數(shù)
object[] objs = new object[2] { "hello", 110 };
//法一:用Activator的CreateInstance靜態(tài)方法(調(diào)用有參構(gòu)造方法,傳入object數(shù)組),生成新對象
object o = Activator.CreateInstance(t,objs);
//法二:用Activator的CreateInstance靜態(tài)方法(調(diào)用有參構(gòu)造方法,不直接傳入object數(shù)組),生成新對象
object o = Activator.CreateInstance(t, "hello", 10);
//法三:用Activator的CreateInstance靜態(tài)方法(調(diào)用無參的構(gòu)造方法)
object o = Activator.CreateInstance(t);
//調(diào)用生成對象的方法
((NewClass)o).Name = "carson";
((NewClass)o).Age = 20;
((NewClass)o).show();
}
(5)查看類中的屬性(Property)
//獲取相應(yīng)類的Type引用
Type t = typeof(NewClass);
//獲取屬性
PropertyInfo[] propertyInfos = t.GetProperties();
//遍歷輸出屬性
foreach(PropertyInfo pi in propertyInfos)
{
Console.WriteLine(pi.Name);
}
(6)查看類中的public方法
//獲取相應(yīng)類的Type引用
Type t = typeof(NewClass);
//獲取方法
MethodInfo[] methodInfos = t.GetMethods();
//遍歷輸出方法
foreach(MethodInfo mi in methodInfos)
{
Console.WriteLine(mi.ReturnType+" "+mi.Name);
}
(7)查看類中的public字段(Field)
//獲取相應(yīng)類的Type引用
Type t = typeof(NewClass);
//獲取Public字段
FieldInfo[] fieldInfos = t.GetFields();
//遍歷輸出字段
foreach(FieldInfo fi in fieldInfos)
{
Console.WriteLine(fi.Name);
}
(8)用反射生成對象,并調(diào)用屬性、方法和字段進(jìn)行操作
//獲取相應(yīng)類的Type引用
Type t = typeof(NewClass);
//獲取Public字段
object obj = Activator.CreateInstance(t);
//取得a字段
FieldInfo aField = t.GetField("a");
//給a字段賦值[傳入:實(shí)例對象,賦值的具體值]
aField.SetValue(obj, "hello");
//取得b字段
FieldInfo bField = t.GetField("b");
//給a字段賦值
bField.SetValue(obj, 20);
//取得Name屬性
PropertyInfo NameProperty = t.GetProperty("Name");
//給Name屬性賦值
NameProperty.SetValue(obj, "Carson");
//取得Age屬性
PropertyInfo AgeProperty = t.GetProperty("Age");
//給Age屬性賦值
AgeProperty.SetValue(obj, 22);
//取得show方法
MethodInfo mi = t.GetMethod("show");
//調(diào)用show方法
mi.Invoke(obj, null);
c# 預(yù)處理指令
在C#中,預(yù)處理指令是用于在代碼編譯之前執(zhí)行一些特定操作的指令,它們以#
字符開頭,不以;
結(jié)尾。
這些指令用于控制編譯過程,并可以在代碼中進(jìn)行條件編譯,根據(jù)不同的條件選擇性地包含或排除代碼。
以下是一些常見的C#預(yù)處理指令及其示例:
-
#define
和#undef
:-
#define
用于定義一個(gè)符號(hào),可以在代碼中用于條件編譯。 -
#undef
用于取消已定義的符號(hào)。
#define DEBUG using System; class Program { static void Main() { #if DEBUG Console.WriteLine("Debug mode is enabled."); #else Console.WriteLine("Debug mode is not enabled."); #endif #undef DEBUG #if DEBUG Console.WriteLine("This will not be compiled."); #else Console.WriteLine("Debug mode is not enabled."); #endif } }
-
-
#if
、#elif
和#else
:-
#if
用于根據(jù)符號(hào)是否已定義來包含或排除代碼塊。 -
#elif
用于在多個(gè)條件之間選擇一個(gè)。 -
#else
用于在沒有任何條件匹配時(shí)執(zhí)行。
#define DEBUG #define VERSION1 using System; class Program { static void Main() { #if (DEBUG && !VERSION1) Console.WriteLine("Debug mode is enabled."); #elif (!DEBUG && VERSION1) Console.WriteLine("Version 1 is enabled."); #else Console.WriteLine("Debug mode is not enabled, and Version 1 is not enabled."); #endif } }
-
-
#warning
和#error
:-
#warning
用于生成警告消息。 -
#error
用于生成編譯錯(cuò)誤消息。
#define VERSION1 using System; class Program { static void Main() { #if !VERSION1 #warning This code is for a different version. #error This code should not be compiled for the current version. #endif Console.WriteLine("Some code here."); } }
-
這些預(yù)處理指令允許您在不同的條件下控制代碼的編譯行為,這在處理不同配置和環(huán)境的代碼時(shí)非常有用。
請注意,預(yù)處理指令在編譯時(shí)處理,因此編譯后的可執(zhí)行文件將不包含未滿足條件的代碼塊。這有助于優(yōu)化和定制您的應(yīng)用程序的構(gòu)建。
C#結(jié)構(gòu)體
在 C# 中,struct 是表示數(shù)據(jù)結(jié)構(gòu)的值類型數(shù)據(jù)類型
。它們在棧上分配內(nèi)存,而不是在堆上,這有助于提高性能。然而,由于是值類型,結(jié)構(gòu)體的拷貝會(huì)導(dǎo)致值的復(fù)制,因此在某些情況下可能不適用于大型對象。
它可以包含參數(shù)化構(gòu)造函數(shù)、靜態(tài)構(gòu)造函數(shù)、常量、字段、方法、屬性、索引器、運(yùn)算符、事件和嵌套類型。
struct 可用于保存不需要繼承的小數(shù)據(jù)值,例如 坐標(biāo)點(diǎn)、鍵值對和復(fù)雜的數(shù)據(jù)結(jié)構(gòu)。
結(jié)構(gòu)體聲明
使用 struct 關(guān)鍵字聲明結(jié)構(gòu)。 默認(rèn)訪問修飾符是internal
以下示例聲明了一個(gè)結(jié)構(gòu) Coordinate。
struct Coordinate
{
public int x;
public int y;
}
//使用 new 關(guān)鍵字創(chuàng)建了 Coordinate 結(jié)構(gòu)的對象。
//它調(diào)用結(jié)構(gòu)的默認(rèn)無參數(shù)構(gòu)造函數(shù),該構(gòu)造函數(shù)將所有成員初始化為其指定數(shù)據(jù)類型的默認(rèn)值。
var point = new Coordinate();
Console.WriteLine(point.x); //輸出: 0
Console.WriteLine(point.y); //輸出: 0
//-------------------------------------------
//如果在不使用 new 關(guān)鍵字的情況下聲明 struct 類型的變量,則它不會(huì)調(diào)用任何構(gòu)造函數(shù),因此所有成員都保持未分配狀態(tài)。 因此,您必須在訪問它們之前為每個(gè)成員賦值,否則會(huì)產(chǎn)生編譯時(shí)錯(cuò)誤。
struct Coordinate
{
public int x;
public int y;
}
Coordinate point;
Console.Write(point.x); // 編譯錯(cuò)誤
point.x = 10;
point.y = 20;
Console.Write(point.x); //輸出: 10
Console.Write(point.y); //輸出: 20
結(jié)構(gòu)體的構(gòu)造器
結(jié)構(gòu)不能包含無參數(shù)構(gòu)造函數(shù)。 它只能包含參數(shù)化構(gòu)造函數(shù)或靜態(tài)構(gòu)造函數(shù)。
struct Coordinate
{
public int x;
public int y;
//必須在參數(shù)化構(gòu)造函數(shù)中包含結(jié)構(gòu)體的所有成員,并為它賦值; 否則,如果任何成員未配賦值的話,C# 編譯器將給出編譯時(shí)錯(cuò)誤。
public Coordinate(int x, int y)
{
this.x = x;
this.y = y;
}
public static Coordinate GetOrigin()
{
return new Coordinate();
}
}
Coordinate point = new Coordinate(10, 20);
Console.WriteLine(point.x); //輸出: 10
Console.WriteLine(point.y); //輸出: 20
//---------------
Coordinate point = Coordinate.GetOrigin();
Console.WriteLine(point.x); //輸出: 0
Console.WriteLine(point.y); //輸出: 0
C#枚舉
在 C# 中,枚舉(Enum)是一種用于定義命名整數(shù)常數(shù)集合的數(shù)據(jù)類型。
在 C# 中,枚舉 用于將常量名稱分配給一組數(shù)字整數(shù)值。
它使常量值更具可讀性,例如,當(dāng)引用一周中的某一天時(shí),WeekDays.Monday 比數(shù)字 0 更具可讀性。
以下是一個(gè)簡單的枚舉的定義示例:
public enum DaysOfWeek{
Sunday, // 默認(rèn)值為 0
Monday, // 默認(rèn)值為 1
Tuesday, // 默認(rèn)值為 2
Wednesday, // 默認(rèn)值為 3
Thursday, // 默認(rèn)值為 4
Friday, // 默認(rèn)值為 5
Saturday // 默認(rèn)值為 6
}
枚舉值
在這個(gè)示例中,DaysOfWeek
枚舉表示一周中的每一天,每個(gè)成員都有一個(gè)與其關(guān)聯(lián)的整數(shù)值,默認(rèn)從 0 開始遞增。如果你不為枚舉成員指定特定的值,它們的值將按照默認(rèn)規(guī)則遞增,從 0 開始。
您可以為枚舉成員分配不同的值。 枚舉成員默認(rèn)值的更改將自動(dòng)按順序?qū)⒃隽恐捣峙浣o其他成員。
enum Categories
{
Electronics, // 0
Food, // 1
Automotive = 6, // 6
Arts, // 7
BeautyCare, // 8
Fashion // 9
}
枚舉可以是任何數(shù)字?jǐn)?shù)據(jù)類型,例如 byte、sbyte、short、ushort、int、uint、long 或 ulong。 但是,枚舉不能是字符串類型。
事件
what
當(dāng)一個(gè)對象發(fā)生重要事件時(shí),通常需要通知其他對象來執(zhí)行相應(yīng)的操作。
事件使對象或類具備通知能力的成員,以手機(jī)(對象)舉例:手機(jī)可以響鈴,響鈴(事件)使手機(jī)具備了通知能力。
事件的作用: 事件是類發(fā)送通知或信息到其它類的一種溝通機(jī)制。C# 中使用事件機(jī)制實(shí)現(xiàn)線程間的通信。
事件是委托的一個(gè)子集,為了滿足“廣播/訂閱”模式的需求而生。
為觸發(fā)事件(To raise an event),需要一個(gè)事件發(fā)布者,為接收和處理事件,需要一個(gè)訂閱者或多個(gè)訂閱者。
這些通常是由發(fā)布者類和訂閱者類來進(jìn)行實(shí)現(xiàn)。【觀察者設(shè)計(jì)模式】
我們使用事件:
1、解耦我們的應(yīng)用程序,或者說是松耦合;在沒有破壞已有代碼的情況下,松耦合的程序容易擴(kuò)展并達(dá)到我們想要做的;
2、用于對象之間聯(lián)系的運(yùn)行機(jī)制;
3、在不改變已有代碼的情況下,提供一種簡單而有效的方式去擴(kuò)展應(yīng)用程序;
定義
定義一個(gè)事件需要使用event
關(guān)鍵字,事件依賴于委托,
故其后是一個(gè)事件依賴的委托類型(可以是通用委托類型,也可以是自定義的委托類型)。
與定義委托一樣,只不過比它多了一個(gè)關(guān)鍵字event
。
// define a delegage(聲明一個(gè)事件依賴的委托)
public delegate void FoodPreparedEventHandler(object source, EventArgs args);
// declare the event(定義一個(gè)事件)
//通常,事件的名稱以`On`開頭,但也不是必須的。
public event FoodPreparedEventHandler FoodPrepared;
訂閱/取訂事件
事件以發(fā)布-訂閱模式進(jìn)行工作,那意味著一旦我們訂閱了事件,只有服務(wù)在訂閱就在。
但是有時(shí)候生意邏輯就規(guī)定了可以訂閱也可以取消。比如,用戶可能有權(quán)選擇只接收app通知或只接收郵件通知。
我們要能夠訂閱到我們想要訂閱的事件,可以使用+=
運(yùn)算符:
我們要能夠取消訂閱之前訂閱的事件,可以使用-=
運(yùn)算符:
//訂閱
orderingService.FoodPrepared += appService.OnFoodPrepared;
orderingService.FoodPrepared += mailService.OnFoodPrepared;
//取消訂閱
orderingService.FoodPrepared -= appService.OnFoodPrepared;
orderingService.FoodPrepared -= mailService.OnFoodPrepared;
委托的綁定解綁VS事件的訂閱/取訂:
- 委托的綁定和解綁方法可以使用
賦值運(yùn)算符=
,也有使用+=
和-=
等運(yùn)算符。- 事件的訂閱和取訂則只有使用
+=
和-=
運(yùn)算符。
事件運(yùn)行的底層原理
當(dāng)不使用event(事件)的應(yīng)用程序:
以一個(gè)食品服務(wù)程序?yàn)槔?/p>
其中的Order類
是包含食品類目名稱和成分;
其中的FoodOrderingService類
是用于處理食品預(yù)訂的真正服務(wù)類;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
//正如我們看到的,我們拿到了需要準(zhǔn)備的菜單,模擬4秒鐘的等待時(shí)間以進(jìn)行準(zhǔn)備,然后我們發(fā)送了一條通知到用戶程序:預(yù)訂在準(zhǔn)備。
//當(dāng)然,這僅僅是個(gè)測試所以示例相當(dāng)簡單,我們用console模擬通知消息。在實(shí)際的應(yīng)用中會(huì)包含很多步驟。
namespace Order
{
class FoodOrderingService
{ //調(diào)用服務(wù)來購買一個(gè)帶很多奶酪的pizza:
static void Main(string[] args)
{
var order = new Order { Item = "Pizza with extra cheese" };
var orderingService = new FoodOrderingService();
orderingService.PrepareOrder(order);
}
//食品準(zhǔn)備服務(wù)
public void PrepareOrder(Order order)
{
Console.WriteLine("Preparing your order '{0}',please wait ......", order.Item);
Thread.Sleep(4000);
AppService _appService = new AppService();
_appService.SendAppNotification();
}
}
//發(fā)送通知的服務(wù)類
public class AppService
{
public void SendAppNotification()
{
Console.WriteLine("AppService:your food is prepared!");
}
}
//食品訂單類
public class Order
{
public string Item { get; set; }
public string Ingredients{get;set;}
}
}
但是,我們決定擴(kuò)展程序用email通知用戶他們的訂餐準(zhǔn)備好了。
為達(dá)到上面的目的,需擴(kuò)展service類代碼并修改相關(guān)代碼,結(jié)果如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace Order
{
class FoodOrderingService
{
static void Main(string[] args)
{
var order = new Order { Item = "Pizza with extra cheese"};
var orderingService = new FoodOrderingService();
orderingService.PrepareOrder(order);
Console.ReadKey();
}
public void PrepareOrder(Order order)
{
Console.WriteLine("Preparing your order '{0}',please wait ......", order.Item);
Thread.Sleep(4000);
AppService _appService = new AppService();
_appService.SendAppNotification();
MailService _mailService = new MailService();
_mailService.SendEmailNotification();
}
}
public class AppService
{
public void SendAppNotification()
{
Console.WriteLine("AppService:your food is prepared!");
}
}
public class MailService
{
public void SendEmailNotification()
{
Console.WriteLine("MailService:your food is prepared.");
}
}
public class Order
{
public string Item { get; set; }
public string Ingredients{get;set;}
}
}
這很容易給我們的程序引入bug,甚至是如果我們寫了個(gè)單元測試,我們可能要重新梳理并修改代碼。
同理,我們在FoodOrderingService類中同時(shí)引入appservice 和 mailservice函數(shù)來發(fā)送通知消息,創(chuàng)建了一個(gè)緊密相聯(lián)的耦合程序。
這種實(shí)現(xiàn)方法不是我們期望的!我們嘗試使用event事件提升一下這個(gè)示例,這是引入發(fā)布-訂閱模式的完美場合.
當(dāng)使用了event(事件)的應(yīng)用程序:
看一下如何運(yùn)用委托和事件并用上面的示例來演示event事件。
創(chuàng)建事件發(fā)布者。對于事件發(fā)布者,我們需要做:
1、定義一個(gè)委托;
2、定義一個(gè)依賴委托的事件;
3、觸發(fā)事件;
1.定義一個(gè)事件依賴的委托
事件中定義的委托通常有2個(gè)參數(shù):
第一個(gè)參數(shù)是事件源(表示觸發(fā)事件的那個(gè)組件 如(button/label/listview…),比如說你單擊button,那么sender就是button),更確切地說是將要發(fā)布事件的那個(gè)類;
第二個(gè)參數(shù)EventArgs(它用來輔助你處理事件,比如說你用鼠標(biāo)點(diǎn)擊窗體,那么EventArgs是會(huì)包含點(diǎn)擊的位置等等),是與事件相關(guān)的任何其它數(shù)據(jù)。
通常來說,我們會(huì)給委托起一個(gè)描述性的名字,比如“FoodPrepared”,然后在名字末尾添加上“EventHandler”。
無論委托名稱怎么變化,人們都能很容易了解這是個(gè)委托。
public class FoodOrderingService
{
// define a delegate(聲明一個(gè)委托)
//委托FoodPreparedEventHandler返回void
//事件是FoodPreparedEventHandler類型,因?yàn)槲覀兌x的是一旦操作完成就會(huì)觸發(fā)的事件,所以給它起了個(gè)過去時(shí)的名字---FoodPrepared。
public delegate void FoodPreparedEventHandler(object source, EventArgs args);
...
}
2.再定義一個(gè)依賴委托的事件
定義一個(gè)事件需要使用
event
關(guān)鍵字,其后是一個(gè)委托類型(可以是通用委托類型,也可以是自定義的委托類型)。與定義委托一樣,只不過比它多了一個(gè)關(guān)鍵字event。
public class FoodOrderingService
{
// define a delegage(聲明一個(gè)事件依賴的委托)
public delegate void FoodPreparedEventHandler(object source, EventArgs args);
// declare the event(定義一個(gè)事件)
//通常,事件的名稱以`On`開頭,但也不是必須的。
public event FoodPreparedEventHandler FoodPrepared;
...
}
3.觸發(fā)事件,我們再創(chuàng)建一個(gè)用于觸發(fā)事件的方法函數(shù)
public class FoodOrderingService
{ //delegate
public delegate void FoodPreparedEventHandler(object source, EventArgs args);
//event
public event FoodPreparedEventHandler FoodPrepared;
//被調(diào)用的函數(shù)
public void PrepareOrder(Order order)
{
Console.WriteLine($"Preparing your order '{order.Item}', please wait...");
Thread.Sleep(4000);
//調(diào)用觸發(fā)事件的方法函數(shù)
OnFoodPrepared();
}
//觸發(fā)事件的方法函數(shù)
//按照慣例,方法函數(shù)的修飾符應(yīng)該是protected virtual void,名稱前綴加“On”。
protected virtual void OnFoodPrepared()
{
//觸發(fā)事件(調(diào)用委托(調(diào)用委托綁定的具體方法))
//函數(shù)體內(nèi)部,我們檢查是否有訂閱者(FoodPrepared != null),如果有訂閱者,我們就調(diào)用事件,將this做為參數(shù)傳遞,this是當(dāng)前類;null做為事件參數(shù)。
if (FoodPrepared != null)
FoodPrepared(this, null);
}
}
創(chuàng)建訂閱者:
1.創(chuàng)建AppService
類和MailService
類
//訂閱者1
public class AppService
{
//其中的事件處理方法
public void OnFoodPrepared(object source, EventArgs eventArgs)
{
Console.WriteLine("AppService: your food is prepared.");
}
}
//訂閱者2
public class MailService
{
//其中的事件處理方法
public void OnFoodPrepared(object source, EventArgs eventArgs)
{
Console.WriteLine("MailService: your food is prepared.");
}
}
2.現(xiàn)在實(shí)例化AppService類
和MailService
類,并訂閱到FoodPrepared
事件。
用 += 運(yùn)算符可以訂閱到事件,在示例中我們將訂閱者
AppService類
和訂閱者MailService類
訂閱到FoodPrepared事件中,用AppService
類中的OnFoodPrepared
函數(shù)以及MailService
類中的OnFoodPrepared函數(shù)
來處理事件。
static void Main(string[] args)
{
//訂單對象初始化
var order = new Order { Item = "Pizza with extra cheese" };
//發(fā)布者
var orderingService = new FoodOrderingService();
//訂閱者
var appService = new AppService();
var mailService = new MailService();
//訂閱事件
orderingService.FoodPrepared += appService.OnFoodPrepared;
orderingService.FoodPrepared += mailService.OnFoodPrepared;
//調(diào)用對應(yīng)方法,其中觸發(fā)了事件
orderingService.PrepareOrder(order);
Console.ReadKey();
}
3.故完整代碼如下:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading;
6
7 namespace Order
8 {
//client
9 class Program
10 {
11 static void Main(string[] args)
12 {
13 var order = new Order { Item = "Pizza with extra cheese" };
14 var orderingService = new FoodOrderingService();
15 var appService = new AppService();
16 var mailService = new MailService();
17
18 orderingService.FoodPrepared += appService.OnFoodPrepared;
19 orderingService.FoodPrepared += mailService.OnFoodPrepared;
20
21 orderingService.PrepareOrder(order);
22 Console.ReadKey();
23 }
24 }
25 //發(fā)布者
26 class FoodOrderingService
27 {
28 //定義委托
29 public delegate void FoodPreparedEventHandler(object source, EventArgs args);
30 //聲明事件
31 public event FoodPreparedEventHandler FoodPrepared;
32
33 public void PrepareOrder(Order order)
34 {
35
36 Console.WriteLine("Preparing your order '{0}',please wait ......", order.Item);
37 Thread.Sleep(4000);
38 //調(diào)用觸發(fā)事件的方法
39 OnFoodPrepared();
40 }
41 //觸發(fā)事件的函數(shù)方法
42 protected virtual void OnFoodPrepared()
43 {
44 if (FoodPrepared != null)
45 {
46 FoodPrepared(this,null);
47 }
48 }
49 }
//訂閱者
50 public class AppService
51 {
52 public void OnFoodPrepared(object source ,EventArgs eventArgs)
53 {
54 Console.WriteLine("AppService:your food is prepared!");
55 }
56 }
//訂閱者
57 public class MailService
58 {
59 public void OnFoodPrepared(object sorece , EventArgs eventArgs)
60 {
61 Console.WriteLine("MailService:your food is prepared.");
62 }
63 }
64 //訂單類
65 public class Order
66 {
67 public string Item { get; set; }
68 public string Ingredients{get;set;}
69 }
70
71
72 }
可以像這樣無限地?cái)U(kuò)展我們的程序,我們也可以將FoodOrderingService
類移到其它類庫中或我們想要的地方。
擴(kuò)展EventArgs參數(shù)
像前面提到的那樣,之前我們用EventArgs發(fā)送事件數(shù)據(jù)。
但是其實(shí)我們能創(chuàng)建自EventArgs繼承的自定義事件參數(shù)類:FoodPreparedEventArgs,自定義格式內(nèi)容,從而用于發(fā)送數(shù)據(jù)到訂閱者。
//擴(kuò)展和繼承自EventArgs的自定義事件參數(shù)類:FoodPreparedEventArgs
1 public class FoodPreparedEventArgs : EventArgs
2 {
//自定義字段,其是發(fā)送給訂閱者的數(shù)據(jù)
3 public Order Order { get; set; }
4 }
然后,我們修改發(fā)布者FoodOrderingService
中的事件參數(shù)類型:
//現(xiàn)在我們正發(fā)送order數(shù)據(jù)(OnFoodPrepared(order);)到訂閱者。
//用FoodPreparedEventArgs代替了EventArgs并將order信息傳遞給了訂閱者。
public class FoodOrderingService
{
//事件參數(shù)替換為:FoodPreparedEventArgs
public delegate void FoodPreparedEventHandler(object source, FoodPreparedEventArgs args);
public event FoodPreparedEventHandler FoodPrepared;
public void PrepareOrder(Order order)
{
Console.WriteLine($"Preparing your order '{order.Item}', please wait...");
Thread.Sleep(4000);
OnFoodPrepared(order);
}
protected virtual void OnFoodPrepared(Order order)
{
if (FoodPrepared != null)
FoodPrepared(this, new FoodPreparedEventArgs { Order = order });
}
}
修改訂閱者中的事件參數(shù)類型:
//AppService類
public class AppService
{
public void OnFoodPrepared(object source ,FoodPreparedEventArgs eventArgs)
{
Console.WriteLine($"AppService: your food '{eventArgs.Order.Item}' is prepared.");
}
}
//MailService類
public class MailService
{
public void OnFoodPrepared(object sorece , FoodPreparedEventArgs eventArgs)
{
Console.WriteLine($"MailService: your food '{eventArgs.Order.Item}' is prepared.");
}
}
完整代碼如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace Order
{
class Program
{
static void Main(string[] args)
{
var order = new Order { Item = "Pizza with extra cheese" };
var orderingService = new FoodOrderingService();
var appService = new AppService();
var mailService = new MailService();
orderingService.FoodPrepared += appService.OnFoodPrepared;
orderingService.FoodPrepared += mailService.OnFoodPrepared;
orderingService.PrepareOrder(order);
Console.ReadKey();
}
}
class FoodOrderingService
{
//定義委托
public delegate void FoodPreparedEventHandler(object source, FoodPreparedEventArgs args);
//聲明事件
public event FoodPreparedEventHandler FoodPrepared;
public void PrepareOrder(Order order)
{
Console.WriteLine("Preparing your order '{0}',please wait ......", order.Item);
Thread.Sleep(4000);
//觸發(fā)事件
OnFoodPrepared(order);
}
//觸發(fā)事件的函數(shù)方法
protected virtual void OnFoodPrepared(Order order)
{
if (FoodPrepared != null)
{
FoodPrepared(this, new FoodPreparedEventArgs { Order = order});
}
}
}
public class AppService
{
public void OnFoodPrepared(object source, FoodPreparedEventArgs eventArgs)
{
Console.WriteLine("AppService:your food {0} is prepared!",eventArgs.Order.Item);
}
}
public class MailService
{
public void OnFoodPrepared(object sorece , FoodPreparedEventArgs eventArgs)
{
Console.WriteLine("MailService:your food {0} is prepared!", eventArgs.Order.Item);
}
}
public class Order
{
public string Item { get; set; }
public string Ingredients{get;set;}
}
//自定義的事件參數(shù)類
public class FoodPreparedEventArgs : EventArgs
{
public Order Order { get; set; }
}
}
用EventHandler類來簡化事件使用
現(xiàn)在,通過**.net**用一些適合我們的現(xiàn)代方式修改代碼,在C#中使用event handler。
- 主要使用
EventHandler
和EventHandler<TEventArgs>
創(chuàng)建事件,他們是專門的封裝器,可以簡化事件的創(chuàng)建。
EventHandler
類似于提供了的已經(jīng)封裝好了的事件依賴的委托類型。
EventHandler<TEventArgs>
類<>的TEventArgs
代表自定義的事件相關(guān)參數(shù)類型。
-
使用了傳統(tǒng)的
Invoke()
函數(shù)來觸發(fā)事件,避免復(fù)雜的null檢查,可以清理項(xiàng)目冗余代碼。
所以現(xiàn)在我們通過添加event handler修改上面的FoodOrderingService
類:
public class FoodOrderingService
{
//使用EventHandler類型創(chuàng)建事件(委托)
//現(xiàn)在,用于代替委托和事件的兩種聲明,我們用EventHandler和FoodPreparedEventArgs,這樣可以使代碼更簡潔,更有可讀性。 //這可能是你在其它項(xiàng)目中使用事件時(shí)會(huì)看到的。
public event EventHandler<FoodPreparedEventArgs> FoodPrepared;
public void PrepareOrder(Order order)
{
Console.WriteLine($"Preparing your order '{order.Item}', please wait...");
Thread.Sleep(4000);
OnFoodPrepared(order);
}
protected virtual void OnFoodPrepared(Order order)
{
//使用了傳統(tǒng)的Invoke()函數(shù)來觸發(fā)事件,避免復(fù)雜的null檢查,可以清理項(xiàng)目冗余代碼。
FoodPrepared?.Invoke(this, new FoodPreparedEventArgs { Order = order });
}
}
c#流(stream)
C# 包括以下標(biāo)準(zhǔn) IO(輸入/輸出)類,用于從文件、內(nèi)存、網(wǎng)絡(luò)、隔離存儲(chǔ)等不同來源讀取/寫入。
字節(jié)流
Stream:System.IO.Stream 是一個(gè)抽象類,它提供了將字節(jié)(讀取、寫入等)傳輸?shù)皆吹臉?biāo)準(zhǔn)方法。它就像一個(gè)包裝類來傳輸字節(jié)。需要從特定源讀取/寫入字節(jié)的類必須實(shí)現(xiàn) Stream 類。
以下字節(jié)類繼承 Stream 類以提供從特定源讀取/寫入字節(jié)的功能:
FileStream: 從/向物理文件讀取或?qū)懭胱止?jié),無論它是 .txt、.exe、.jpg 還是任何其他文件。 FileStream 派生自 Stream 類。
MemoryStream:MemoryStream 讀取或?qū)懭氪鎯?chǔ)在內(nèi)存中的字節(jié)。
BufferedStream:BufferedStream 從其他流讀取或?qū)懭胱止?jié)以提高某些 I/O 操作的性能。
NetworkStream:NetworkStream 從網(wǎng)絡(luò)套接字讀取或?qū)懭胱止?jié)。
PipeStream:PipeStream 從不同進(jìn)程讀取或?qū)懭胱止?jié)。
CryptoStream:CryptoStream 用于將數(shù)據(jù)流鏈接到加密轉(zhuǎn)換。
字符流
StreamReader:StreamReader 是一個(gè)輔助類,用于通過使用編碼值將字節(jié)轉(zhuǎn)換為字符來從 Stream 中讀取字符。 它可用于從不同的流(如 FileStream、MemoryStream 等)讀取字符串(字符)。
StreamWriter:StreamWriter 是一個(gè)幫助類,用于通過將字符轉(zhuǎn)換為字節(jié)來將字符串寫入 Stream。 它可用于將字符串寫入不同的流,例如 FileStream、MemoryStream 等。
BinaryReader:BinaryReader 是一個(gè)幫助類,用于從字節(jié)讀取原始數(shù)據(jù)類型。
BinaryWriter:BinaryWriter 以二進(jìn)制形式寫入原始類型。
舉個(gè)例子:
上圖顯示 FileStream 從物理文件中讀取字節(jié),然后 StreamReader 通過將這些字節(jié)轉(zhuǎn)換為字符串來讀取字符串。 同理,StreamWriter 將字符串轉(zhuǎn)換為字節(jié)寫入 FileStream,然后 FileStream 將字節(jié)寫入物理文件。
因此,F(xiàn)ileStream 處理字節(jié),而 StreamReader 和 StreamWriter 處理字符串。
C# 文件操作
C# 提供了下面的這些類用來操作文件,它們可以用來訪問文件夾,文件,打開文件,寫入文件,創(chuàng)建文件, 移動(dòng)文件,列出目錄下所有的文件等等。
類名 | 用法 |
---|---|
File | File 是一個(gè)靜態(tài)類,提供各種功能,如復(fù)制、創(chuàng)建、移動(dòng)、刪除、打開以讀取或/寫入、加密或解密、檢查文件是否存在、添加新的文件內(nèi)容、獲取最后訪問權(quán)限 時(shí)間等 |
FileInfo | FileInfo 類提供與靜態(tài) File 類相同的功能。 通過手動(dòng)編寫代碼來讀取或?qū)懭胛募械淖止?jié),您可以更好地控制如何對文件進(jìn)行讀/寫操作。 |
Directory | Directory 是一個(gè)靜態(tài)類,提供創(chuàng)建、移動(dòng)、刪除和訪問子目錄的功能。 |
DirectoryInfo | DirectoryInfo 提供了創(chuàng)建、移動(dòng)、刪除和訪問子目錄的實(shí)例方法。 |
Path | Path 是一個(gè)靜態(tài)類,它提供檢索文件擴(kuò)展名、更改文件擴(kuò)展名、檢索絕對物理路徑以及其他與路徑相關(guān)的功能等功能。 |
File類
靜態(tài) File 類包括各種實(shí)用方法來與任何類型的物理文件進(jìn)行交互,例如二進(jìn)制,文本等
使用這個(gè)靜態(tài) File 類對物理文件執(zhí)行一些快速操作。
File是在System.IO下面的,所以我們得引用這個(gè)命名空間:
using System.IO;
File 的重要方法:
方法 | 用法 |
---|---|
AppendAllLines | 在文件中追加行,然后關(guān)閉文件。如果指定的文件不存在,此方法會(huì)創(chuàng)建一個(gè)文件,將指定的行寫入文件,然后關(guān)閉文件。 |
AppendAllText | 打開文件,將指定的字符串附加到文件,然后關(guān)閉文件。如果文件不存在,此方法會(huì)創(chuàng)建一個(gè)文件,將指定的字符串寫入文件,然后關(guān)閉文件。 |
AppendText | 創(chuàng)建一個(gè) StreamWriter,它將 UTF-8 編碼的文本附加到現(xiàn)有文件,如果指定的文件不存在,則附加到新文件。 |
Copy | 將現(xiàn)有文件復(fù)制到新文件。不允許覆蓋同名文件。 |
Create | 創(chuàng)建或覆蓋指定路徑中的文件。 返回一個(gè)FileStream |
CreateText | 創(chuàng)建或打開用于寫入 UTF-8 編碼文本的文件。 返回一個(gè)StreamWriter |
Decrypt | 解密當(dāng)前帳戶使用加密方法加密的文件。 (依賴于CSP) |
Delete | 刪除指定的文件。 |
Encrypt | 加密文件,以便只有用于加密文件的帳戶才能對其進(jìn)行解密。 (依賴于CSP) |
Exists | 確定指定的文件是否存在。 |
Move | 將指定文件移動(dòng)到新位置,提供指定新文件名的選項(xiàng)。 |
Open | 在指定路徑上打開一個(gè)具有讀/寫訪問權(quán)限的 FileStream。 |
ReadAllBytes | 打開二進(jìn)制文件,將文件內(nèi)容讀入字節(jié)數(shù)組,然后關(guān)閉文件。 |
ReadAllLines | 打開一個(gè)文本文件,讀取文件的所有行,然后關(guān)閉文件。 |
ReadAllText | 打開一個(gè)文本文件,讀取文件的所有行,然后關(guān)閉文件。 |
Replace | 用另一個(gè)文件的內(nèi)容替換指定文件的內(nèi)容,刪除原始文件,并創(chuàng)建替換文件的備份。 |
WriteAllBytes | 創(chuàng)建一個(gè)新文件,將指定的字節(jié)數(shù)組寫入文件,然后關(guān)閉文件。如果目標(biāo)文件已經(jīng)存在,它會(huì)被覆蓋。 |
WriteAllLines | 創(chuàng)建一個(gè)新文件,將一組字符串寫入該文件,然后關(guān)閉該文件。 |
WriteAllText | 創(chuàng)建一個(gè)新文件,將指定的字符串寫入文件,然后關(guān)閉文件。如果目標(biāo)文件已經(jīng)存在,它會(huì)被覆蓋。 |
FileInfo類
FileInfo 類提供與靜態(tài) File 類相同的功能,但您可以通過手動(dòng)編寫代碼來從文件讀取或?qū)懭胱止?jié),從而更好地控制文件的讀/寫操作。
其重要屬性和方法:
屬性 | 說明 |
---|---|
Directory | 獲取父目錄的實(shí)例。 |
DirectoryName | 獲取表示目錄完整路徑的字符串。 |
Exists | 獲取一個(gè)值,該值指示文件是否存在。 |
Extension | 獲取表示文件擴(kuò)展名部分的字符串。 |
FullName | 獲取目錄或文件的完整路徑。 |
IsReadOnly | 獲取或設(shè)置一個(gè)值,該值確定當(dāng)前文件是否為只讀。 |
LastAccessTime | 獲取或設(shè)置上次訪問當(dāng)前文件或目錄的時(shí)間 |
LastWriteTime | 獲取或設(shè)置當(dāng)前文件或目錄上次寫入的時(shí)間 |
Length | 獲取當(dāng)前文件的大小(以字節(jié)為單位)。 |
Name | 獲取文件的名稱。 |
方法 | 說明 |
---|---|
AppendText | 創(chuàng)建一個(gè) StreamWriter,它將文本附加到由 FileInfo 的此實(shí)例表示的文件。 |
CopyTo | 將現(xiàn)有文件復(fù)制到新文件,不允許覆蓋現(xiàn)有文件。 |
Create | 創(chuàng)建文件。 返回 FileStream |
CreateText | 創(chuàng)建一個(gè)寫入新文本文件的 StreamWriter。 |
Decrypt | 解密當(dāng)前帳戶使用加密方法加密的文件。 (依賴于CSP) |
Delete | 刪除指定的文件。 |
Encrypt | 加密文件,以便只有用于加密文件的帳戶才能對其進(jìn)行解密。 (依賴于CSP) |
MoveTo | 將指定文件移動(dòng)到新位置,提供指定新文件名的選項(xiàng)。 |
Open | 在指定的 FileMode 中打開一個(gè)。 |
OpenRead | 創(chuàng)建一個(gè)只讀的 FileStream。 |
OpenText | 使用 UTF8 編碼創(chuàng)建一個(gè)從現(xiàn)有文本文件讀取的 StreamReader。 |
OpenWrite | 創(chuàng)建一個(gè)只寫的 FileStream。 |
Replace | 用當(dāng)前 FileInfo 對象描述的文件替換指定文件的內(nèi)容,刪除原始文件,并創(chuàng)建替換文件的備份。 |
ToString | 以字符串形式返回路徑。 |
Directory類
操作文件夾的一個(gè)靜態(tài)類。
下面是其常用的一些方法:
法 | 說明 |
---|---|
CreateDirectory(String) | 在指定路徑中創(chuàng)建所有目錄和子目錄,除非它們已經(jīng)存在。 |
Delete(String) | 從指定路徑刪除空目錄。 |
Delete(String, Boolean) | 刪除指定的目錄,并刪除該目錄中的所有子目錄和文件(如果表示)。 |
Exists(String) | 確定給定路徑是否引用磁盤上的現(xiàn)有目錄。 |
GetCurrentDirectory() | 獲取應(yīng)用程序的當(dāng)前工作目錄。 |
GetDirectories(String) | 返回指定目錄中的子目錄的名稱(包括其路徑)。 |
GetDirectories(String, String) | 返回指定目錄中與指定的搜索模式匹配的子目錄的名稱(包括其路徑)。 |
GetDirectories(String, String, EnumerationOptions) | 返回指定目錄中與指定的搜索模式和枚舉選項(xiàng)匹配的子目錄的名稱(包括其路徑)。 |
GetDirectories(String, String, SearchOption) | 返回與在指定目錄中的指定搜索模式匹配的子目錄的名稱(包括其路徑),還可以選擇地搜索子目錄。 |
GetFiles(String) | 返回指定目錄中文件的名稱(包括其路徑) |
GetFiles(String, String) | 返回指定目錄中與指定的搜索模式匹配的文件的名稱(包含其路徑)。 |
GetFiles(String, String, EnumerationOptions) | 返回指定目錄中與指定的搜索模式和枚舉選項(xiàng)匹配的文件的名稱(包括其路徑) |
GetFiles(String, String, SearchOption) | 返回指定目錄中與指定的搜索模式匹配的文件的名稱(包含其路徑),使用某個(gè)值確定是否要搜索子目錄。 |
GetFileSystemEntries(String) | 返回指定路徑中的所有文件和子目錄的名稱。 |
GetFileSystemEntries(String, String) | 返回一個(gè)數(shù)組,其中包含與指定路徑中的搜索模式相匹配的文件名和目錄名稱。 |
GetFileSystemEntries(String, String, EnumerationOptions) | 返回指定路徑中與搜索模式和枚舉選項(xiàng)匹配的文件名和目錄名的數(shù)組。 |
GetFileSystemEntries(String, String, SearchOption) | 返回指定路徑中與搜索模式匹配的所有文件名和目錄名的數(shù)組,還可以搜索子目錄。 |
Move(String, String) | 將文件或目錄及其內(nèi)容移到新位置。 |
DirectoryInfo類
主要用來獲取當(dāng)前的目錄名字為主,因?yàn)槠涓鶧irectoy都是類似的
class Program
{
static async Task Main(string[] args)
{
var info = new DirectoryInfo(@"D:\GitHub\Articles\csharp");
Console.WriteLine(info.Name);
}
}
Path類
合并路徑
static async Task Main(string[] args)
{
var path = Path.Combine("abc", "dd");
Console.WriteLine(path);
}
//windows下 輸出 abc\dd linux 下輸出的是 abc/dd
得到擴(kuò)展名
static async Task Main(string[] args)
{
var extension = Path.GetExtension("asdf/sdfsdf/sdf.png");
Console.WriteLine(extension);
}
得到文件名
static async Task Main(string[] args)
{
var fileName = Path.GetFileName("d:/as/aa/abc.txt");
Console.WriteLine(fileName);//abc.txt
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension("d:/as/aa/abc.txt");
Console.WriteLine(fileNameWithoutExtension);//abc
}
創(chuàng)建臨時(shí)文件
static async Task Main(string[] args)
{
var tempFile = Path.GetTempFileName();
Console.WriteLine(tempFile);
}
異步編程
異步與多線程的關(guān)系
異步是一種編程模型,目的是在等待 I/O 操作時(shí)不阻塞調(diào)用io操作的線程,以提高程序的效率和響應(yīng)性。
多線程是一種并發(fā)編程模型,通過同時(shí)運(yùn)行多個(gè)線程來提高性能,但需要注意線程同步和共享資源的問題,避免競態(tài)條件和死鎖。
綜合考慮:
- 在 I/O 密集型任務(wù)中,異步編程通常更適用,因?yàn)樗軌蚋行У乩觅Y源,提高響應(yīng)性。
- 在 CPU 密集型任務(wù)中,多線程可能更合適,因?yàn)樗梢栽诙鄠€(gè)核上并行執(zhí)行任務(wù),提高計(jì)算性能。
總的來說,選擇異步還是多線程取決于具體的應(yīng)用場景和任務(wù)類型。在現(xiàn)代編程中,往往會(huì)將異步編程和多線程結(jié)合使用,以充分發(fā)揮它們各自的優(yōu)勢。
Task類
概述:
-
Task
是用于表示一個(gè)異步操作的類,通常用于沒有返回值的異步方法。 - 可以通過
Task.Run
或Task.Factory.StartNew
創(chuàng)建 Task 對象,也可以在異步方法中直接返回 Task。 - 選擇異步方法的返回類型取決于你的需求和應(yīng)用場景。通常情況下,推薦使用
Task
或Task<TResult>
,因?yàn)樗鼈兲峁┝艘环N通用的方式來表示異步操作,并且能夠充分利用異步編程的優(yōu)勢。
創(chuàng)建 Task 對象:
// 使用 Task.Run,通常情況下,推薦使用 Task.Run,因?yàn)樗峁┝艘环N簡潔且性能良好的方式來在新的任務(wù)上執(zhí)行操作。
Task task1 = Task.Run(() => SomeMethod());
// 使用 Task.Factory.StartNew
Task task2 = Task.Factory.StartNew(() => SomeMethod(),
CancellationToken.None,
TaskCreationOptions.DenyChildAttach,
TaskScheduler.Default);
創(chuàng)建Task對象時(shí)Task.Run 和 Task.Factory.StartNew 之間的區(qū)別是什么?
-
默認(rèn)調(diào)度器
:-
Task.Run
使用TaskScheduler.Default
,它是線程池調(diào)度器,通常用于執(zhí)行計(jì)算密集型的任務(wù)。 -
Task.Factory.StartNew
默認(rèn)使用TaskScheduler.Current
,它繼承自調(diào)用線程的任務(wù)調(diào)度器,適用于 UI 線程或其他自定義調(diào)度器。
-
-
異常處理
:-
Task.Run
更加友好地處理異常,不會(huì)封裝在AggregateException
中。 -
Task.Factory.StartNew
在沒有明確指定選項(xiàng)的情況下,可能會(huì)將異常封裝在AggregateException
中。
-
-
默認(rèn)選項(xiàng)
:-
Task.Run
使用一組默認(rèn)選項(xiàng),適用于大多數(shù)常見的情況。 -
Task.Factory.StartNew
需要手動(dòng)配置選項(xiàng),例如TaskCreationOptions
和TaskScheduler
。
-
異步方法返回 Task:
public async Task DoAsyncOperation()
{
// 異步操作
}
等待 Task 完成:
Task myTask = DoAsyncOperation();
await myTask;
使用 Task.ContinueWith 處理完成后的任務(wù):
Task myTask = Task.Run(() => SomeMethod());
myTask.ContinueWith(task => Console.WriteLine("Task Completed"));
Task返回異常信息
在異步方法中,如果 Task 返回了異常,可以通過檢查 Task.Exception
屬性來獲取異常信息。
Task myTask = Task.Run(() => SomeMethod());
Console.WriteLine(myTask.Exception);
Task<TResult >類
概述:
-
Task<TResult>
是Task
的泛型版本,用于表示一個(gè)異步操作,其中包含返回值。 - 通過
Task.Run
或Task.Factory.StartNew
創(chuàng)建 Task 對象,或者在異步方法中直接返回Task<TResult>
。
創(chuàng)建 Task 對象:
Task<int> myTask = Task.Run(() => SomeMethodReturningInt());
異步方法返回 Task:
public async Task<int> GetNumberAsync()
{
// 異步操作,并返回一個(gè)整數(shù)結(jié)果
return await SomeAsyncNumberOperation();
}
等待 Task 完成并獲取結(jié)果:
Task<int> myTask = GetNumberAsync();
int result = await myTask;
async與await關(guān)鍵字
在async修飾的方法中,總會(huì)出現(xiàn)await的身影。所以你想拋開async和await中的某一個(gè),去單獨(dú)認(rèn)識(shí)另一個(gè)是很難的。
async概述
async是一個(gè)關(guān)鍵字,同時(shí)也是修飾符(和abstract、static一樣)。
使用async修飾符可以將一個(gè)方法、lambda表達(dá)式或匿名方法指定為異步的。
如果在方法或表達(dá)式上使用async修飾符,則它就被稱為異步方法(async method)。
下面示例代碼定義了一個(gè)名為ExampleMethodAsync
的異步方法:
public async Task<int> ExampleMethodAsync(){
//...
}
異步方法的返回類型:
具體取決于異步操作是否有返回值:
- 異步方法的返回類型可以是
Task
,表示異步操作完成,沒有返回值。 - 也可以是
Task<TResult>
,表示異步操作完成,并返回一個(gè)類型為TResult
的結(jié)果。
await概述
await是一個(gè)operator(運(yùn)算符或者操作符)。
該運(yùn)算符會(huì)掛起(suspend)封閉的異步方法(async method),直到操作對象的異步操作完成。
這句話初看比較難懂,稍微拆解一下。
- operator,運(yùn)算符,跟加減乘除一樣,作用于某個(gè)值(或?qū)ο螅缓笤撝禃?huì)進(jìn)行一些運(yùn)算,發(fā)生變化。
- 掛起,就是使某個(gè)過程暫停。
- 封閉的,我們可以想象方法(或者說函數(shù))是一個(gè)容器,里面裝載了一些運(yùn)算的語句,隨著運(yùn)算的進(jìn)行,方法(容器)中的狀態(tài)會(huì)發(fā)生變化,此時(shí)我掛起方法,就相當(dāng)于把方法(連同那些狀態(tài))封閉起來,不再改變。
- 異步方法,指的是該方法不是阻塞的,我運(yùn)行到某個(gè)點(diǎn),可能要等很久,此時(shí)我不等了,直接去干別的事情了,該點(diǎn)運(yùn)行完之后通知我回來繼續(xù)運(yùn)行。
- 直到操作對象的異步操作完成,就是說await作用的對象的其他異步操作還在進(jìn)行,進(jìn)行完了我再回來繼續(xù)執(zhí)行await下面的語句。
在異步編程中,“掛起” 通常指的是在異步操作中
暫停當(dāng)前方法的執(zhí)行,以等待某些異步操作的完成。
await
表示在執(zhí)行異步操作時(shí),將控制權(quán)返回給調(diào)用方,直到異步操作完成后再繼續(xù)執(zhí)行。
//下面是一個(gè)簡單的示例,用于理解方法的掛起:
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Console.WriteLine("Before DoAsync");
// 調(diào)用異步方法 DoAsync
await DoAsync();
Console.WriteLine("After DoAsync");
}
static async Task DoAsync()
{
Console.WriteLine("Start DoAsync");
// 模擬一個(gè)異步操作,比如網(wǎng)絡(luò)請求或I/O操作
await Task.Delay(2000);
Console.WriteLine("End DoAsync");
}
}
//在這個(gè)例子中,Main 方法調(diào)用了異步方法 DoAsync。在 DoAsync 方法內(nèi)部,使用了 await Task.Delay(2000) 來模擬一個(gè)異步操作,此時(shí)控制權(quán)會(huì)在異步操作執(zhí)行期間返回給調(diào)用方(Main 方法),這時(shí) Main 方法的執(zhí)行并沒有被阻塞,而是可以繼續(xù)執(zhí)行其他操作。當(dāng)異步操作完成后,DoAsync 方法會(huì)在 await 處恢復(fù)執(zhí)行,之后的代碼才會(huì)執(zhí)行。
//總體來說,異步方法的 "掛起" 是指在遇到異步操作時(shí),暫停當(dāng)前方法的執(zhí)行,將控制權(quán)返回給調(diào)用方,等待異步操作完成后再繼續(xù)執(zhí)行。這使得應(yīng)用程序在執(zhí)行異步操作的同時(shí)可以繼續(xù)執(zhí)行其他任務(wù),提高了程序的響應(yīng)性。
畫個(gè)草圖,理解await運(yùn)算符
:
藍(lán)色手描實(shí)線表示該方法正在執(zhí)行。
異步≠多線程,但異步往往會(huì)和多線程一起用。
只能在async關(guān)鍵字修飾的方法、lambda表達(dá)式或匿名方法上使用await運(yùn)算符。
await
關(guān)鍵字用于等待異步操作的完成。它會(huì)暫時(shí)將控制權(quán)返回給調(diào)用方,允許調(diào)用方在等待的過程中執(zhí)行其他操作。當(dāng)遇到
await
表達(dá)式時(shí),當(dāng)前方法會(huì)在此處分割,將剩余部分作為回調(diào)(或稱為后續(xù)任務(wù))注冊到異步操作的完成事件上。
async代碼示例:
在對await有個(gè)大概理解后,繼續(xù)學(xué)習(xí)async關(guān)鍵字
假設(shè)下面代碼是在一個(gè)異步方法中的,并且它調(diào)用了 HttpClient.GetStringAsync
方法:
string contents = await httpClient.GetStringAsync(requestUrl);
異步方法會(huì)以同步的方式運(yùn)行,直到遇到await表達(dá)式,此時(shí)該方法被掛起(await表達(dá)式下面的語句停止執(zhí)行),直到等待的任務(wù)完成。
與此同時(shí),控制(執(zhí)行)權(quán)返回給方法的調(diào)用者。
如果async關(guān)鍵字修飾的方法不包含await表達(dá)式或語句,則該方法將同步執(zhí)行。編譯器會(huì)警告你該異步方法不包含await語句,因?yàn)檫@種情況可能會(huì)指示錯(cuò)誤。
總結(jié)一下:
1??async和await往往一起出現(xiàn).
2??async修飾符,指明該方法是一個(gè)異步方法,異步方法應(yīng)該使用 async
和 await
關(guān)鍵字。這樣可以使異步代碼更加清晰。
3??await運(yùn)算符,等候操作對象的異步操作完成
正確使用 async 和 await 的步驟:
-
方法聲明: 使用
async
關(guān)鍵字來修飾異步方法。 -
返回類型: 異步方法的返回類型應(yīng)該是
Task
或Task<TResult>
,取決于是否有返回值。 -
await 關(guān)鍵字: 在異步方法中使用
await
關(guān)鍵字等待異步操作的完成。 -
異常處理: 使用
try-catch
塊捕獲異步方法中可能拋出的異常。
異步操作
what is 異步操作?
異步操作是一種允許程序繼續(xù)執(zhí)行其他任務(wù)而不必等待某個(gè)長時(shí)間運(yùn)行的操作完成的編程模型。在異步操作中,程序可以啟動(dòng)一個(gè)任務(wù),然后繼續(xù)執(zhí)行其他任務(wù),而無需等待啟動(dòng)的任務(wù)完成。這對于執(zhí)行可能涉及網(wǎng)絡(luò)請求、文件 I/O、長時(shí)間計(jì)算等的操作特別有用,因?yàn)檫@些操作可能需要一定的時(shí)間來完成。
異步操作的主要優(yōu)勢在于提高程序的響應(yīng)性和效率,特別是在處理用戶界面(UI)或執(zhí)行多個(gè)操作的情況下。而在同步操作中,如果一個(gè)操作耗時(shí)較長,程序可能會(huì)在等待這個(gè)操作完成時(shí)變得不響應(yīng)。
在C#中,異步操作通常使用 async
和 await
關(guān)鍵字,這使得異步編程變得更加清晰和易于理解。
更加具體的來說,異步操作指的是的async
異步方法中加了await
關(guān)鍵字的相關(guān)表達(dá)式
如何取消異步操作
取消異步操作通常使用 CancellationToken
和 CancellationTokenSource
來實(shí)現(xiàn)。
1.使用CancellationToken參數(shù)
:
首先,在異步方法中使用 CancellationToken
參數(shù),例如:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Test{
public class AsyncCancellationExample{
public async Task SomeAsyncOperation(CancellartionToken cancellationToken){
Console.WriteLine("Async operation started.");
try{
//模擬異步操作
await Task.Delay(5000,cancellationToken);
//在異步操作中檢查CancellationToken是否已取消
cancellationToken.ThrowIfCancellationRequested();
Console.WriteLine("Async operation completed.");
}catch(OperationCanceledException){
Console.WriteLine("Async operation canceled.");
}
}
}
public void CancelAsyncOperation(){
//在需要取消異步操作的地方調(diào)用Cancel方法
cts?.Cancel();
}
}
2.使用CancellationTokenSource參數(shù)
:
在調(diào)用異步方法的地方,創(chuàng)建一個(gè) CancellationTokenSource
對象,并將其 Token
屬性傳遞給異步方法。
在需要取消異步操作的地方,調(diào)用 CancellationTokenSource
的 Cancel
方法。
在這里,
StartAsyncOperation
方法中使用using
語句創(chuàng)建一個(gè)新的CancellationTokenSource
對象,確保每次開始新的異步操作時(shí)都使用一個(gè)新的CancellationTokenSource
。
using System;
using System.Threading;
using System.Threading.Tasks;
public class AsyncCancellationExample
{
private CancellationTokenSource cts;
public async Task StartAsyncOperation(){
try{
// 創(chuàng)建一個(gè)新的異步操作的 CancellationTokenSource
using(cts = new CancellationTokenSource()){
//調(diào)用異步方法,并傳遞CancellationTokenSource的Token屬性給異步方法
await SomeAsyncOperation(cts.Token);
}
Console.WriteLine("Async operation completed successfully.");
}catch (OperationCanceledException)
{
Console.WriteLine("Async operation canceled.");
}
}
}
c# 異常
Exception 類是 SystemException 和 ApplicationException 類的基類。
SystemException 類是程序執(zhí)行過程中可能發(fā)生的所有異常的基類。
如果我們程序希望定義異常的話可以繼承于Exception.
下面列出了一些派生自 System.SystemException 類的預(yù)定義的異常類:
異常類 | 描述 |
---|---|
System.IO.IOException | 處理 I/O 錯(cuò)誤。 |
System.IndexOutOfRangeException | 處理當(dāng)方法指向超出范圍的數(shù)組索引時(shí)生成的錯(cuò)誤。 |
System.ArrayTypeMismatchException | 處理當(dāng)數(shù)組類型不匹配時(shí)生成的錯(cuò)誤。 |
System.NullReferenceException | 處理當(dāng)引用一個(gè)空對象時(shí)生成的錯(cuò)誤。 |
System.DivideByZeroException | 處理當(dāng)除以零時(shí)生成的錯(cuò)誤。 |
System.InvalidCastException | 處理在類型轉(zhuǎn)換期間生成的錯(cuò)誤。 |
System.OutOfMemoryException | 處理空閑內(nèi)存不足生成的錯(cuò)誤。 |
System.StackOverflowException | 處理?xiàng)R绯錾傻腻e(cuò)誤。 |
ArgumentNullException | 參數(shù)不應(yīng)為null時(shí) |
自定義異常
自定義異常類通常繼承自 Exception
類或其派生類。
自定義異常類的一般步驟:
-
創(chuàng)建自定義異常類: 創(chuàng)建一個(gè)繼承自
Exception
類的新類,添加構(gòu)造函數(shù)以及其他需要的屬性和方法。 - 使用自定義異常: 在你的代碼中,當(dāng)發(fā)生特定的錯(cuò)誤條件時(shí),拋出自定義異常。
下面是一個(gè)簡單的示例,演示如何創(chuàng)建和使用自定義異常:
using System;
//步驟1:創(chuàng)建自定義異常類
public class CustomException:Exception{
//添加自定義的屬性,可根據(jù)需要擴(kuò)展
public int ErrorCode{get;}
//構(gòu)造函數(shù),可根據(jù)需要接受不同的參數(shù)
public CustomException(int errorCode,string message):base(message){
ErrorCode = errorCode;
}
}
//【client】
class Program{
static void Main(){
try{
//步驟2:使用自定義異常
throw new CustomException(404,"Resource not found");
}catch(Exception ex){
Console.WriteLine($"An unexpected error occurred:{ex.Message}");
}
}
}
異常處理
異常是在程序執(zhí)行期間出現(xiàn)的問題。異常提供了一種把程序控制權(quán)從某個(gè)部分轉(zhuǎn)移到另一個(gè)部分的方式。
C# 異常處理時(shí)建立在四個(gè)關(guān)鍵詞之上的:try、catch、finally 和 throw。
-
try:一個(gè) try 塊標(biāo)識(shí)了一個(gè)將被激活的特定的異常的代碼塊。后跟一個(gè)或多個(gè) catch 塊。
-
catch:程序通過異常處理程序捕獲異常。catch 關(guān)鍵字表示異常的捕獲。
無參數(shù) catch 塊 catch 或一般的 catch 塊 catch(Exception ex) 必須是最后一個(gè)塊。 如果在 catch 或 catch(Exception ex) 塊之后還有其他 catch 塊,編譯器將給出錯(cuò)誤。
-
finally:finally 塊用于執(zhí)行給定的語句,不管異常是否被拋出都會(huì)執(zhí)行。例如,如果您打開一個(gè)文件,不管是否出現(xiàn)異常文件都要被關(guān)閉。finally 塊是一個(gè)可選塊,應(yīng)該在 try 或 catch 塊之后。 無論是否發(fā)生異常,finally 塊都將始終執(zhí)行。 finally 塊通常用于清理代碼。注意 不允許添加多個(gè) finally 塊。 此外,finally 塊也不能有 return、continue 或 break 關(guān)鍵字。這個(gè)跟 finally 塊設(shè)計(jì)不符合。
-
throw:當(dāng)問題出現(xiàn)時(shí),程序拋出一個(gè)異常。使用 throw 關(guān)鍵字來完成。
可以使用 throw 關(guān)鍵字拋出從 Exception 類派生的任何類型的異常。
throw 使用 new 關(guān)鍵字創(chuàng)建任何有效異常類型的對象。 throw 必須跟從Excpetion派生出來的類型一起使用。
eg:
throw new NullReferenceException("Student object is null.");
try
{
// 引起異常的語句
}
catch( ExceptionName e1 )
{
// 錯(cuò)誤處理代碼
}
catch( ExceptionName e2 )
{
// 錯(cuò)誤處理代碼
}
catch(Exception ex) //捕獲所有
{
// 錯(cuò)誤處理代碼
}
finally
{
// 要執(zhí)行的語句
}
LINQ
- 什么是LINQ?
答:LINQ(Language-Integrated Query)(集成語言查詢)是一種用于.NET平臺(tái)的查詢和數(shù)據(jù)操作技術(shù)。
它是由微軟開發(fā)的一組語言擴(kuò)展和API,用于在編程語言中執(zhí)行數(shù)據(jù)查詢、轉(zhuǎn)換和操作。LINQ 提供了一種統(tǒng)一的查詢語法,稱為查詢表達(dá)式,讓開發(fā)人員可以使用簡潔的語法來編寫查詢表達(dá)式,而無需將查詢邏輯嵌入到編程語言的代碼中。這種語法類似于SQL語句,但在編譯時(shí)進(jìn)行類型檢查,并集成到編程語言中。
使用查詢表達(dá)式,開發(fā)人員可以以聲明式的方式指定查詢邏輯,而不必顯式編寫循環(huán)和條件語句。
LINQ 的概念基于一組通用的查詢操作,如過濾、排序、分組、投影和聚合等。
它可以用于訪問各種數(shù)據(jù)源,包括對象集合(例如數(shù)組和列表)、數(shù)據(jù)集(例如數(shù)據(jù)庫表)和XML文檔等。
LINQ 還提供了一組強(qiáng)大的標(biāo)準(zhǔn)查詢操作符,如Where、OrderBy、GroupBy、Select、Join、Aggregate等,開發(fā)人員可以使用這些操作符來構(gòu)建復(fù)雜的查詢。此外,LINQ 還支持?jǐn)U展方法語法,允許開發(fā)人員通過鏈?zhǔn)秸{(diào)用方法來編寫查詢。
總之,LINQ 是一種強(qiáng)大的查詢和數(shù)據(jù)操作技術(shù),簡化了開發(fā)人員對各種數(shù)據(jù)源進(jìn)行查詢和操作的過程,提高代碼的可讀性可維護(hù)性。
- 關(guān)系圖:
LINQ兩種語法形式
LINQ(Language Integrated Query)提供了兩種主要的語法風(fēng)格:查詢語法(Query Syntax)和方法語法(Method Syntax)。這兩種語法風(fēng)格都用于執(zhí)行查詢和操作集合的操作,只是表達(dá)方式上有所不同。
查詢語法
查詢語法是一種更接近自然語言的語法,類似于 SQL 查詢語句。它使用關(guān)鍵字
(如 from
、where
、select
)來描述查詢操作。
使用LINQ查詢語法可以方便地對各種數(shù)據(jù)源(如數(shù)組、集合、數(shù)據(jù)庫表等)進(jìn)行查詢、篩選、排序、分組等操作,同時(shí)還能夠進(jìn)行數(shù)據(jù)轉(zhuǎn)換和投影。如下示例:
demo1.cs
:
//下面是一個(gè)簡單的LINQ查詢表達(dá)式的示例:
//假設(shè)有一個(gè)包含若干個(gè)整數(shù)的數(shù)組,我們想要篩選出其中所有的偶數(shù):
int[] numbers = { 1, 2, 3, 4, 5, 6 };
IEnumerable<int> evenNumbers = from num in numbers
where num % 2 == 0
select num;
//在這個(gè)查詢表達(dá)式中,我們使用了關(guān)鍵字“from”、“where”和“select”來描述查詢的過程。
//其中,“from”關(guān)鍵字用于指定數(shù)據(jù)源,這里是整數(shù)數(shù)組“numbers”;
//“where”關(guān)鍵字用于指定篩選條件,這里是“num % 2 == 0”,即判斷數(shù)字是否為偶數(shù);
//“select”關(guān)鍵字用于指定查詢結(jié)果的輸出形式,這里是直接輸出篩選出來的數(shù)字本身。
demo2.cs
//另外,LINQ查詢表達(dá)式還可以進(jìn)行更加復(fù)雜的數(shù)據(jù)處理操作,例如:
var students = new List<Student>
{
new Student { Name = "Alice", Age = 20, Gender = Gender.Female },
new Student { Name = "Bob", Age = 22, Gender = Gender.Male },
new Student { Name = "Charlie", Age = 18, Gender = Gender.Male },
new Student { Name = "David", Age = 19, Gender = Gender.Male },
new Student { Name = "Eve", Age = 21, Gender = Gender.Female },
};
var query = from student in students
where student.Age >= 20
group student by student.Gender into genderGroups
select new
{
Gender = genderGroups.Key,
AverageAge = genderGroups.Average(s => s.Age),
Names = string.Join(", ", genderGroups.Select(s => s.Name)),
};
foreach (var result in query)
{
Console.WriteLine($"{result.Gender}: {result.Names} (avg. age = {result.AverageAge})");
}
//在這個(gè)示例中,我們使用了LINQ查詢表達(dá)式對一個(gè)名為“students”的學(xué)生列表進(jìn)行了處理,首先篩選出了年齡大于等于20歲的學(xué)生,然后按照性別進(jìn)行了分組,最終輸出了每個(gè)性別的學(xué)生名單和平均年齡。
//這里使用了“group by”關(guān)鍵字來進(jìn)行分組操作,并對每個(gè)分組進(jìn)行了聚合計(jì)算和投影輸出。
方法語法
方法語法使用方法調(diào)用鏈來實(shí)現(xiàn)查詢。它使用一系列的 LINQ 方法,如 Where()
、OrderBy()
、Select()
等。
示例:
var result = peoples
.Where(person => person.Age > 25)
.OrderBy(person => person.Name)
.Select(person => person);
區(qū)別
-
表達(dá)方式:
- 查詢語法更類似于自然語言,更容易閱讀和理解。
- 方法語法使用方法調(diào)用鏈,更像是編寫傳統(tǒng)的 C# 代碼。
-
可讀性:
- 查詢語法通常在簡單的查詢場景下更易讀,特別是對于初學(xué)者。
- 方法語法在復(fù)雜的查詢鏈中可能更具可讀性,特別是對于熟悉編程范式的開發(fā)者。
-
適用場景:
- 查詢語法適用于相對簡單的查詢,尤其是需要強(qiáng)調(diào)查詢邏輯的情況。
- 方法語法適用于更復(fù)雜的查詢鏈,可以更靈活地進(jìn)行組合和擴(kuò)展。
運(yùn)行時(shí)沒有區(qū)別,編譯后都是一樣的 “查詢語法”看起來更酷,但是“方法語法”更實(shí)用,因此.net開發(fā)者大部分還是用“方法語法”
LINQ常用的擴(kuò)展方法(以方法語法示例)
Linq中提供了大量的類似where的擴(kuò)展方法,簡化數(shù)據(jù)處理,且這些擴(kuò)展方法大部分都在System.Linq命令空間中。
Linq中所有的擴(kuò)展方法幾乎都是針對IEnummerable接口的,能返回集合的都返回IEnumerable,所以是可以把幾乎所有方法“鏈?zhǔn)绞褂谩?/p>
-
where()方法
數(shù)據(jù)源中的每一項(xiàng)數(shù)據(jù)都會(huì)經(jīng)過predicate測試,如果針對數(shù)據(jù)源中的每一個(gè)元素,predicate執(zhí)行的返回值為true,那么這個(gè)元素就會(huì)放到返回值中。
where方法的實(shí)際參數(shù)是一個(gè)lambda表達(dá)式格式的匿名方法,方法的參數(shù)e表示當(dāng)前判斷的元素對象。參數(shù)的名字不一定非要叫e,不過一般lambda表達(dá)式中的變量名長度都不長。
// example int[] nums = new int[]{3,99,88,77,7,8,9,66,15,7}; IEnumerable<int> items = nums.where(e => e>10);
-
Count()方法: 獲取數(shù)據(jù)條數(shù)
//example int count1 = list.Count(e => e.salary > 1000 || e.Age < 30); int count2 = list.where(e => e.salary > 1000 || e.Age < 30).Count();
-
Any()方法:是否至少有一條數(shù)據(jù)
//example bool b1 = list.Any(e => e.salary > 8000); bool b2 = list.Where(e => e.salary > 8000).Any();
-
獲取一條數(shù)據(jù)的相關(guān)API方法
First()
和FirstOrDefault()
:這兩個(gè)方法都用于獲取序列中的第一個(gè)元素//First():返回序列的第一個(gè)元素,如果序列為空,會(huì)拋出異常 var firstElement = list.First(); //FirstOrDefault():返回序列的第一個(gè)元素,如果序列為空,則返回默認(rèn)值(例如,對于 int 類型返回 0) var firstOrDefaultElement = list.FirstOrDefault();
Single()
和SingleOrDefault()
:這兩個(gè)方法用于獲取序列中的唯一一個(gè)元素。//Single():返回序列中的唯一一個(gè)元素。如果序列為空或包含多個(gè)元素,會(huì)拋出異常。 var singleElement = list.Single(); //SingleOrDefault():返回序列中的唯一一個(gè)元素,如果序列為空,則返回默認(rèn)值;如果序列包含多個(gè)元素,會(huì)拋出異常。 var singleOrDefaultElement = list.SingleOrDefault();
-
Take()方法: 獲取多條數(shù)據(jù)
Take
方法用于獲取序列中指定數(shù)量的元素//example: Take(5) 表示從序列中取前面的 5 個(gè)元素。 var takeElements = list.Take(5);
-
排序的相關(guān)API方法
OrderBy()
和OrderByDescending()
:兩個(gè)方法用于對序列進(jìn)行升序(OrderBy)或降序(OrderByDescending)排列//OrderBy(): 按照指定的鍵升序排列 var orderedList = list.OrderBy(item => item.Property); //OrderByDescending(): 按照指定的鍵降序排列 var orderedListDesc = list.OrderByDescending(item => item.Property);
ThenBy()
和ThenByDescending():
這兩個(gè)方法用于在已經(jīng)進(jìn)行排序的基礎(chǔ)上,如果第一個(gè)鍵相同,再按照第二個(gè)鍵再次進(jìn)行升序(ThenBy)或降序(ThenByDescending)排列。//ThenBy():在已經(jīng)進(jìn)行排序的基礎(chǔ)上,按照指定的鍵升序排列。 var thenByList = list.OrderBy(item => item.Property1).ThenBy(item => item.Property2); //ThenByDescending(): 在已經(jīng)進(jìn)行排序的基礎(chǔ)上,按照指定的鍵降序排列。 var thenByDescendingList = list.OrderByDescending(item => item.Property1).ThenByDescending(item => item.Property2);
Reverse()方法:
用于顛倒序列的順序,即將第一個(gè)元素變成最后一個(gè),第二個(gè)變成倒數(shù)第二個(gè),以此類推。var reversedList = list.Reverse();
-
Skip()方法:
限制結(jié)果集,用于跳過序列中的指定數(shù)量的元素,返回剩余的元素。//表示跳過序列中的前 5 個(gè)元素,返回剩余的元素。 var skippedList = list.Skip(5); //Skip 方法通常與 Take 方法結(jié)合使用,以實(shí)現(xiàn)分頁的效果。 var page = 2; // 當(dāng)前頁碼 var pageSize = 10; // 每頁元素?cái)?shù)量 var resultList = yourList.Skip((page - 1) * pageSize).Take(pageSize);
-
聚合函數(shù)相關(guān)API方法
下面的
yourList
可以是任何實(shí)現(xiàn)IEnumerable<T>
接口的序列,例如數(shù)組、列表等。Sum:
Sum
方法用于計(jì)算序列中數(shù)值型元素的總和。var sum = yourList.Sum();
Average:
Average
方法用于計(jì)算序列中數(shù)值型元素的平均值。var average = yourList.Average();
Min:
Min
方法用于找到序列中數(shù)值型元素的最小值。var min = yourList.Min();
Max:
Max
方法用于找到序列中數(shù)值型元素的最大值。var max = yourList.Max();
Aggregate:
Aggregate
方法用于通過指定的累加函數(shù)對序列中的元素進(jìn)行累積。TResult Aggregate(Func<T, T, T> func)
這個(gè)版本的
Aggregate
接受一個(gè)二元函數(shù)(Func<T, T, T>
),該函數(shù)定義了如何累積序列中的元素。函數(shù)接受兩個(gè)參數(shù),表示當(dāng)前的累加值和下一個(gè)元素,返回值表示下一步的累加值。在沒有初始累加值的情況下,Aggregate
將使用序列中的第一個(gè)元素作為初始累加值,然后逐個(gè)遍歷剩余的元素進(jìn)行累積。示例:
var numbers = new List<int> { 1, 2, 3, 4, 5 }; var product = numbers.Aggregate((acc, next) => acc * next); Console.WriteLine(product); // 輸出: 120 //在這個(gè)例子中,Aggregate 以第一個(gè)元素 1 作為初始累加值,然后通過乘法逐個(gè)累積后續(xù)的元素,得到最終的結(jié)果 120。
Aggregate(帶初始值):
Aggregate
方法還可以帶一個(gè)初始值,作為累加的起始值。TAccumulate Aggregate<TAccumulate>(TAccumulate seed, Func<TAccumulate, T, TAccumulate> func)
這個(gè)版本的
Aggregate
接受一個(gè)初始累加值seed
和一個(gè)二元函數(shù)(Func<TAccumulate, T, TAccumulate>
)。與前一個(gè)版本不同,這個(gè)版本明確指定了初始累加值,然后逐個(gè)遍歷序列中的元素進(jìn)行累積。示例:
var numbers = new List<int> { 1, 2, 3, 4, 5 }; var productWithSeed = numbers.Aggregate(1, (acc, next) => acc * next); Console.WriteLine(productWithSeed); // 輸出: 120 //在這個(gè)例子中,Aggregate 以初始累加值 1 開始,然后通過乘法逐個(gè)累積后續(xù)的元素,得到最終的結(jié)果 120。
Join:
Join
方法用于將序列中的元素連接為一個(gè)字符串。var joinedString = yourList.Join(", ");
-
GroupBy()方法
: 用于按照指定的鍵進(jìn)行分組//example var students = new List<Student> { new Student { Name = "Alice", Grade = "A" }, new Student { Name = "Bob", Grade = "B" }, new Student { Name = "Charlie", Grade = "A" }, new Student { Name = "David", Grade = "B" }, new Student { Name = "Emily", Grade = "A" } }; var groupedStudents = students.GroupBy(student => student.Grade); foreach (var group in groupedStudents) { Console.WriteLine($"Grade: {group.Key}"); foreach (var student in group) { Console.WriteLine($" {student.Name}"); } } //輸出結(jié)果如下: Grade: A Alice Charlie Emily Grade: B Bob David
-
投影及相關(guān)API
在 LINQ(Language Integrated Query)中,投影(Projection)是指從源序列中選擇或變換元素的過程。通過投影,你可以從集合中選擇特定的字段、計(jì)算新的值,或者創(chuàng)建新的對象。在 C# 中,投影通常使用
Select()
方法進(jìn)行,如下示例://基礎(chǔ)示例: var fruits = new List<string> { "apple", "orange", "banana", "grape" }; var uppercasedFruits = fruits.Select(fruit => fruit.ToUpper()); foreach (var result in uppercasedFruits) { Console.WriteLine(result); } //輸出結(jié)果為: APPLE ORANGE BANANA GRAPE
LINQ 允許你創(chuàng)建匿名類型來組合源序列中的多個(gè)字段或?qū)傩?,如下示?
var students = new List<Student> { new Student { Name = "Alice", Grade = "A" }, new Student { Name = "Bob", Grade = "B" }, new Student { Name = "Charlie", Grade = "A" } }; var studentDetails = students.Select(student => new {student.Name,student.Grade}); foreach (var detail in studentDetails) { Console.WriteLine($"Name: {detail.Name}, Grade: {detail.Grade}"); } //輸出結(jié)果: Name: Alice, Grade: A Name: Bob, Grade: B Name: Charlie, Grade: A
可以通過
Select()
創(chuàng)建新的自定義類型,將源序列中的元素映射到這個(gè)新類型,如下示例:var employees = new List<Employee> { new Employee { Id = 1, Name = "John", Department = "HR" }, new Employee { Id = 2, Name = "Alice", Department = "IT" }, new Employee { Id = 3, Name = "Bob", Department = "Finance" } }; var employeeDetails = employees.Select(employee => new EmployeeDetails { Id = employee.Id, FullName = $"{employee.Name} - {employee.Department}" }); foreach (var detail in employeeDetails) { Console.WriteLine($"Employee ID: {detail.Id}, Full Name: {detail.FullName}"); } //輸出結(jié)果: Employee ID: 1, Full Name: John - HR Employee ID: 2, Full Name: Alice - IT Employee ID: 3, Full Name: Bob - Finance
-
集合轉(zhuǎn)換相關(guān)API
ToArray()方法
:用于將IEnumerable<T>
轉(zhuǎn)換為數(shù)組類型T[]
,示例如下:var numbers = Enumerable.Range(1, 5);//Enumerable.Range(1, 5) 生成的是一個(gè)整數(shù)序列,它屬于 IEnumerable<int> 類型。在 C# 中,IEnumerable<T> 是表示可枚舉集合的接口,它表示一個(gè)可以按順序逐個(gè)訪問其元素的集合。 //雖然這個(gè)序列的底層實(shí)現(xiàn)是由 Enumerable.Range 方法創(chuàng)建的,但它并不是一個(gè)數(shù)組(Array)類型。它是一個(gè)通過 IEnumerable<int> 接口提供迭代功能的對象。 int[] arrayNumbers = numbers.ToArray();
ToList()方法:
用于將IEnumerable<T>
轉(zhuǎn)換為List<T>
類型,示例如下:var fruits = new[] { "apple", "banana", "orange" }; List<string> fruitList = fruits.ToList();
-
鏈?zhǔn)秸{(diào)用
鏈?zhǔn)秸{(diào)用是指在 LINQ 查詢中使用一系列的操作方法,每個(gè)操作方法都返回一個(gè)新的
IEnumerable<T>
,從而可以在其上繼續(xù)進(jìn)行操作。常見的鏈?zhǔn)秸{(diào)用包括Where()
、Select()
、OrderBy()
、GroupBy()
、Skip()
等,如下示例:var employees = new List<Employee> { new Employee { Id = 1, Name = "Alice", Age = 25, Salary = 50000 }, new Employee { Id = 2, Name = "Bob", Age = 30, Salary = 60000 }, new Employee { Id = 3, Name = "Charlie", Age = 25, Salary = 55000 }, new Employee { Id = 4, Name = "David", Age = 35, Salary = 70000 } }; var result = employees .Where(employee => employee.Id > 2) .GroupBy(employee => employee.Age) .OrderBy(group => group.Age) .Select(group => new { Age = group.Age, Count = group.Count(), AverageSalary = group.Average(employee => employee.Salary) }) .Take(3); //在這個(gè)示例中,鏈?zhǔn)秸{(diào)用首先使用 Where 篩選出 Id > 2 的員工,然后使用 GroupBy 按照年齡分組,接著使用 OrderBy 對分組按照年齡排序,最后使用 Select 投影出年齡、人數(shù)、平均工資的匿名類型,并使用 Take 取前 3 條結(jié)果。
-
LINQ to Object步驟
-
引用 LINQ 命名空間:在代碼文件的頂部,使用
using
關(guān)鍵字引用System.Linq
命名空間,以便使用 LINQ 相關(guān)的類型和擴(kuò)展方法。 -
創(chuàng)建數(shù)據(jù)源:定義一個(gè)數(shù)據(jù)源,可以是對象集合、數(shù)組、數(shù)據(jù)集或其他實(shí)現(xiàn)了相應(yīng)接口的數(shù)據(jù)結(jié)構(gòu)。
-
構(gòu)建 LINQ 查詢表達(dá)式:使用 LINQ 的查詢語法或方法語法來構(gòu)建查詢表達(dá)式,指定要過濾、排序、投影等的操作。
-
執(zhí)行查詢:通過調(diào)用適當(dāng)?shù)牟樵儾僮鞣麃韴?zhí)行查詢。
查詢操作可以是即時(shí)執(zhí)行的(立即返回結(jié)果),也可以是延遲執(zhí)行的(在需要時(shí)才計(jì)算結(jié)果)。
-
處理查詢結(jié)果:使用循環(huán)、條件語句或其他操作來處理查詢結(jié)果,并獲取所需的數(shù)據(jù)。
下面是簡單的 LINQ To Object
示例,演示如何對一個(gè)整數(shù)列表進(jìn)行過濾和排序:
using System;
using System.Linq;//引入LINQ
public class Program
{
public static void Main()
{
// 創(chuàng)建數(shù)據(jù)源
int[] numbers = { 5, 1, 4, 2, 3 };
// 構(gòu)建 LINQ 查詢表達(dá)式
var query = from num in numbers
where num % 2 == 0 // 過濾偶數(shù)
orderby num descending // 按降序排序
select num; // 投影選擇的數(shù)字
// 執(zhí)行查詢并處理結(jié)果
foreach (var num in query)
{
Console.WriteLine(num);
}
}
}
//以上示例代碼使用 LINQ 查詢語法,從一個(gè)整數(shù)數(shù)組中過濾出偶數(shù),并按降序排序。然后通過循環(huán)打印出篩選后的結(jié)果。
//通過以上步驟,你可以開始使用 LINQ 來進(jìn)行各種查詢和數(shù)據(jù)操作。
Visual Studio 2019連接MySQL數(shù)據(jù)庫
-
要想在 Visual Studio 2019中使用MySQL數(shù)據(jù)庫,首先需要下載MySQL的驅(qū)動(dòng):
-
mysql-connector-odbc-8.0.20-winx64.msi
鏈接: https://dev.mysql.com/downloads/connector/odbc/. -
mysql-for-visualstudio-1.2.9.msi
鏈接:https://dev.mysql.com/downloads/windows/visualstudio/ -
mysql-connector-net-8.0.20.msi
鏈接:https://dev.mysql.com/downloads/connector/net/8.0.html
自行下載即可
下載完后按照以上順序安裝
-
-
安裝完后重啟visual studio
然后點(diǎn)擊菜單欄的視圖->服務(wù)器資源管理器
然后數(shù)據(jù)連接->添加連接
你就會(huì)發(fā)現(xiàn)有MySQL 的選項(xiàng)了,進(jìn)入里面配置數(shù)據(jù)庫相關(guān)信息即可。若還是沒有MySQL選項(xiàng),嘗試選擇更高版本的framework
Visual Studio 2019 中找不到Linq to SQL 類
在 Visual Studio 2019 中,Linq to SQL 已被標(biāo)記為“過時(shí)”的技術(shù),但仍然可以使用。如果您在 Visual Studio 2019 中找不到 Linq to SQL 類,請按照以下步驟操作:
- 確保已安裝 .NET 框架版本 3.5 或更高版本。
- 在解決方案資源管理器中,右鍵單擊項(xiàng)目,選擇“添加”>“新建項(xiàng)”。
- 在“添加新項(xiàng)”對話框中,選擇“數(shù)據(jù)”類別,然后選擇“LINQ to SQL 類”模板。
- 給 Linq to SQL 類命名并點(diǎn)擊“添加”按鈕。
- 在 Server Explorer 中連接到數(shù)據(jù)庫。
- 在“服務(wù)器資源”窗格中,展開數(shù)據(jù)庫,然后將表拖動(dòng)到 Linq to SQL 類設(shè)計(jì)器中,以創(chuàng)建對應(yīng)的實(shí)體類和 DataContext 類。
如果您仍然無法找到 Linq to SQL 類,請確保已安裝 Linq to SQL 組件。在 Visual Studio 2019 中,您可以通過轉(zhuǎn)到“工具”>“獲取工具和功能”>“修改”>“單個(gè)組件”>“SDK、庫和框架”>“.NET 框架 4.x 開發(fā)人員工具”>“Linq to SQL 工具”來安裝 Linq to SQL 組件?!局苯铀阉飨鄳?yīng)的工具名即可】
LINQ To Sql 步驟
LINQ to SQL
是一種在.NET Framework中使用LINQ查詢關(guān)系數(shù)據(jù)庫的技術(shù)。它通過將數(shù)據(jù)庫架構(gòu)映射到對象模型來簡化數(shù)據(jù)庫的訪問操作。
即: ORM(Object Relation Mapping
.
下面是使用 LINQ to SQL
的一般步驟和示例:
- 創(chuàng)建數(shù)據(jù)庫表結(jié)構(gòu):在關(guān)系數(shù)據(jù)庫中創(chuàng)建并定義操作的表和字段。
-
引用 LINQ 命名空間:在代碼文件的頂部,使用
using
關(guān)鍵字引用System.Linq
命名空間,以便使用 LINQ 相關(guān)的類型和擴(kuò)展方法。 - 連接到數(shù)據(jù)庫:添加一個(gè)數(shù)據(jù)連接,連接到目標(biāo)數(shù)據(jù)庫。
- 創(chuàng)建對象模型:如果是sql server數(shù)據(jù)庫,可以使用 Visual Studio 工具中的 “LINQ to SQL Classes”,從連接的數(shù)據(jù)庫中提取架構(gòu)信息,自動(dòng)生成與數(shù)據(jù)庫表對應(yīng)的類和屬性。不然就手動(dòng)編寫創(chuàng)建相應(yīng)的數(shù)據(jù)對象模型。
- 構(gòu)建查詢:利用生成的對象模型,使用 LINQ 查詢語法或方法語法來構(gòu)建查詢。
- 執(zhí)行查詢和操作:通過對查詢結(jié)果執(zhí)行相關(guān)操作,例如排序、更新、插入或刪除數(shù)據(jù)。
下面是一個(gè)簡單的 LINQ to SQL 示例,演示如何查詢數(shù)據(jù)庫中的數(shù)據(jù):
using System;
using System.Linq;//引入
public class Program
{
public static void Main()
{
// 創(chuàng)建 LINQ to SQL 數(shù)據(jù)上下文
// CRUD都找這個(gè)上下文對象
using (var db = new MyDataContext())
{
// 構(gòu)建查詢(查詢語法格式)
var query = from c in db.Customers
Where c.City == "London"
select c;
//構(gòu)建查詢(方法語法格式)
var query = db.Customers.Where(c => c.City == "London");
// 執(zhí)行查詢并處理結(jié)果
foreach (var customer in query)
{
Console.WriteLine(customer.CustomerName);
}
}
}
}
//在此示例中,我們首先創(chuàng)建了一個(gè) `MyDataContext` 的 LINQ to SQL 數(shù)據(jù)上下文對象,該對象表示與數(shù)據(jù)庫的連接。然后,我們構(gòu)建了一個(gè)查詢,查詢 `Customers` 表中城市為 “London” 的客戶名。最后,我們通過循環(huán)打印出查詢結(jié)果中的客戶名。
//通過以上步驟,你可以開始使用 LINQ to SQL 來訪問和操作關(guān)系數(shù)據(jù)庫,以更簡潔和直觀的方式進(jìn)行數(shù)據(jù)查詢。
Entity Framework(EF)
如果要操作Mysql數(shù)據(jù)庫,使用第三方庫如Entity Framework,也是可以實(shí)現(xiàn).Net環(huán)境下對MySQL數(shù)據(jù)庫的訪問和操作。
以下是使用Entity Framework配合MySQL進(jìn)行操作的基本步驟:
- 打開Visual Studio,創(chuàng)建或打開已有項(xiàng)目。
- 打開
工具
->NuGet包管理器
->管理解決方案的NuGet程序包
。 - 在打開的NuGet包管理器中搜索
MySql.Data.EntityFramework
并安裝。 - 在你的模型類上添加[System.Data.Linq.Table(Name = “your_table_name”)]特性,你的列的屬性添加[System.Data.Linq.Column(IsPrimaryKey = true, IsDbGenerated = true)]特性。
- 創(chuàng)建你的數(shù)據(jù)上下文類繼承自
DbContext
,并且創(chuàng)建對應(yīng)的DbSet
屬性。
以上就是一種方式來連接MySQL和EF,另外你還可以通過Code First
、Database First
或者Model First
來創(chuàng)建模型。
然而,如果還是想用類似LINQ to SQL的方式來操作MySQL數(shù)據(jù)庫,可以選擇Dapper等其它比較流行的ORM框架。
C# 注意事項(xiàng):
-
如下的各個(gè)成對寫法是等效的:
為什么要有兩種寫法呢?
因?yàn)槠渲械男戭愋烷_頭的是c#定義的,而大寫類型開頭的則是.Net framework的CLR里的CTS定義的。
這樣的話不同類型語言到了.Net框架后才可以達(dá)到一個(gè)相同的定義。
所以小寫屬于c#寫法,大寫屬于.Net框架寫法。
-
c#
中的Struct數(shù)據(jù)類型類似Class類型,但區(qū)別是Class是屬于引用類型
,Struct是屬于值類型
。 -
值類型
和引用類型
在內(nèi)存(棧和堆)中的分配形式:c#的值類型是在棧中分配空間,即值存儲(chǔ)在棧中。
c#的引用類型是在堆中分配空間,棧中只保存其引用。即棧中保存指向堆的地址,堆中保存真實(shí)的對象和數(shù)據(jù)整體。
如下:
-
實(shí)例化一個(gè)對象時(shí),從內(nèi)存角度分析:
case1
:case2
: -
方法調(diào)用時(shí)形參的
值傳遞
(默認(rèn)行為,傳遞變量的副本)介紹:先說結(jié)論:
值類型的形參
和引用類型的形參
在方法調(diào)用都是值傳遞
,跟形參的類型是值類型或者是引用類型都無關(guān)。所謂值傳遞:
即copy一份副本出來調(diào)用。
對于值類型的形參:
對于引用類型的形參(方法體中不帶有new一個(gè)實(shí)例對象):
方法改變的是同一個(gè)對象
。對于引用類型的形參(方法體中帶有new一個(gè)實(shí)例對象):
方法改變的是方法中創(chuàng)建的新對象。
-
方法調(diào)用時(shí)形參的
引用傳遞
:由于默認(rèn)是
值傳遞
,要實(shí)現(xiàn)引用傳遞
只能做特殊處理。- 法一:在形參變量的前面加上
ref
關(guān)鍵字,同時(shí)傳入實(shí)參時(shí)也在實(shí)參變量前面加上ref
關(guān)鍵字。 - 法二:在形參變量的前面加上
out
關(guān)鍵字,同時(shí)傳入實(shí)參時(shí)也在實(shí)參變量前面加上out
關(guān)鍵字。
這兩種特殊處理的區(qū)別:
-
ref側(cè)重于改變某一個(gè)值; out側(cè)重于輸出一個(gè)值
-
法二需要在方法體里面添加一句形參變量的初始化語句才不會(huì)報(bào)錯(cuò),如下:
- 法一:在形參變量的前面加上
-
對于接收數(shù)字的形參變量的類型定義:
只要不參與運(yùn)算,方法的形參接收的數(shù)字均定義為string類型。
只有參與運(yùn)算,形參的數(shù)字才定義為數(shù)字類型。
-
小數(shù)會(huì)默認(rèn)被認(rèn)為是double類型,若想標(biāo)識(shí)為float,后面就要加f;同理,若要標(biāo)識(shí)為decimal,后面就要加m,如下所示:
-
在類的成員方法中聲明的本地變量(局部變量),在聲明時(shí)還要賦值,因?yàn)椴毁x值其是沒有默認(rèn)值的。如: int age = 5.
但是.若是定義在類中的成員變量(靜態(tài)變量或?qū)嵗兞?,則不需要賦值,其是有默認(rèn)值的.【Java中也一樣】
-
c#
中沒有全局變量的說法,即沒法在類之外定義變量。所以全局變量的思路往往采用類的靜態(tài)成員的思路解決。
-
c#的構(gòu)造函數(shù)注意事項(xiàng):
- 名字和類名相同。
- 沒有返回值,void也不行。
- 使用public作為權(quán)限修飾符,因?yàn)閜rivate的話就不能用new來創(chuàng)建這個(gè)類的對象了。
- 沒有手動(dòng)寫有參構(gòu)造方法時(shí),一般編譯器會(huì)默認(rèn)生成一個(gè)無參構(gòu)造函數(shù)(并賦值默認(rèn)值),而如果手動(dòng)寫有參構(gòu)造方法,則編譯器不會(huì)默認(rèn)生成一個(gè)無參構(gòu)造函數(shù),需要另外手動(dòng)寫無參構(gòu)造函數(shù)。
-
c#的完全限定名 = 命名空間 + 類名。
當(dāng)打印一個(gè)實(shí)例對象時(shí),打印的結(jié)果就是一個(gè)完全限定名。
-
Object
類的ToString()
方法:用于將當(dāng)前對象實(shí)例以字符串的形式來表示。 -
Visual Studio怎么快速多行注釋和取消注釋?
用鼠標(biāo)選中要注釋的代碼,然后,先按 Ctrl - K 組合鍵,再按 Ctrl - C 組合鍵進(jìn)行注釋。
如果要取消注釋,就選中被注釋的代碼,然后,先按 Ctrl - K 組合鍵,再按 Ctrl - U 組合鍵即可取消注釋。
-
C#
中的internal
關(guān)鍵字限制的訪問范圍是?即同一個(gè)項(xiàng)目內(nèi)部是可以訪問的。
或者同一個(gè)
dll
文件內(nèi)部是可以訪問的。因?yàn)橥ǔ碇v一個(gè)項(xiàng)目生成一個(gè)
dll
文件 -
C#
中的extern
關(guān)鍵字?在 C# 中,
extern
關(guān)鍵字用于聲明一個(gè)外部方法。它用于指示編譯器該方法的實(shí)現(xiàn)是在外部的,即在當(dāng)前的代碼文件之外,通常是在其他的本機(jī)語言(如 C++)或外部庫中實(shí)現(xiàn)的。使用extern
關(guān)鍵字聲明的方法不需要提供方法體,因?yàn)樗膶?shí)現(xiàn)在其他地方。這樣可以使 C# 代碼與其他語言或庫進(jìn)行交互。 -
在C#中,如果你在數(shù)據(jù)類型后面加上問號(hào)(?),這表示該數(shù)據(jù)類型是可空的。這特別適用于值類型(Value Types),例如整數(shù)(int)、雙精度浮點(diǎn)數(shù)(double)等。通過將其聲明為可空,你可以將其值設(shè)置為null,表示缺少數(shù)值。
int? nullableInt = null; //在這里,nullableInt 是一個(gè)可空整數(shù),可以包含一個(gè)整數(shù)值,也可以是null。這在處理數(shù)據(jù)庫查詢等場景中非常有用,因?yàn)閿?shù)據(jù)庫中的某些字段可能允許為空。
-
在C#中,
?.運(yùn)算符
和??運(yùn)算符
是用于處理可能為null的引用類型的特殊運(yùn)算符:?.運(yùn)算符
(Null 條件成員訪問運(yùn)算符):?.運(yùn)算符允許您在訪問引用類型成員之前進(jìn)行空值檢查。
它的作用是如果左側(cè)的操作數(shù)為null,則整個(gè)表達(dá)式的結(jié)果為null,否則(不為Null)才會(huì)繼續(xù)訪問成員。
示例:
int? length = name?.Length; Console.WriteLine(length); // 輸出:null //在上面的示例中,如果`name`為null,則`name?.Length`表達(dá)式的結(jié)果將為null,而不會(huì)拋出NullReferenceException。這在避免空引用異常的情況下很有用。
??運(yùn)算符
(null 合并運(yùn)算符):??
運(yùn)算符用于在表達(dá)式中處理可能為null的引用類型,并提供一個(gè)默認(rèn)值,當(dāng)左側(cè)的操作數(shù)為null時(shí)使用該默認(rèn)值。
示例:string displayName = name ?? "Guest"; Console.WriteLine(displayName); // 輸出:Guest //在上面的示例中,如果`name`為null,則`name ?? "Guest"`表達(dá)式的結(jié)果將為"Guest",因?yàn)樽髠?cè)的操作數(shù)為null,所以使用了默認(rèn)值"Guest"。
這些運(yùn)算符也可以結(jié)合使用,如下示例:
string username = user?.Name ?? "Guest"; Console.WriteLine(username); //在上面的示例中,如果`user`為null或者`user.Name`為null,那么`username`將被賦值為"Guest"。否則,它將被賦值為`user.Name`的值。 //這樣可以防止使用null值引發(fā)異常,并提供一個(gè)默認(rèn)值。
-
所謂二進(jìn)制數(shù)據(jù)的本質(zhì)就是字節(jié)數(shù)組;
字節(jié)數(shù)組是存儲(chǔ)和傳輸二進(jìn)制數(shù)據(jù)的一種常見方式。
通過使用字節(jié)數(shù)組,我們可以有效地處理和管理二進(jìn)制數(shù)據(jù),并在計(jì)算機(jī)系統(tǒng)中進(jìn)行傳輸和存儲(chǔ)。
The End!!創(chuàng)作不易,歡迎點(diǎn)贊/評論!!歡迎關(guān)注個(gè)人GZH!!文章來源:http://www.zghlxwxcb.cn/news/detail-803219.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-803219.html
到了這里,關(guān)于【一文詳解】知識(shí)分享:(C#開發(fā)學(xué)習(xí)快速入門)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!