github: https://github.com/mfjiang
e-mail: hamlet.jiang@live.com
⼀、C#,CLR,IL,JIT概念 以及 .NET 家族
(⼀)基礎概念
C# (唸作 C Sharp) 是在CLR上實現的一種程式語言,也是.NET平臺上最通用的程式語言,它在語法上借鑑了Java和C++風格,但更為精簡。Borland Turbo Pascal編譯器的主要作者安德斯·海爾斯伯格(Anders Hejlsberg)是C#與.NET平臺的創始人。本文詣在為初次接觸C#和.NET平臺的使用者提供較全面的路線指引,也為早期.NET開發人員介紹當代.NET平臺的新特性。
相對於 C 和 C++,C# 在許多方面進行了限制和增強:
1、指標(Pointer)只能用於不安全模式之中。大多數物件訪問通過安全的引用實現,以避免無效的呼叫,並且有許多演算法用於驗證溢位,指標只能用於呼叫值型別,以及受垃圾收集控制的託管物件。
2、物件不能被顯式釋放,代替為當不存在被引用時通過垃圾回收器回收。
3、只允許單一繼承(single inheritance),但是一個類可以實現多個介面(interfaces)。
4、C# 比 C++ 更加型別安全。預設的安全轉換是隱含轉換,例如由短整型轉換為長整型和從派生類轉換為基類。而介面布林型同整型,及列舉型同整型不允許隱含轉換,非空指標(通過引用相似物件)同使用者定義型別的隱含轉換欄位被顯式的確定,不同於C++的複製建構函式。
5、陣列宣告語法不同("int[] a = new int[5]"而不是"int a[5]")。
6、列舉位於其所在的名字空間中。
7、C# 中沒有模版(Template),但是在C# 2.0中引入了泛型(Generic programming),並且支援一些 C++ 模版不支援的特性。比如泛型引數中的型別約束。另一方面,表示式不能像C++模版中被用於型別引數。
8、屬性支援,使用類似訪問成員的方式呼叫。
9、完整的反射支援。
CLR-Common Language Runtime 意為公共語⾔運⾏庫,它是⼀個可由多種不同程式設計語⾔使⽤的運⾏庫,只要是⾯向 CLR 的編譯器編譯的程式設計語⾔都被 CLR ⽀持。
IL-Intermediate Language,意為中間語⾔,⾯向 CLR 的程式設計語⾔被編譯為IL程式碼,IL程式碼也被稱為託管程式碼,它是與 CPU ⽆關的機器語⾔,是⼀種⾯向物件的機器語⾔。每⼀個 IL 程式碼⽂件被稱為託管模組(managed module)。託管模組是 ⼀個32位或是64位可移植執⾏體⽂件,它們需要CLR才能執⾏。
每個託管模組帶有相應的後設資料(metadata),後設資料描述模組中定義的內容,⽐如型別及成員、導⼊的型別及成員。每 個託管模組由作業系統頭資訊、CLR頭(記錄版本、⼊口⽅法等)、後設資料、IL程式碼(CLR在運⾏時將IL編譯成本地CPU 指令)。 ⼀個.NET程式集是由⼀個或者多個託管模組和資源⽂件組成,程式集是⼀個或是多個託管模組的邏輯分組,是最⼩的可重用、安全性及版本控制單元。
JIT-just-in-time,意為CLR對IL程式碼進⾏即時編譯的過程,CLR擁有進⾏JIT過程的編譯器(JITComiler),它將要調⽤的 IL 程式碼編譯為本地 CPU 指令。
(二).NET 家族
本文將Windows上的.NET Framework稱為經典 .NET,由公共語⾔運⾏庫(CLR)和類庫(FCL --Framework Class Library)構成。
.NET Core 是 經典.NET 的跨平臺實現,.NET Standard是 .NET Core 和 .NET Framework之間的通用庫。
Mono是一個由Xamarin公司所主持的開源專案。該專案的目標是建立一系列匹配ECMA標準的.NET工具,包括C#編譯器和通用語言架構。
ML.Net 是.NET Core上實現的AI開發框架。
開發Windows應用建議選擇經典.NET (v.4.x);
開發Linux上的微服務、Web服務、docker容器服務建議使用.NET Core (v.2.2.x、v.3.0.x) ;
開發跨平臺手機應用建議使用.NET Xamarin框架(支援ios,Aandroid);
注:Visual Studio 2017 支援使用者使用以上任何一個框架開發應用,並內建相關應用場景的專案模板。
微軟公司在2014年開源了Roslyn編譯器,隨後成立了.NET 開源基金會,並在 Github上以MIT協議公開了.NET原始碼。詳情參考: https://github.com/dotnet
.NET 5 將在2020年推出,它將統一目前所有的 .NET 分支。
上圖為.NET 5 架構圖
上圖是 .NET 釋出路線圖
二、C# 語言要點
(一)基元型別
C#型別 | FCL 型別 | 說明 |
Sbyte | System.Sbyte | 有符號8位值 (⼀位即1bit,8bit即1byte,下同) |
byte | System.Byte | ⽆符號8位值 |
Short | System.Int16 | 有符號16位值 |
ushort | System.UInt16 | 無符號16位值 |
int | System.Int32 | 有符號32位值 |
uint | System.UInt32 | 無符號32位值 |
Long | System.Int64 | 有符號64位值 |
ULong | System.UInt64 | 無符號64位值 |
char | System.Char | 16位Unicode字元 |
Float | System.Single | IEEE32位浮點 |
Double | System.Double | IEEE64位浮點 |
Bool | System.Boolean | ⼀個true/false值 |
Decimal | System.Decimal | 128位⾼精度浮點值 |
String | System.String | ⼀個字元陣列 |
Object | System.Object | 所有型別的基型別 |
(二)引用型別和值型別
CLR⽀持引⽤型別和值型別。 引⽤型別總是從託管堆上分配,C#的new操作符會返回物件的記憶體地址。結構與列舉都是值型別,與引⽤型別相⽐,值型別是⼀種輕量級的型別,值型別例項是線上程的堆疊上分配,值型別不需要記憶體指標,不需要垃圾收集處理。所有型別 都是System.Object派⽣,所有值型別都是由System.ValueType抽象類派⽣。
(三) 值型別的裝箱與拆箱:
當需要⼀個值型別進⾏例項引⽤時產⽣裝箱(boxing) ,裝箱過程是從託管堆中分配記憶體,並將值型別欄位複製到新分 配的堆記憶體,然後返回新物件的引⽤。
裝箱情景:
Struct Point{public int32 x,y;}
Public sealed class Program
{
Public static void Main()
{
ArrayList a = new ArrayList();
Point p;
For(int32 i =0;i<10;i++)
{
p.x = p.y = i;
a.Add(p);//這⾥產⽣裝箱,Add⽅法⼊參必須是Object型別,⽽Object型別是⼀個引⽤型別,值型別P要被裝箱為引⽤ 型別。
}
}
}
上例中,ArrayList內的p元素是引⽤型別,與原 Point P 結構脫離了關係。
拆箱情景(unboxing):
Point p2 =(Point)a[0];//這⾥產⽣拆箱
拆箱是獲取已裝箱物件各個欄位的地址(拆箱關鍵),並將已經裝箱的物件的欄位值複製到新的值型別變數的欄位。拆 箱時只能將物件拆箱為它裝箱時的型別。
⼿動控制裝箱的速度將⽐編譯器裝箱的速度快。
如:
1)Int32 v =5;Console.writeLine(“{0}{1}{2}”,v,v,v);
2)Int32 v=5;object o =v(⼿動裝箱);Console.writeLine(“{0}{1}{2}”,o,o,o)//這個⽅法快
(四)型別、類成員、介面
型別基礎
型別:是可以在型別內部巢狀地定義其他型別的邏輯單位。
型別的成員種類:常量、欄位、例項構造器、型別構造器(靜態構造)、⽅法、操作符過載、轉換操作符、屬性、靜態 事件、例項事件。
訪問修飾符表:
名稱 | 用作類 | 用作類成員 |
Public |
該型別對所有程式集是可見的 | 成員可以由所有程式集的所有⽅法訪問 |
Protected internal | 成員可以由所在型別及其巢狀型別、所有派⽣型別 (不限程式集)、型別所在程式集的所有⽅法訪 問。 | |
Internal | 該型別僅在程式集內部可見以及友元程式 集可見 | 成員可由當前程式集中的所有⽅法訪問 |
Protected | 成員只能由定義該成員型別中的⽅法、該型別的所 有巢狀型別的⽅法、或者該型別的⼀個派⽣型別 (不限程式集)的⽅法訪問 | |
Private | 成員只能由定義該成員的型別中的⽅法或者該型別 的所有巢狀型別中的⽅法訪問 |
元件版本控制修飾符表:
名稱 | 用作類 | 用作方法、屬性、事件 | ⽤作常量/欄位 |
Abstract | 表⽰該型別不能構建例項 | 表⽰在構建派⽣類的例項之前派⽣類 型必須實現這個成員 | |
Virtual | 表⽰這個成員可以由派⽣型別重寫 | ||
Override | 表⽰派⽣型別重寫了基型別的成員 | ||
Sealed | 表⽰該型別不能⽤作基類 | 表⽰該成員不能被派⽣型別重寫 | |
new | 應⽤於巢狀型別、⽅法、屬性、事件、常量或欄位時,表⽰該成員與基類中類似的成員沒有關係 |
靜態類(static class):靜態類是不需要例項化,僅擁有靜態成員的型別。靜態類不⽀持接⼜,這是因為只有使⽤類的實 例的時候才調⽤類的接⼜⽅法。靜態型別只包括靜態成員,靜態類本⾝不能⽤作欄位、⽅法引數或者區域性變數。
部分類(partial class):為了將⼀個類分佈在多個⽂件中編輯⽽採⽤partial修飾符,它們在編譯後成為⼀個類。
索引器(indexer):索引器是⼀種引數化的成員屬性。索引器不⽀持靜態型別。索引器的作⽤是為型別向外界間接提供 內部的集合成員。
例:
public object this[int x]{get;set;},public object this[int x,int y]{get;set;}
可變引數⽅法:以params關鍵字修飾的引數稱為可變引數,它允許輸⼊數量不定的引數來調⽤⽅法。
例:
Public static double GetAvg(params double[] list){…}; GetAvg(1,2,12,4,3.2);GetAvg(1,57.3);
基類初始化(initializer)調⽤:⼦類在例項化時可以⼀並調⽤基類的建構函式。這在多個類共享基類建構函式設定的⼀ 些公共成員屬性時更便利。
例:
Public class ClassA
{
public ClassA(int a,string b){…}
}
Public class ClassB:ClassA
{
public ClassB(int a,string b,bool c):base(a,b){…}
}
型別的私有建構函式常被⽤於只通過靜態⽅法和欄位來提供功能的型別。採⽤私有建構函式的類不能被外部類例項化, 但可以在內部例項化。
靜態建構函式⽤於初始化靜態成員,也只能訪問靜態成員,不管型別被例項化多少次,靜態建構函式只執⾏⼀次。
C# 特性標記的使用
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, Inherited = true)]
public class CustomerAttribute:Attribute
{
public String Name{get;set;}
}
使用反射獲取特性標記值
[CustomerAttribute(){Name="Sample"}]
public class Sample(){}
Sample o = new Sample();
Type ot = typeof(o);//typeof(t)
ot.GetCustomAttributes();
C# 匿名擴充套件方法
public class A
{
public A();
public void M1();
}
public static class M
{
public static M2(this A a )
{
//do sth.
}
}
(五) 集合型別 Array\ArrayList\List\HashTable(雜湊表)\Dictionary(字典)\Stack(堆疊)\Queue(佇列)
Array型別:是實現陣列的基類,只有系統和編譯器可以派⽣。Array提供CreateInstance⽅法進⾏後期繫結,沒有公共構 造函式。以下都是宣告陣列的⽅式:
Array my1DArray=Array.CreateInstance( typeof(Int32), 5 );
Int32[] my2DArray = new Int32[5]{1,2,3,4,5}
ArrayList型別:是⼤⼩按需⾃動增加的Array實現,實現了IList介面。以下是ArrayList常見⽤法:
ArrayList myAL = new ArrayList();
myAL.Add("Hello");
myAL.Add("World"); myAL.Add("!");
HashTable: 表⽰鍵/值對的集合,這些鍵/值對根據鍵的雜湊程式碼進⾏組織。
例:
public static void Main() {
// Creates and initializes a new Hashtable.
Hashtable myHT = new Hashtable();
myHT.Add("First", "Hello");
myHT.Add("Second", "World");
myHT.Add("Third", "!");
// Displays the properties and values of the Hashtable.
Console.WriteLine( "myHT" );
Console.WriteLine( " Count: {0}", myHT.Count );
Console.WriteLine( " Keys and Values:" );
PrintKeysAndValues( myHT );
}
public static void PrintKeysAndValues( Hashtable myHT ) {
Console.WriteLine( "\t-KEY-\t-VALUE-" );
foreach ( DictionaryEntry de in myHT )
Console.WriteLine("\t{0}:\t{1}", de.Key, de.Value);
Console.WriteLine();
}
Dictionary:是HashTable的泛型實現
Stack:表⽰物件的簡單的後進先出⾮泛型集合。Stack 的容量是 Stack 可以儲存的元素數。Stack 的預設初始容量為 10。 向 Stack 新增元素時,將通過重新分配來根據需要⾃動增⼤容量。Stack常被當作迴圈緩衝區。
Queue(佇列):是表⽰物件的先進先出集合,與Stack相反。佇列在按接收順序儲存訊息⽅⾯⾮常有⽤,以便於進⾏順 序處理。此類將佇列作為迴圈陣列實現。儲存在 Queue 中的物件在⼀端插⼊,從另⼀端移除。
Queue 的容量是 Queue 可以儲存的元素數。Queue 的預設初始容量為 32。向 Queue 新增元素時,將通過重新分配來根據 需要⾃動增⼤容量。
(六)泛型
泛型(generic)是CLR與程式設計語⾔提供的⼀種實現“演算法重⽤”的機制。
例:
List sl = new List();sl.add(DateTime.Now);sl.add(DateTime.MinValue);
泛型物件設計⽤於管理在型別上成家族的集合,例如設計⼀個⼯⼚型別⽤於建立或修改基於某個介面演變的多個⼦型別 的物件。
例:
/// <summary>
/// 為安全成員物件提供公共服務
/// </summary>
public abstract class SecurityMemberService<T> where T:ISecurityMember
{
public abstract T MemberLogin(string memberUserName,string memberPassword);
public abstract T MemberLogin(string memberEmail,string memberPassword);
public abstract bool MemberLogout(T member);
public abstract T CreateMember(T obj,SecurityMemberInfo info);
public abstract bool DeleteMember(T member);
public abstract T FindMemberByUserName(string userName);
public abstract T FindMemberByEmail(string email);
public abstract bool LockMember(T member);
public abstract bool UnlockMember(T member);
public abstract bool ChangePassword(string memberName, string oldPassword,string newPassword);
public abstract bool ChangePasswordQuestionAndAnswer(T member);
public abstract bool ResetPasswordAndUpdate(T member);
}
在上例中,SecurityMemberService型別封裝了⼀般對ISecurityMember型別的處理⽅法,型別引數T可以是任意 實現了ISecurityMember接⼝的型別,這樣對這些型別的⼀般處理並不需要建立額外對應的⼯⼚型別。 注意:泛型類SecurityMemberService有⼀個對型別引數T的約束,它由where關鍵字指定。
在⾮泛型類中也可以有泛型⽅法成員,同樣泛型⽅法也可有型別約束。
例:
Public class A
{
Void M1<T>(T obj){obj.ToString();}
Void M2<T>(T obj)where T:ClassB {obj.ToString();}
}
委託也可以被設計成泛型,因為委託也可以被當作⽅法的⼀種定義形式,即委託本身描述的是回撥⽅法的定義。
例:
Delegate void EventHandler(Object sender,TEventArgs e)where TEventArgs:EventArgs;
上例定義的EventHandler要求回撥⽅法中的引數e必須是EventArgs型別或是EventArgs的派⽣型別,TEventArgs 是⼀個型別引數,相當於常⻅的T。
(七)執行緒 (Threading、Lock、Monitor、Mutex)
執行緒概述:
執行緒分為前臺執行緒和後臺執行緒,後臺執行緒不妨礙程式的終⽌。執行緒具有優先順序,優先順序⾼的執行緒會得到更多的CPU時 間。多執行緒可以提⾼對CPU時間的利⽤率,但會佔⽤更多的記憶體等資源。
執行緒安全:
Lock關鍵字可以將⼀段程式碼定義為互斥段。互斥段在⼀個時刻內只允許⼀個執行緒進⼊執⾏,⽽其他執行緒必須等待。如果 有⼀些任務每次只能交給⼀個執行緒去操作,就可以使⽤Lock關鍵字將程式碼定義為互斥段。
例:
Lock(this)
{
//do anything
}
Monitor 類通過向單個執行緒授予物件鎖來控制對物件的訪問。物件鎖提供限制訪問程式碼塊(通常稱為臨界區)的能⼒。當 ⼀個執行緒擁有物件的鎖時,其他任何執行緒都不能獲取該鎖。還可以使⽤ Monitor 來確保不會允許其他任何執行緒訪問正在由 鎖的所有者執⾏的應⽤程式程式碼節,除⾮另⼀個執行緒正在使⽤其他的鎖定物件執⾏該程式碼。
例:
Queue myQueue = new Queue();
Monitor.Enter(myQueue);
//可以在當前執行緒下對myQueue做任何操作。
Monitor.Exit(myQueue)//釋放鎖
為了保證在異常情況下仍可釋放鎖,Monitor.Exit()⽅法可以放在finally塊⾥。調⽤Monitor.Pulse()⽅法會通知預備佇列中的 執行緒可以⽴即使⽤釋放的物件。
Mutex類是同步基元。當兩個或更多執行緒需要同時訪問⼀個共享資源時,系統需要使⽤同步機制來確保⼀次只有⼀個執行緒 使⽤該資源。
Mutex只向⼀個執行緒授予對共享資源的獨佔訪問權。如果⼀個執行緒獲取了互斥體,則要獲取該互斥體的第⼆個執行緒將被掛 起,直到第⼀個執行緒釋放該互斥體。已命名的系統互斥體(Mutex)在整個作業系統中都可見,可⽤於同步程式活動。
與Monitor類不同,Mutex可與WaitHandle⼀起構成“等待機制”,Mutex還可以穿越應⽤程式域。
例:
class Test
{
// Create a new Mutex. The creating thread does not own the
// Mutex.
private static Mutex mut = new Mutex();
private const int numIterations = 1;
private const int numThreads = 3;
static void Main()
{
// Create the threads that will use the protected resource.
for(int i = 0; i < numThreads; i++)
{
Thread myThread = new Thread(new ThreadStart(MyThreadProc));
myThread.Name = String.Format("Thread{0}", i + 1);
myThread.Start();
}
// The main thread exits, but the application continues to
// run until all foreground threads have exited.
}
private static void MyThreadProc()
{
for(int i = 0; i < numIterations; i++)
{
UseResource();
}
}
// This method represents a resource that must be synchronized
// so that only one thread at a time can enter.
private static void UseResource()
{
// Wait until it is safe to enter.
mut.WaitOne();
Console.WriteLine("{0} has entered the protected area",
Thread.CurrentThread.Name);
// Place code to access non-reentrant resources here.
// Simulate some work.
Thread.Sleep(500);
Console.WriteLine("{0} is leaving the protected area\r\n",
Thread.CurrentThread.Name);
// Release the Mutex.
mut.ReleaseMutex();
}
}
(八) C# 物件導向程式設計、繼承、多型、介面、委託、事件
基本概念
⾯向物件程式設計(Object –Oriented Programming,OOP),抽象、繼承和多型是OOP程式設計語⾔的三⼤要素。
繼承:類繼承的重要特性是,在希望出現基型別例項的任何地⽅,都可以替換成派⽣型別的例項。類似地,介面繼承允許在希望出現已命名介面型別的例項的任何地⽅,都可以替換成實現介面的⼀個型別的實現。
多型:指的是多個型別的物件對同⼀訊息做出各⾃的處理。多型是⼦類對⽗類的⽅法進⾏重寫或替換⽽實現的。
介面:介面是⼀組已命名的⽅法簽名,在介面內還可以定義事件和屬性,它們在本質上也是⽅法。C# 要求介面⽅法標記為 Public。介面的關鍵價值在於隱藏型別的設計細節,即外部物件不依賴當前物件的內部細節。
介面特性
介面⽅法的隱式實現:當⽅法簽名與繼承的介面中的簽名⼀致,並且是public或者是viture修飾的⽅法都視為隱式實現了介面⽅法。
例:
Internal sealed class SimpleType:IDisposable
{
Public void Dispose(){Console.WriteLine(“Dispose”);}
}
介面⽅法的顯式實現:以介面型別名稱作為⽅法字首時,建立的是⼀個顯式介面⽅法實現(explicit interface method implementation,EIMI)。⼀個EIMI⽅法不允許標記訪問性(⽐如公共或私有),也不能被標記為virture,因⽽也不能被重 寫。顯⽰介面⽅法會損害效能,應當謹慎使⽤。
例:
Internal sealed class SimpleType:IDisposable
{
Public void Dispose(){….}
Void IDisposable.Dispose(){….}//顯式
}
對顯式介面的調⽤,需要通過⼀個介面型別的變數來進⾏。
例:
SimpleType st = new SimpleType();
IDisposable d = st;
d.Dispose();
泛型介面有如下優點:
1、使用介面方法變為強型別。
2、泛型介面在操作值型別時,會減少裝箱操作。
3、類可以實現同一個介面若干次,只要使用不同的型別引數。
例:
Public sealed class Number:IComparable<Int32>,IComparable<String>
{
Private int32 m_val =5;
//實現IComparable<Int32>
Public Int32 CompareTo(Int32 n){return m_val.CompareTo(n);}
//實現IComparable<String>
Public Int32 CompareTo(String s){return m_val.CompareTo(Int32.Parse(s));}
}
委託
委託是.NET中的回撥機制。將一個方法繫結到一個委託時,C#和CLR允許引用型別的協變(covariance)和反協變(contra-variance)。協變是指一個方法能返回一個從委託的返回型別派生出來的型別。反協變是指一個方法的引數型別可以是委託的引數型別的基類。但協變對於返回值型別或void的方法不適用。
例:
//MyCallback委託
Delegate object MyCallback(FileStream s);
//SomeMethod⽅法
String SomeMethod(Stream s);
上例中,SomeMethod的返回型別(String)繼承⾃委託返回型別(Object),這種協變是允許的。SomeMethod的引數型別
(Stream)是委託的引數型別(FileStream)的基類。這種反協變是允許的。
鏈式委託指的是⽤⼀個委託回撥多個⽅法,即⼀系列委託物件組成的集合。Delegate的公共靜態⽅法Combine⽤於新增⼀ 個委託到委託鏈,Remove⽅法⽤於從鏈中刪除⼀個委託物件。在C#中內建的+=與-=操作符簡化了這些操作。
例:
Internal delegate void Feedback(int32 value);
Feedback fb1 = new Feedback(….);
Feedback fb2 = new Feedback(….);
fbChain =(Feedback)Delegate.Combine(fbChain,fb1);
fbChain =(Feedback)Delegate.Combine(fbChain,fb2);
⼀組委託是按順序執⾏的,如果他們帶有返回值,只能得到最後⼀個委託的返回值,如果其間有委託⽅法出現致命錯誤,其它委託就⽆法執⾏。為了克服這些問題,產⽣了MulticastDelegate類,它的GetInvocationList⽅法⽤於顯式調⽤鏈中的每 ⼀個委託,並使⽤符合⾃⼰需求的任何演算法。MulticastDelegate類是特殊的型別,只能由系統派⽣,Delegate類已經具備了 MulticastDelegate的能⼒。
委託的便捷實現:
1. 不構造委託物件
例:
internal sealed class AClass
{
public static void CallbackWithoutNewingADelegateObject()
{
ThreadPool.QueueUserWorkItem(SomeAsyncTask,5);
}
private static void SomeAsyncTask(Object o)
{
Console.WriteLine(o);
}
}
上例中ThreadPool類的靜態⽅法QueueUserWorkItem期望接收⼀個WaitCallback委託物件引⽤,該物件又包含⼀個 SomeAsyncTask⽅法引⽤。因為C#編譯器能夠⾃⼰進⾏推斷,所以我們可以省略構造WaitCallback物件的程式碼。
2. 不定義回撥⽅法
例:
internal sealed class AClass
{
public static void CallbackWithoutNewingADelegateObject()
{
ThreadPool.QueueUserWorkItem(delegate(Object obj){Console.WriteLine(obj);},5)
}
}
上例中⽤了⼀段程式碼塊替代了回撥⽅法名,編譯器會⾃動在類中增加⼀個經過命名的基於此程式碼塊的回撥⽅法。
3. 不指定回撥⽅法的引數
例:
button1.Click += delegate(Object sender,EventArgs e){MessageBox.Show(“The Button was clicked”);}
//由於上述⽅法中沒有⽤到sender與e兩個引數,可簡寫為:
button1.Click+=delegate{MessageBox.Show(“ The Button was clicked”);}
4. 不需要將區域性變數⼈⼯封裝到類中,即可傳給⼀個回撥⽅法
事件
事件:在.NET中事件(event)是類的成員,與成員屬性和⽅法⼀樣。型別的事件,是對外提供的⾃⾝狀態的通知。外部類 型通過訂閱的形式與事件的釋出型別進⾏協作。將事件與處理⽅法關聯起來的是委託。.NET中⽤event關鍵指定特定的委託 來為事件做出響應,這樣做可以限制其它⽅法對委託的調⽤(在內部定義委託為私有的,通過event公開,因此外部⽆法訪 問委託中的⽅法)。
設計執行緒安全的事件,必須顯⽰地控制事件的訂閱與登出。
例:
internal class MailManager
{
//建立⼀個作為執行緒同步鎖的私有例項欄位
private readonly Object m_eventLock = new Object();
//增加⼀個引⽤ 委託連結串列頭部的私有欄位
private EventHadler<NewMailEventArgs> m_NewMail;
//增加⼀個事件成員
public event EventHandler<NewMailEventArgs> NewMail
{
//顯式實現add
add
{
//加私有鎖,並向委託連結串列增加⼀個處理程式以‘value’為引數
lock(m_eventLock){m_NewMail+=value;}
}
//顯式實現remove
remove
{
//加私有鎖,並向委託連結串列移除⼀個處理程式以‘value’為引數
lock(m_eventLock){m_NewMail -= value;}
}
}
//定義⼀個負責引發事件的⽅法,來通知已訂閱事件的物件事件已經發⽣,如果類是封裝的
//則需要將⽅法宣告為private和non-virtual
proteted virtual void OnNewMail(NewMailEventArgs e)
{
//出於執行緒安全考慮,將委託欄位儲存到⼀個臨時欄位中
EventHadler<NewMailEventArgs> temp = m_NewMail;
if(temp!=null){temp(this,e);}
}
//將輸⼊轉化為希望的事件
public void SimulateNewMail(String from,String to,String subject)
{
//構建⼀個物件存放給事件接收者的資訊
NewMailEventArgs e = new NewMailEventArgs(from,to,subject);
//引發
OnNewMail(e);
}
}
委託與事件
關鍵字“event”是個修飾詞,在絕⼤多數的情形中,被指定為委託(delegate)的物件和被指定為事件(event)的物件是可以互換的。然⽽,事件還有特殊之處:
● 事件就像⼀個委託型別的欄位。該欄位引⽤了⼀個代表事件處理器的委託,這些處理器是被新增到事件上的;
● 事件只能在宣告它的類中被調⽤,⽽所有能見到委託的地⽅都可以使⽤委託;
● 事件可以被包含在介面中⽽委託不可以;
● 事件有可被重寫的Add和Remove存取(acccessor)⽅法;
(九)、Linq表示式、非同步處理
LINQ
語言整合查詢 (LINQ) 是一系列直接將查詢功能整合到 C# 語言的技術統稱,比如涵蓋:SQL 資料庫查詢、XML 文件查詢、List物件查詢、Array物件查詢、String物件查詢……。 藉助 LINQ,查詢成為了最高階的語言構造,就像類、方法和事件一樣。
示例:
class LINQQueryExpressions
{
static void Main()
{
// Specify the data source.
int[] scores = new int[] { 97, 92, 81, 60 };
// Define the query expression.
IEnumerable<int> scoreQuery =
from score in scores
where score > 80
select score;
// Execute the query.
foreach (int i in scoreQuery)
{
Console.Write(i + " ");
}
}
}
// Output: 97 92 81
更詳細的Linq用法請參考:
非同步處理
非同步是 .NET 中充分使用處理器核心資源的機制,非同步機制直接處理多個核心上的阻塞 I/O 和併發操作以提高系統執行效率。
.NET 非同步的特點:
1、等待 I/O 請求返回的同時,可通過生成處理更多請求的執行緒,處理更多的伺服器請求。
2、等待 I/O 請求的同時生成 UI 互動執行緒,並通過將長時間執行的工作轉換到其他 CPU 核心,讓 UI 的響應速度更快。
使用基於 .NET 任務的非同步模型可直接編寫繫結 I/O 和 CPU 的非同步程式碼。 該模型由 Task 和 Task<T> 型別以及 C# 和 Visual Basic 中的 async 和 await 關鍵字公開。 (有關特定語言的資源,請參見另請參閱部分。)
Task是用於實現稱之為併發 Promise 模型的構造。 簡單地說,它們“承諾”,會在稍後完成工作。
Task 表示不返回值的單個操作。
Task<T> 表示返回 T 型別的值的單個操作。
Task在當前執行緒上執行,且在適當時會將工作委託給作業系統。 可選擇性地通過 Task.Run API 顯式請求任務在獨立執行緒上執行。
示例:
//定義一個基於Task的非同步方法
public Task<string> GetHtmlAsync()
{
// Execution is synchronous here
var client = new HttpClient();
return client.GetStringAsync("https://www.dotnetfoundation.org");
}
//第二個非同步方法
public async Task<string> GetFirstCharactersCountAsync(string url, int count)
{
// Execution is synchronous here
var client = new HttpClient();
// Execution of GetFirstCharactersCountAsync() is yielded to the caller here
// GetStringAsync returns a Task<string>, which is *awaited*
var page = await client.GetStringAsync(url);
// Execution resumes when the client.GetStringAsync task completes,
// becoming synchronous again.
if (count > page.Length)
{
return page;
}
else
{
return page.Substring(0, count);
}
}
//呼叫示例
var str = await GetHtmlAsync();
var str2 = await GetFirstCharactersCountAsync("https://www.dotnetfoundation.org",100);
更深入地瞭解 .NET 上的非同步程式設計
三、.NET 上的 Web 開發: ASP.NET Core
.NET上的Web解決方案由ASP.NET Core 框架實現,某種程度上你可以將之理解為Java界的Spring MVC。ASP.NET 是經典.NET上的Web解決方案,我們建議新的Web應用應該選擇ASP.NET Core。
當前Web開發存在兩種主要的風格:MVC,Web API。MVC指的是模型--檢視--控制器的Web程式設計模式,而Web API指的是面向RESTful API場景的Web程式設計模式,它僅提供API呼叫的響應而不關心檢視。
ASP.NET Core
ASP.NET Core MVC 框架由如下基本元件構成:
路由
模型繫結
模型驗證
依賴關係注入
篩選器
區域
Web API
Razor 檢視引擎
強型別檢視
標記幫助程式
檢視元件
控制器:ASP.NET Core MVC 的Web請求入口是由Controller型別或其子型別的公共方法實現的,一般情況下每個請求入口都是一部分業務邏輯程式碼的聚合。
例:
public class DefaultController : ControllerBase
{
public ActionResult<string> Index()
{
return "hello,world";
}
}
路由:ASP.NET Core MVC 建立在 ASP.NET CORE 的路由之上,是一個功能強大的 URL 對映元件,可用於生成具有易於理解和可搜尋 URL 的應用程式。 它可讓你定義適用於搜尋引擎優化 (SEO) 和連結生成的應用程式 URL 命名模式,而不考慮如何組織 Web 伺服器上的檔案。 可以使用支援路由值約束、預設值和可選值的方便路由模板語法來定義路由。
例:
routes.MapRoute(name: "Default", template: "{controller=Home}/{action=Index}/{id?}");
模型:ASP.NET Core MVC 模型繫結將客戶端請求資料(窗體值、路由資料、查詢字串引數、HTTP 頭)轉換到控制器可以處理的物件中。 因此,控制器邏輯不必找出傳入的請求資料;它只需具備作為其操作方法的引數的資料。
例:
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null) { ... }
模型驗證:ASP.NET Core MVC 通過使用資料註釋驗證屬性修飾模型物件來支援驗證。 驗證屬性在值釋出到伺服器前在客戶端上進行檢查,並在呼叫控制器操作前在伺服器上進行檢查。
例:
using System.ComponentModel.DataAnnotations;
public class LoginViewModel
{
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
Razor檢視引擎: Razor 是一種緊湊、富有表現力且流暢的模板標記語言,用於使用嵌入式 C# 程式碼定義檢視。 Razor 用於在伺服器上動態生成 Web 內容。 可以完全混合伺服器程式碼與客戶端內容和程式碼。我們可以在MVC工程中,往Controller新增請求入口的View檔案,這些View檔案代表檢視檔案(.cshtml),這些檔案預設使用Razor檢視引擎來實現服務端渲染檢視。
例:
Index.cshtml:
<!-- 單行程式碼塊 -->
@{ var myMessage = "Hello World"; }
<!-- 行內表示式或變數 -->
<p>The value of myMessage is: @myMessage</p>
<!-- 多行程式碼塊 -->
@{
var greeting = "Welcome to our site!";
var weekDay = DateTime.Now.DayOfWeek;
var greetingMessage = greeting + " Today is: " + weekDay;
}
<p>The greeting is: @greetingMessage</p>
更深入的 Razor 介紹 http://www.w3school.com.cn/aspnet/razor_intro.asp
Web API: ASP.NET Core 支援使用 C# 建立 RESTful 服務,也稱為 Web API。 Web API 使用控制器響應這些請求,Web API 中的控制器是派生自 ControllerBase 的類。
例:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
public ActionResult<Pet> Create(Pet pet)
{
pet.Id = _petsInMemoryStore.Any() ? _petsInMemoryStore.Max(p => p.Id) + 1 : 1;
_petsInMemoryStore.Add(pet);
return CreatedAtAction(nameof(GetById),
new { id = pet.Id }, pet);
}
}
SignalR: ASP.NET Core SignalR 是一個開原始碼庫,它簡化了嚮應用新增實時 Web 功能的過程。 實時 Web 功能使伺服器端程式碼能夠即時將內容推送到客戶端。
SignalR 的適用物件:需要來自伺服器的高頻率更新的應用。
例如:
遊戲、社交網路、投票、拍賣、地圖和 GPS 應用;
儀表板和監視應用;
協作應用,例如白板應用和團隊會議軟體;
需要通知的應用, 社交網路、電子郵件、聊天、遊戲、行程警示以及許多其他應用都使用通知;
SignalR 提供了一個用於建立伺服器到客戶端遠端過程呼叫(RPC)的 API。 RPC 通過伺服器端 .NET Core 程式碼呼叫客戶端上的 JavaScript 函式。
以下是 ASP.NET Core SignalR 的一些功能:
1、自動管理連線。
2、向所有連線的客戶端廣播訊息。 例如,聊天室。
3、將訊息傳送到特定的客戶端或客戶端組。
4、擴充套件以處理增加的流量。
更深入的瞭解.NET上的Web開發: https://docs.microsoft.com/zh-cn/aspnet/core/?view=aspnetcore-2.2
四、.NET 上的ORM
EF6 & EF Core
EntityFramework 6.x (EF6) 是經典 .NET上的 ORM 框架,它功能全面在Windows上執行穩定。
EntityFramework Core (EF Core) 是 EntityFramework 的跨平臺移植版本,目前功能上與 EF6 仍有差距,可以滿足絕大部分 CRUD 操作。
下圖是 EF6 與 EF Core 在資料庫支援上的對比:
其它ORM
dapper 是Stack Overflow貢獻的輕量級 ORM 框架,相容.NET Core 和 .NET 4.5x,它直接擴充套件了.NET Connection 物件。
SmartSql 是一個包括ORM及延伸功能的資料、快取讀寫與配置框架。
以上介紹的主要的ORM工具都可以在Github上找到其官方主頁。
五、.NET 微服務和容器化
.NET Core 是最早響應微服務與容器化部署的技術平臺。.NET 團隊在Docker Hub 官網上維護著所有主要的 .NET Core 版本的 Docker 映象。
你可以在這個連結上找到這些映象: https://hub.docker.com/_/microsoft-dotnet-core
值得一提的是,.NET Core 在 Docker 上的效能表現超過了大部分其他同類技術平臺。例如使用 Raygun 工具測試相同 Linux 環境的上執行的 Node.js 與 .NET Core 的效能對比,.NET Core 的效能是 Node.js 的2000%。
.NET Core 是天生為雲端計算優化的技術平臺,有著優良的可伸縮性,併相容主流的雲端計算平臺,比如 Azure、AWS、阿里雲。
上圖是 .NET Core 上實現的微服務與 docker 容器部署的典型架構示例
關於如何設計、釋出 .NET Core 的微服務到 Docker 映象,可以下載這個官方中文說明書: https://dotnet.microsoft.com/download/thank-you/microservices-architecture-ebook-zh-cn
六、.NET平臺與Java平臺的互換性
.NET | Java | |
包管理 |
nuget | Maven |
Web場景開發 |
ASP.NET
ASP.NET Core
|
Spring Boot |
ORM |
EntityFramework 6.x
EntityFramework Core
dapper
NHibernate
SmartSql
|
Hibernate
Mybatis
|
單元測試 |
MSUnit
XUnit.net
|
JUnit |
Android/ios 開發 | Xamarin |
Android SDK
RoboVM
|
Windows 開發 |
.NET Framework 4.x
.NET Core 3.0+
Mono |
Oracle JDK
Open JDK (free)
|
Mac OS 開發 |
Mono
Xamarin/.NET Core
|
Oracle JDK
Open JDK(free)
|
linux開發 |
Mono
.NET Core
|
Oracle JDK
Open JDK(free)
|
docker支援 |
.NET Core
ASP.NET Core
Mono
|
Oracle JDK
Open JDK(free)
|
AI/資料分析 |
ML.net
ONNX Runtime Microsoft Cognitive Toolkit(CNTK) tensorflow.net
.NET for Apache Spark
|
Eclipse Deeplearning4j
Apache OpenNLP
Java-ML
Spark
Flink
Kafka
Storm
|
遊戲開發 |
Unity (C#語言)
MonoGame
CRYENGINE
|
|
IoT | .NET Core | Open IoT Stack for Java |
以上關於 .NET 平臺及 Java 平臺的比較資訊來源於一小部分有代表性的技術棧,僅供參考。
關於 .NET 平臺更多的生態內容可以參考這個連結:https://github.com/thangchung/awesome-dotnet-core
感謝 .NET 社群中的朋友幫忙審校。
參考連結: