1. 程式人生 > >【程式語言】C# 從菜鳥到高手

【程式語言】C# 從菜鳥到高手

1 Overview

1.1 CLI

  • common language infrastructure
  • The .NET Framework, .NET Core, Mono, DotGNU and Portable.NET are implementations of the CLI.
  • 能更容易的設計跨語言互動的元件和應用程式,也就是不同語言的物件能相互通訊
  • CLR 某種意義上可理解為 Java 的 JVM,而 MSIL 相當於 Java 中的位元組碼;

1.2 .NET Framework 是什麼

.NET Framework 由一個巨大的程式碼庫組成,適用於 C#,C++,VB,Jscript,COBOL等等,這些語言可以訪問框架,彼此之間也可以相互訪問:

1.3 C# 語言特徵

C# is a multi-paradigm programming language encompassing strong typing, imperative, declarative, functional, generic, object-oriented (class-based), and component-oriented programming disciplines.

  • 布林條件(Boolean Conditions)
  • 自動垃圾回收(Automatic Garbage Collection)
  • 標準庫(Standard Library)
  • 元件版本(Assembly Versioning)
  • 屬性(Properties)和事件(Events)
  • 委託(Delegates)和事件管理(Events Management)
  • 易於使用的泛型(Generics)
  • 索引器(Indexers)
  • 條件編譯(Conditional Compilation)
  • 簡單的多執行緒(Multithreading)
  • LINQ 和 Lambda 表示式
  • 整合 Windows

1.4 Programming paradigms

程式設計正規化

  1. imperative programming
  2. declarative programming
  3. structured programming, block-structured programming
  4. modular programming
  5. object-oriented programming
  6. 指令式程式設計語言與宣告式程式語言,前者更關注於怎麼做,how,後者更關注於做什麼,what;
  7. Procedural programming is a type of imperative programming; 程式由若干過程(subroutine,function,)組成;
  8. proc, def, sub, func, …
  9. From the 1960s onwords, structured programming and modular programming in general have been promoted as a techniques to improve the maintainability and overall quality of imperative programs. And then is object-oriented programming.
  10. The hardware implementation of almose all computers is imperative.

<待續>

2 C# 基礎

2.1 關鍵字

  1. namespace, using : Java 中通過 package,import 來管理名稱空間, 可巢狀,如 System.Console
  2. 識別符號可以以 @ 開頭,比如@if,第一個字元不能是數字

保留關鍵字:

  • checked/unchecked
  • decimal : 128 bit 十進位制值
  • default
  • delegate
  • event
  • explicit/implicit
  • extern
  • fixed
  • foreach : 遍歷陣列 foreach (int j in intArray)
  • in
  • internal
  • is : 判斷物件是否為某型別, if(xiaoming is JingCha)
  • as : 強制型別轉換, StringReader r = obj as StringReader;
  • lock
  • namespace
  • object
  • operator : 運算子過載
  • out
  • params : 不定引數
  • readonly
  • ref
  • sealed : 密封類不能被繼承,Java 中為 final
  • sizeof :獲取一個型別或變數的儲存大小,sizeof(stype) == 2
  • stackalloc : 在棧上分配空間,用於 unsafe code
  • struct
  • typeof: 獲取 class 的型別,typeof(StreamReader)
  • unsafe
  • virtual : virtual 方法可以在不同的繼承類中有不同的實現,C# 通過抽象類和虛方法來實現動態多型性
  • volatile
  • & : 返回變數的地址

上下文關鍵字:

  • add
  • alias
  • ascending/descending
  • dynamic : 執行時進行型別檢查,而不是編譯期
  • from
  • get
  • global
  • group
  • into
  • join
  • let
  • orderby
  • partial
  • remove
  • select
  • set

2.2 型別

2.2.1 引用

  引用是一個指向實際儲存空間的一個指標,C#的引用型別有: object,dynamic,string

  object 可以理解為 void *,在使用時,需要進行型別轉換。當一個值型別轉換為物件型別時,稱為裝箱;當一個物件型別轉換為值型別時,稱為拆箱。

object obj;
obj = 100

  在 java 中:

Integer num = 100;
Object obj = 100;

  dynamic 變數的型別檢查時在執行時發生的, 而物件型別變數的型別檢查時在編譯時發生的。

dynamic a = 20;

  String 型別允許給變數分配任何字串值,可以通過兩種形式:引號和@引號

string a = @"c:\windows";
string b = "c:\\windows";
string c = @"<script type=""text/javascript"">
    <!--
    -->
</script>"; // 在寫usage方法時,非常有用

2.2.2 指標型別

C# 中指標與 c/c++中的指標有相同的功能。

type* tptr;
char* cptr;
int* iptr;

2.2.3 變數

本質上講,變數就是特定記憶體的名稱,而型別就是變數所指的記憶體塊的大小。

int num = Convert.ToInt32(Console.ReadLine());

2.3 封裝

訪問修飾符:

  1. public
  2. private
  3. protected
  4. internal : 只有同一個程式集的物件可以訪問
  5. protected internal

2.4 引數傳遞

  • 值引數 : 賦值引數的實際值,實參和形參使用不同的記憶體區,方法呼叫不影響實參的值;
  • 引用引數 : 會改變實參的值
  • out 引數 : 接收返回結果,使用out就不需要通過賦值獲取 return 的值了。
void swap_ref(ref int x, ref int y){ int tmp = x; x = y; y = tmp;};
void swap_value(int x, int y) {int tmp = x; x = y; y = tmp;};
void value_out(out int x) {x=8;};

  對應 java 程式碼:需要特別注意的是,引用本身是值變數,在進行引數傳遞的時候,實參和形參是不同的引用!

public static void getValue(Integer a) {
    a = 200; /、 這裡的 a 引用跟形參傳入的 b 已經是不同的變量了
}

public static void getValue(int[] a) {
    a[1] = 5;
}


Integer b = new Integer(100);
getValue(b);
System.out.println(b); // b = 100

int[] c = {1, 2, 3};
getValue(c);
System.out.println(c[1]); // c = {1, 5, 3}

  感受一下 Java 與 C# 之間的不同!

2.5 Nullable

int i; // 預設值為 0
int? j; // 預設值為 null

  首先,primitive type 如 int,double,bool 等無法直接賦值null,比如在 java 中:

int i; // compile error: 變數 i 未初始化
int i = null; // compile error: 不相容的型別,nulltype 無法轉換為 int;
Integer i = null; // OK

  在處理資料庫和其他包含可能為負值的元素的資料型別時,將 null 賦值給數值型別或布林型的功能特別有用。

  比如使用 Hibernate 時,如果資料表中的 column 時允許為 null 的,那麼 Entity 中的型別必須為 Integer,而不能為 int,否則就會出錯。

2.6 String

  • String.Join
  • String.Format
  • String.Compare
  • String.Concat
  • String.Contains
  • String.Copy
  • String.Equals
  • String.EndsWith
  • String.IndexOf
  • String.Insert
  • String.Remove
  • String.Replace
  • String.Split
  • Strint.Trim
  • String.ToLower
  • String.TOUpper
  • String.Length
//方法返回字串
string[] sarray = { "Hello", "From", "Tutorials", "Point" };
string message = String.Join(" ", sarray);
Console.WriteLine("Message: {0}", message);

//用於轉化值的格式化方法
DateTime waiting = new DateTime(2012, 10, 10, 17, 58, 1);
string chat = String.Format("Message sent at {0:t} on {0:D}", 
waiting);
Console.WriteLine("Message: {0}", chat);

2.7 Struct 結構體

結構體是值型別,不是引用型別,是用來代表一個記錄。

struct Books 
{
    public string title;
    public string author;
    public string subject;
    public int id;
}

Books book; // 已經進行了例項化,即分配了book的儲存區
  • 可以帶有方法、欄位、索引、屬性、運算子方法和事件;
  • 可定義建構函式,不能定義解構函式
  • 不能被繼承
  • 可以實現多個介面
  • 成員訪問描述符不能為: abstract,virtual,protected
  • 與類不同,介面例項化可以不適用 New

  引用物件的值是儲存在堆空間的,值型別的物件值是儲存在棧上的, 生存期不同。記憶體訪問方式不同,效率也不一樣,但是棧空間大小是有限的。

2.8 Enum 列舉

  C# 列舉是值型別,不能被繼承;

enum Days { Sun, Mon, Tue, Wed, Thu, Fri, Sat};

2.9 Class

成員函式: 物件的方法
成員變數: 物件的屬性

2.9.1 繼承的形式

<acess-specifier> class <base_class>
{
 ...
}

<acess-specifier> interface <base_interface>
{
 ...
}

class <derived_class> : <base_class>, <base_interface>
{
 ...
}

  • Java 中需要關鍵字 extends, implements 來實現繼承;
  • C# 不支援多重繼承,單可以使用介面來實現多重繼承
  • 通過 inteface 來定義介面

2.9.2 多型

  簡單的說,就是相同的介面,不同的行為;在程式語言層面,又分為靜態多型性與動態多型性,也稱為靜態繫結與動態繫結。靜態繫結主要是指函式過載,運算子過載;而動態繫結則指函式重寫(),C# 通過抽象類和虛方法來實現動態多型性。

  C# 中的 virtual, override 在 Java 中體現為 @Override 方法。

2.10 運算子過載

public static result-type operator unary-operator ( op-type operand )
public static result-type operator binary-operator ( op-type operand, op-type2 operand2 )
public static implicit operator conv-type-out ( conv-type-in operand )
public static explicit operator conv-type-out ( conv-type-in operand )
  • result-type 運算子的結果型別。
  • unary-operator 下列運算子之一:+ - ! ~ ++ — true false
  • op-type 第一個(或唯一一個)引數的型別。
  • operand 第一個(或唯一一個)引數的名稱。
  • binary-operator 其中一個:+ - * / % & | ^ << >> == != > < >= <=
  • op-type2 第二個引數的型別。
  • operand2 第二個引數的名稱。
  • conv-type-out 型別轉換運算子的目標型別。
  • conv-type-in 型別轉換運算子的輸入型別。

2.11 預處理指令

  前處理器指令指導編譯器在實際編譯開始之前對資訊進行預處理。

  • define 它用於定義一系列成為符號的字元。
  • undef 它用於取消定義符號。
  • if 它用於測試符號是否為真。
  • else 它用於建立複合條件指令,與 #if 一起使用。
  • elif 它用於建立複合條件指令。
  • endif 指定一個條件指令的結束。
  • line 它可以讓您修改編譯器的行數以及(可選地)輸出錯誤和警告的檔名。
  • error 它允許從程式碼的指定位置生成一個錯誤。
  • warning 它允許從程式碼的指定位置生成一級警告。
  • region 它可以讓您在使用 Visual Studio Code Editor 的大綱特性時,指定一個可展開或摺疊的程式碼塊。
  • endregion 它標識著 #region 塊的結束。

2.12 正則表示式

Regex

  • Regex.IsMatch
  • Regex.Matches
  • Regex.Replace
  • Regex.Split

2.13 異常處理

  • System.IO.IOException 處理 I/O 錯誤。
  • System.IndexOutOfRangeException 處理當方法指向超出範圍的陣列索引時生成的錯誤。
  • System.ArrayTypeMismatchException 處理當陣列型別不匹配時生成的錯誤。
  • System.NullReferenceException 處理當依從一個空物件時生成的錯誤。
  • System.DivideByZeroException 處理當除以零時生成的錯誤。
  • System.InvalidCastException 處理在型別轉換期間生成的錯誤。
  • System.OutOfMemoryException 處理空閒記憶體不足生成的錯誤。
  • System.StackOverflowException 處理棧溢位生成的錯誤。

2.14 檔案 IO

  • BinaryReader 從二進位制流讀取原始資料。
  • BinaryWriter 以二進位制格式寫入原始資料。
  • BufferedStream 位元組流的臨時儲存。
  • Directory 有助於操作目錄結構。
  • DirectoryInfo 用於對目錄執行操作。
  • DriveInfo 提供驅動器的資訊。
  • File 有助於處理檔案。
  • FileInfo 用於對檔案執行操作。
  • FileStream 用於檔案中任何位置的讀寫。
  • MemoryStream 用於隨機訪問儲存在記憶體中的資料流。
  • Path 對路徑資訊執行操作。
  • StreamReader 用於從位元組流中讀取字元。
  • StreamWriter 用於向一個流中寫入字元。
  • StringReader 用於讀取字串緩衝區。
  • StringWriter 用於寫入字串緩衝區。

3 高階特性

3.1 Attribute

  C# 的 Attribute 類似於 Java 的 Annotation,用於在執行時向程式傳遞在程式碼元素(如類、方法、結構、列舉、元件等)中宣告的各類資訊。

  Attribute 用於新增元資料,如編譯器指令和註釋、描述、方法、類等其他資訊,有預定義特性和自定義特性兩類。

  語法:

[attribute(positional_parameters, name_parameter = value, ...)]
element

3.1.1 預定義特性

  1. AttributeUsage
  2. Conditional
  3. Obsolete : 在 java 中使用 @Deprecated

3.1.2 建立自定義特性

。。。

3.2 反射 Reflection

  反射是指程式可以訪問、檢測和修改它本身狀態和行為的一種能力。

  程式集包含模組,模組中有類,類有成員和方法,反射則封裝了描述程式、模組、類等元資訊的物件。可以通過反射動態地建立型別的例項,或者將型別繫結到現有物件,或者從現有物件中獲取型別,還可以在執行時獲取特性的資訊。

  優點:

  • 反射提高了程式的靈活性和擴充套件性。
  • 降低耦合性,提高自適應能力。
  • 它允許程式建立和控制任何類的物件,無需提前硬編碼目標類。

  缺點:

  • 效能問題:使用反射基本上是一種解釋操作,用於欄位和方法接入時要遠慢於直接程式碼。因此反射機制主要應用在對靈活性和拓展性要求很高的系統框架上,普通程式不建議使用。
  • 使用反射會模糊程式內部邏輯;程式設計師希望在原始碼中看到程式的邏輯,反射卻繞過了原始碼的技術,因而會帶來維護的問題,反射程式碼比相應的直接程式碼更復雜。

  MemberInfo 物件中封裝了型別資訊

System.Reflection.MemberInfo info = typeof(MyClass);

3.3 屬性 Property

  屬性是 field 的擴充套件,通過訪問器來訪問。

  訪問器通過上下文關鍵字 get/set 來定義。

    class Student
   {

      private string code = "N.A";

      // 宣告型別為 string 的 Code 屬性
      public string Code
      {
         get
         {
            return code;
         }
         set
         {
            code = value;
         }
      }
    }

3.4 索引器

  允許一個物件可以像被陣列一樣被索引,當為一個類定義一個索引器時,該類的行為就會想一個虛擬陣列一樣,可以使用[]來訪問。

  有點類似於運算子過載的一個特例,[] 本質上可以看作為運算子,在 C++ 中就可以對其進行運算子過載。

element-type this[int index] 
{
   // get 訪問器
   get 
   {
      // 返回 index 指定的值
   }

   // set 訪問器
   set 
   {
      // 設定 index 指定的值 
   }
}

  注意: 索引器可以宣告為多個引數,引數型別也可以為其他型別,比如字串;

3.5 委託 Delegate

  C# 中的委託類似於c/c++中的函式指標,委託讓 C# 中的函式成為第一類物件,可以作為引數和返回值來進行傳遞,在實現事件和回撥方法時特別有用。在 Java 中需要藉助 Function 介面來實現此類功能。

  宣告委託,也類似於 c/c++ 宣告函式指標:

delegate

  比如,宣告一個帶有單個 string 引數且返回 int 值的方法的委託:

public delegate int MyDelegate (string s);

  委託通過關鍵字 new 來例項化,

MyDelegate a = new MyDelegate(TargetMethod);

  委託可以多播!與事件處理有很大關係!!

  委託在使用形式上比 Java 的 Function 更簡潔,更流暢一些。

  匿名方法:是一種無名的委託物件,將程式碼塊賦值給匿名委託:

delegate void NumberChanger(int n);
...
NumberChanger nc = delegate(int x)
{
    Console.WriteLine("Anonymous Method: {0}", x);
};


void change(int x)
{
    ...
}

NumberChanger nc1 = new NumberChanger(change;

3.6 事件 Event

  • 釋出器: 管理事件與訂閱者的對映關係,併發布事件到器訂閱者;
  • 訂閱器: 又稱為監聽器,處理器等,接收一個事件,並處理

  事件宣告:

public delegate void BoilerLogHandler(string status); // 宣告事件的委託型別,也就是定義 handler
public event BoilerLogHandler BoilerEventLog; // 定義事件

  使用事件的步驟:

  1. 例項化釋出器
  2. 例項化訂閱器
  3. 使用訂閱器的方法來例項化釋出器的委託
  4. 將例項化的委託新增到釋出器事件中

3.7 集合 Collection

  集合類是專門用於資料儲存和檢索的類,根據結構可分為 Stack,Deque,List,Set,Map

  System.Collection 名稱空間的類:

  1. 動態陣列(ArrayList):它代表了可被單獨索引的物件的有序集合。它基本上可以替代一個數組。但是,與陣列不同的是,您可以使用索引在指定的位置新增和移除專案,動態陣列會自動重新調整它的大小。它也允許在列表中進行動態記憶體分配、增加、搜尋、排序各項。
  2. 雜湊表(Hashtable):它使用鍵來訪問集合中的元素。當您使用鍵訪問元素時,則使用雜湊表,而且您可以識別一個有用的鍵值。雜湊表中的每一項都有一個鍵/值對。鍵用於訪問集合中的專案。
  3. 排序列表(SortedList):它可以使用鍵和索引來訪問列表中的項。排序列表是陣列和雜湊表的組合。它包含一個可使用鍵或索引訪問各項的列表。如果您使用索引訪問各項,則它是一個動態陣列(ArrayList),如果您使用鍵訪問各項,則它是一個雜湊表(Hashtable)。集合中的各項總是按鍵值排序。
  4. 堆疊(Stack):它代表了一個後進先出的物件集合。當您需要對各項進行後進先出的訪問時,則使用堆疊。當您在列表中新增一項,稱為推入元素,當您從列表中移除一項時,稱為彈出元素。
  5. 佇列(Queue):它代表了一個先進先出的物件集合。當您需要對各項進行先進先出的訪問時,則使用佇列。當您在列表中新增一項,稱為入隊,當您從列表中移除一項時,稱為出隊。
  6. 點陣列(BitArray):它代表了一個使用值 1 和 0 來表示的二進位制陣列。當您需要儲存位,但是事先不知道位數時,則使用點陣列。您可以使用整型索引從點陣列集合中訪問各項,索引從零開始。

3.8 泛型 Generic

  泛型允許引數化型別,在集合類中大量使用。

  使用泛型是一種增強程式功能的技術,具體表現在以下幾個方面:

  1. 它有助於您最大限度地重用程式碼、保護型別的安全以及提高效能。
  2. 您可以建立泛型集合類。.NET 框架類庫在 System.Collections.Generic 名稱空間中包含了一些新的泛型集合類。您可以使用這些泛型集合類來替代 System.Collections 中的集合類。
  3. 您可以建立自己的泛型介面、泛型類、泛型方法、泛型事件和泛型委託。
  4. 您可以對泛型類進行約束以訪問特定資料型別的方法。
  5. 關於泛型資料型別中使用的型別的資訊可在執行時通過使用反射獲取。

3.9 多執行緒

System.Threading

3.10 Iterator

  直接 yield return

public static System.Collections.IEnumerable SomeNumbers()  
{  
    yield return 3;  
    yield return 5;  
    yield return 8;  
}

  通過迴圈來 yield return

public static System.Collections.Generic.IEnumerable<int>  
    EvenSequence(int firstNumber, int lastNumber)  
{  
    for (int number = firstNumber; number <= lastNumber; number++)  
    {  
        if (number % 2 == 0)  
        {  
            yield return number;  
        }  
    }  
}  

  建立 Collection類: foreach 語句會隱式呼叫 GetEnumerator 方法

public class DaysOfTheWeek : IEnumerable  
{  
    private string[] days = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };  

    public IEnumerator GetEnumerator()  
    {  
        for (int index = 0; index < days.Length; index++)  
        {  
            // Yield each day of the week.  
            yield return days[index];  
        }  
    }  
}  

  yield return 可以在 method 或者 get 訪問器中呼叫。儘管 iterator 的定義像個方法,但其實編譯器會將其轉換為一個巢狀類(nested class),本質上是個狀態機。這個類會在 佛reach loop 中跟蹤 iterator 的位置。

  可以通過 Ildasm.exe 工具去檢視編譯器為 iterator 生成的中間程式碼。

3.11 表示式樹 Expression Trees

  簡單來說就是語法樹,通過樹形結構的節點來儲存表示式。通過表示式樹可以動態修改可執行程式碼。

  下面的程式碼展現瞭如何通過 API 來建立 lambda 表示式對應的 Expression tree。

ParameterExpression numParam = Expression.Parameter(typeof(int), "num");  
ConstantExpression five = Expression.Constant(5, typeof(int));  
BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);  
Expression<Func<int, bool>> lambda1 =  
    Expression.Lambda<Func<int, bool>>(  
        numLessThanFive,  
        new ParameterExpression[] { numParam });  

  Expression tree api 也支援賦值語句,控制語句(迴圈,條件),以及 try-catch 程式碼塊。

  編譯與執行

// Creating an expression tree.  
Expression<Func<int, bool>> expr = num => num < 5;  

// Compiling the expression tree into a delegate.  
Func<int, bool> result = expr.Compile();  

// Invoking the delegate and writing the result to the console.  
Console.WriteLine(result(4));  

// Prints True.  

// You can also use simplified syntax  
// to compile and run an expression tree.  
// The following line can replace two previous statements.  
Console.WriteLine(expr.Compile()(4));  

4 非同步程式設計

4.1 async, await

  非同步/非阻塞程式設計,可提升應用的響應性。比如在 web 開發,以及 ui 開發上,大有益處。

  但傳統的實現非同步的方式都比較複雜,比如 Java 中的 nio,需要了解 channel,selector,buffer 這些概念,或者使用 netty 這樣的網路框架。c/c++ 進行非同步/非阻塞程式設計,則需要理解 select,poll,epoll 等概念。難以開發,也難以維護。

  C# 則通過引入 async,await 等關鍵字,讓編譯器去處理複雜的非同步/非阻塞邏輯,讓語言層面的非同步邏輯清晰流暢。

  使用 async 來指定 method,lambda expression, or anonymous method 為非同步。

  例如:

public async Task<int> ExampleMethodAsync()  
{  
    // . . . .  
} 

  任何函式(方法),如果內部呼叫了 async 方法,那麼該方法也必須宣告為 async 方法,除非通過 await 關鍵字來呼叫 async 方法或者其返回結果 。

async Task<int> AccessTheWebAsync()  
{   
    HttpClient client = new HttpClient();  
    Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");  

    
    DoIndependentWork();  
    string urlContents = await getStringTask;  

    return urlContents.Length;  
}  
  1. 方法簽名中有 async
  2. 方法名字尾 Async
  3. 返回值為 Task

  await 標明方法可以執行到何處,await 之前應該為結果無關的獨立邏輯,當執行到 awati 時停止,等待非同步操作完成。同時該方法被掛起,控制權返回至上層函式。

4.2 控制流程

這裡寫圖片描述

  1. 一個 event handler 呼叫並 await 一個非同步方法: AccessTheWebAsync
  2. AccessTheWebAsync 呼叫非同步方法 GetStringAsync 去網站下載資料
  3. 當 GetStringAsync 因為某種原因需要掛起時,不會阻塞,會將控制權重新交給 AccessTheWebAsync
  4. AccessTheWebAsync 會執行它的同步程式碼邏輯,直到 await 語句,然後掛起並將控制權交給 event handler
  5. 當 GetStringAsync 完成時,結果會存放在其返回的 Task 物件中,await getStringTask 返回結果。

 async 方法是非阻塞的,await 表示式並不會使當前執行緒阻塞,它只是標註掛載點並將控制權轉移至呼叫方。async 和 await 關鍵字並不會去建立額外的執行緒,非同步機制並不是通過多執行緒來實現的。