跳至內容

C Sharp語法

維基百科,自由的百科全書

C Sharp語法英語Syntax (programming languages)是編寫該語言程序的一套規則。也適用於.NET Framework and Mono

C# 2.0的特性

[編輯]

針對於.NET SDK 2.0(相對應於ECMA-334標準第三版),C# 的新特性有:

分部類別

[編輯]

分部類別將類別的實現分在多個文件中。該概念於C# 中首次出現,除了能將一個類別的成員分開存放,還使ASP.NET中的代碼後置得以實現。代碼後置實現了HTML代碼和後台交互代碼的分離。

file1.cs:

public partial class MyClass1
{
    public void MyMethod1()
    {
        // implementation
    }
}

file2.cs:

public partial class MyClass1
{
    public void MyMethod2()
    {
        // implementation
    }
}

分部類別這個特性允許將一個類別的編寫工作分配給多個人,一人寫一個文件,便於版本控制。它又可以隔離自動生成的代碼和人工書寫的代碼,例如設計窗體應用程式時。

泛型

[編輯]

泛型,或參數化類型,是被C#支持的.NET 2.0特性。不同於C++模版,.NET參數化類型是在運行時被實例化,而不是編譯時,因此它可以跨語言,而C++模版卻不行。C#泛型類在編譯時,先生成中間代碼IL,通用類型符號T只是一個占位符;在實例化類時,根據實際數據類型代替T並由即時編譯器(JIT)生成本地代碼,其中使用了實際的數據類型,等同於用實際類型寫的普通的類。

它支持的一些特性並不被C++模版直接支持,比如約束泛型參數實現一個接口。另一方面,C# 不支持無類型的泛型參數。不像Java中的泛型,在CLI虛擬機中,.NET generics使用具化生成泛型參數,它允許優化和保存類型信息。[1]

泛型類中,可以用where關鍵字對參數類型實現約束。例如:

 public class Node<T, V> where T : Stack, IComparable, new(),class where V: Stack,struct
 {...}

上述表示T和V必須是Stack類或其派生類,T必須繼承了IComparable接口、有無參構造函數、是引用類型;V必須是值類型。

泛型不僅能作用在類上,也可單獨用在類的方法上,稱為「泛型方法」。

泛型類的靜態成員變量在相同封閉類間共享,不同的封閉類間不共享。

泛型類中的方法重載,參數類型T和V在運行時確定,不影響這個類通過編譯。C#的泛型是在實例的方法被調用時檢查重載是否產生混淆,而不是在泛型類本身編譯時檢查。特別地,當一般方法與泛型方法具有相同的簽名時,會覆蓋泛型方法。

靜態類別

[編輯]

靜態類別它不能被實例化,並且只能有靜態成員。這同很多過程語言中的模塊概念相類似。

迭代器

[編輯]

一種新形式的迭代器它提供了函數式編程中的generator,使用yield return

類似於Python中使用的yield

// Method that takes an iterable input (possibly an array)
// and returns all even numbers.
public static IEnumerable<int> GetEven(IEnumerable<int> numbers)
{
    foreach(int i in numbers)
    {
        if (i % 2 == 0) yield return i;
    }
}

注意事項:

  • foreach循環時考慮執行緒安全性,不要試圖對被遍歷的集合進行remove和add等操作
  • IEnumerable接口是LINQ特性的核心接口。只有實現了IEnumerable接口的集合,才能執行相關的LINQ操作,比如select,where等

匿名方法

[編輯]

匿名方法類似於函數式編程中的閉包[2]匿名方法是通過使用 delegate 關鍵字創建委託實例來聲明的。例如:


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



public void Foo(object parameter)
{
    // ...

    ThreadPool.QueueUserWorkItem(delegate
    {
        // anonymous delegates have full access to local variables of the enclosing method
        if(parameter == ...)
        { 
            // ... 
        }

        // ...
    });
}

委託的協變和逆變

[編輯]

委託簽名的協變和逆變,[3]

屬性訪問器可以被單獨設置訪問級別

[編輯]

例子:

string status = string.Empty;

public string Status
{
    get { return status; }             // anyone can get value of this property,
    protected set { status = value; }  // but only derived classes can change it
}

可空類型

[編輯]

可空類型(跟個問號,如int? i = null;)允許設置null給任何類類型。

int? i = null;
object o = i;
if(o == null)
    Console.WriteLine("Correct behaviour - runtime version from September 2005 or later");
else
    Console.WriteLine("Incorrect behaviour - pre-release runtime (from before September 2005)");

??運算子

[編輯]

??):如果左運算數表達式的值不為空值時回傳該值,如果為空值則返回右運算數表達式的值。

object nullObj = null; 
object obj = new Object(); 
return nullObj ?? obj; // returns obj

主要用作將一個可空類型賦值給不可空類型的簡便語法

int? i = null;
int j = i ?? 0; // Unless i is null, initialize j to i. Else (if i is null), initialize j to 0.

C# 3.0的特性

[編輯]

C# 3.0發布於2007年10月17日,是.NET Framework 3.5的一部分,它的新特性靈感來自於函數式編程語言,如:HaskellML,並廣泛地引入了Language Integrated Query(LINQ)模式到通用語言運行庫中e.[4]

Linq

[編輯]

語言集成查詢(英語:Language Integrated Query,縮寫:LINQ):[5] 上下文相關關鍵字"from, where, select"可用於查詢SQL、XML、集合等。這些標識符在LINQ上下文中被作為關鍵字,但是它們的增加不會破壞原有的名為fromwhereselect的變量。

類型初始化器

[編輯]
Customer c = new Customer();
c.Name = "James";

可寫作:

Customer c = new Customer() { Name="James" };

集合初始化器

[編輯]
MyList list = new MyList();
list.Add(1);
list.Add(2);

可寫作

MyList list = new MyList { 1, 2 };

假設MyList實現了System.Collections.IEnumerable且有一個Add方法method[6]

匿名類型

[編輯]
var x = new { Name="James" };

局部變量類型推斷

[編輯]

局部變量類型推斷

var x = new Dictionary<string, List<float>>();

等同於

Dictionary<string, List<float>> x = new Dictionary<string, List<float>>();

它只是一個語法糖,這個特性被匿名類型聲明時所需要

Lambda表達式

[編輯]

Lambda表達式(無函式名稱的物件方法在程式語言中的表達語法):

listOfFoo.Where(
    delegate(Foo x)
    {
        return x.Size > 10; 
    }
)
可寫作
listOfFoo.Where(x => x.Size > 10);

編譯器翻譯Lambda表達式為強類型委託或強類型表達式樹

注意事項:

  • 如果只有一個參數,可以省略括號(),例如 item=>{Console.WriteLine("只有一個參數{0}的Lambda表達式",item); };
  • 如果只有一個返回值的語句,可以省略花括號{}、return關鍵字、分號,例如 item => {return item % 2 == 0;};改寫成:item =>item %2 == 0;
  • Lambda表達式可以分配給Func,Action或Predicate委託。

自動化屬性

[編輯]

編譯器將自動生成私有變量和適當的getter(get訪問器)和setter(set訪問器),如:

public string Name
{
    get; 
    set; 
}

擴展方法

[編輯]

擴展方法能夠使現有的類型添加方法,而無需創建新的派生類型、重新編譯或以其它方式修改原始類型。

使用擴展方法,必須在一個非嵌套、非泛型的靜態類中定義一個靜態方法,方法第一個參數必須附加this關鍵字作為前綴,第一個參數不能有其它修飾符(如ref或者out),這個方法將被編譯器添加到該this的類型中。

public static class IntExtensions
{
    public static void PrintPlusOne(this int x) 
    {
        Console.WriteLine(x + 1);
    }
}
 
int foo = 0;
foo.PrintPlusOne();

注意事項:

  • 擴展方法只會增加編譯器的工作,但不會影響程序運行性能(用繼承的方式為一個類型增加特性反而會影響性能)
  • 如果原來的類中有一個方法,跟擴展方法一樣,那麼擴展方法不會被調用,編譯器也不會提示

分部方法

[編輯]

允許代碼生成器生成方法聲明作為擴展點,如果有人在另一個部分類實現了它才會被包含於原代碼編譯。[7]

  1. 分部方法(Partial methods)必須定義在分部類(partial classes)中
  2. 定義分部方法需要用partial做修飾符
  3. 分部方法不一定總是有執行內容的,也就是說定義的方法可以一句操作語句都沒有
  4. 分部方法返回值必須是void
  5. 分部方法可以是靜態(static)方法
  6. 分部方法可以包含參數,參數可以包含以下修飾詞:this,ref,params
  7. 分部方法必須是私有(private)方法

例子:

partial class C
{
    static partial void M(int i); // defining declaration
}
partial class C
{
    static partial void M(int i)
    {
        dosomething();
    }
}

C# 4.0的特性

[編輯]

dynamic類型

[編輯]

C# 4.0新增dynamic關鍵字,提供動態編程(dynamic programming),把既有的靜態物件標記為動態物件,類似javascript, PythonRuby

dynamic關鍵字標記的實例被處理成一個特殊包裝的object對象,取消了CLI的編譯時類型檢查,編譯時被假定支持任何操作,但如果並不實際支持則運行時報錯。

dynamic calc = GetCalculator();
int sum = calc.Add(10, 20);

具名參數與可選參數

[編輯]
public StreamReader OpenFile(
    string path,
    int bufferSize = 1024)
{
...
}

呼叫OpenFile時,順序可以完全顛倒:

OpenFile(bufferSize: 4096, path: "foo.txt");

與COM組件互動

[編輯]

在C#中打開一個Word文件:

static void Main(string[] args) {
    Word.Application wordApplication = new   
       Word.Application() {Visible = true};     
    wordApplication.Documents.Open(@"C:\plant.docx",   
       ReadOnly: true);  
}

在C#中指定Excel的某一格文字:

excelObj.Cells[5, 5].Value = "This is sample text";

泛型的協變和逆變

[編輯]

C# 4.0支援協變和逆變,例如在泛型介面可以加上in、out修饰字。

  public interface IComparer<in T>  
  {  
    int Compare(T left, T right);  
  }

  public interface IEnumerable<out T> : IEnumerable
  {
    IEnumerator<T> GetEnumerator();
  }

C# 5.0的特性

[編輯]

Async

[編輯]

async和await是一對語法糖,允許開發人員非常輕鬆的調用基於TASK的異步編程。async-await關鍵字並不會真的創建一個執行緒池任務,完成這個動作依賴於被調用方法中的函數。

Caller info attributes

[編輯]

CallerInfoAttributes用於調試時訪問調用者的信息。包括三個主要的類:

  • [CallerMemberName] :返回調用函數的名稱。
  • [CallerFilePath] :返回調用函數所在源文件全路徑信息 。
  • [CallerLineNumber] :返回調用函數調用具體行號。
using System;
using System.Runtime.CompilerServices;

namespace TestPro
{
    class Program
    {
        public static void Main()
        {
            Log("Test.");
        } 

        // 对日志消息进行记录,同时所有内容均有默认值,如果获取失败,则使用默认值。
        public static void Log(string message,
            [CallerMemberName] string callerName = "unknown", 
            [CallerFilePath] string callerFilePath = "unknown", 
            [CallerLineNumber] int callerLineNumber = -1)
        {
            Console.WriteLine("Message: {0}", message);
            Console.WriteLine("Caller's Name: {0}", callerName);
            Console.WriteLine("Caller's FilePath: {0}", callerFilePath);
            Console.WriteLine("Caller's LineNumber: {0}", callerLineNumber);
        }
    }
}
/*
程序执行以后会显示以下内容。
Message: Test.
Caller Name: Main
Caller FilePath: C:UsersAdministratorsource
eposTestProProgram.cs
Caller Line number: 10
请按任意键继续. . .*/

綁定運算符

[編輯]
=: 綁定運算符簡化了數據綁定
comboBox1.Text :=: textBox1.Text; //将文本框的内容绑定到下拉框。  

帶參數的泛型構造函數

[編輯]

這個的加入給一些設計增加了強大功能,泛型早在C#2.0加入後就有著強大的應用,一般稍微設計比較好的框架,都會用到泛型,C#5.0加入帶參數泛型構造函數,則在原有基礎上對C#泛型完善了很多。

class MyClass<T>
    where T : class, new ()
{
    public MyClass()
    {
        this.MyObject = new T();
    }

    T MyObject { get; set; }
}

case支持表達式

[編輯]

以前case里只能寫一個具體的常量,而現在可以加表達式

switch(myobj){
  case string.IsNullorEmpty(myotherobj):
 ..... 
  case myotherobj.Trim().Lower: 
 ....
}

擴展屬性

[編輯]
[Associate(string)]
public static int MyExtensionProperty { get;set;}

支持null類型運算

[編輯]
int x? = null;
int y? = x + 40;

Myobject obj = null;
Myotherobj obj2 = obj.MyProperty ??? new Myotherobj();

C# 6.0的特性

[編輯]

只讀自動屬性

[編輯]

不必使用完整屬性語法定義屬性。可以在聲明屬性的位置或類型的構造函數中初始化屬性。

class Person
    {
        //新语法
        private string Name { get; } = "Fanguzai";  //不用带上 private set;

        //旧语法
        public int Age { get; private set; } ;
    }

自動屬性初始化

[編輯]
class MyClass
       {
           public string Name { get; set; } = "Fanguzai";
       }

具有表達式體的函數成員

[編輯]

可以採用與用於 lambda 表達式相同的輕量語法,聲明代碼為單個表達式的成員。具有立即僅返回表達式結果,或單個語句作為方法主體。C# 6.0支持方法和屬性get。C# 7.0擴展支持了構造函數、終結器、屬性set、索引器。

using System;

public class Person
{
   public Person(string firstName, string lastName)
   {
      fname = firstName;
      lname = lastName;
   }

   private string fname;
   private string lname;

   public override string ToString() => $"{fname} {lname}".Trim(); //返回值类型string
   public void DisplayName() => Console.WriteLine(ToString()); //返回值类型void
   public string Name => $"{fname} {lname}".Trim();//只读属性
}

使用靜態類

[編輯]

可以導入靜態類型的可訪問靜態成員,以便可以在不使用類型名限定訪問的情況下引用成員。

using System;
using static System.Console;

namespace _usingStatic
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hi,Fanguzai!");
            WriteLine("Hi,Fanguzai!");  // 使用了 using static System.Console;
        }
    }
}

nameof 運算符

[編輯]
class Program
    {

        private static void Func1(int x) { }
         private string F<T>() => nameof(T);
        private void Func2(string msg) { }
 
         static void Main(string[] args)
         {
             var program = new Program();
 
             Console.WriteLine(nameof(System.Text));
             Console.WriteLine(nameof(Func1));
             Console.WriteLine(nameof(Program));
             Console.WriteLine(nameof(program));
             Console.WriteLine(nameof(F));
 
             Console.Read();
         }
    }

內插字符串

[編輯]

以$為前綴構造字符串。 內插字符串表達式類似於包含表達式的模板字符串。內插字符串表達式通過將包含的表達式替換為表達式結果的ToString表現形式來創建字符串。

            var name = "Fanguzai";
            Console.WriteLine($"Hello, {name}");

想要在內插字符串中包含大括號「{」 或 「}」,使用兩個大括號,即 「Template:」 或 「」。

NULL 條件運算符

[編輯]

用於在執行成員訪問 ?. 或索引 ?[ 操作之前,測試是否存在 NULL 值。

         static void Main(string[] args)
         {
            string name = null;
            Console.WriteLine($"1:{name?.Length}");
            
            name = "Fanguzai";
            Console.WriteLine($"2:{name?.Length}");
            Console.WriteLine($"3: {name?[0]}");
        }

//普通的委托调用
             Func<int> func = () => 0;
             if (func!=null)
             {
                 func();
             }
 
             //简化调用
             func?.Invoke();

Catch 和 Finally 區塊中的 Await

[編輯]
async Task Test()
         {
             var wc = new WebClient();
             try
             {
                 await wc.DownloadDataTaskAsync("");
             }
             catch (Exception)
             {
                 await wc.DownloadDataTaskAsync("");  //OK
             }
             finally
             {
                 await wc.DownloadDataTaskAsync("");  //OK
             }
         }

索引初始設定

[編輯]
    var nums = new Dictionary<int, string>
            {
                [7] = "seven",
                [9] = "nine",
                [13] = "thirteen"
            };

            //这是旧的方式
            var otherNums = new Dictionary<int, string>()
            {
                {1, "one"},
                {2, "two"},
                {3, "three"}
            };

異常過濾器 (Exception filters) 

[編輯]
            try
            {
                var numbers = new Dictionary<int, string> {[7] = "seven",[9] = "nine",[13] = "thirteen" };
            }
            catch (ArgumentNullException e)
            {
                if (e.ParamName == "customer")
                {
                    Console.WriteLine("customer can not be null");
                }
            }

集合初始設定式的擴充方法

[編輯]

改進的多載解析

[編輯]

C# 7.0的特性

[編輯]

具有表達式體的函數成員

[編輯]

可以採用與用於 lambda 表達式相同的輕量語法,聲明代碼為單個表達式的成員。具有立即僅返回表達式結果,或單個語句作為方法主體。C# 6.0支持方法和屬性get。C# 7.0擴展支持了構造函數、終結器、屬性set、索引器。

   class MyClass
        {
           public string this[int i] //索引器
           {
             get => types[i];
             set => types[i] = value;
            }

            private string[] types = { "Baseball", "Basketball", "Football","Hockey", "Soccer", "Tennis","Volleyball" };

            public MyClass(string name) => Name = name; //构造函数
            private string locationName;

            ~Destroyer() => Console.WriteLine($"The {ToString()} destructor is executing.");//终结器

             public string Name => locationName; //属性get

             public string Name{
                 get => locationName;
                 set => locationName = value; //属性set
             }
        }
using System;
public class Location
{
   private string locationName;

   public Location(string name) => Name = name; //构造函数

   public string Name
   {
      get => locationName;   //get属性
      set => locationName = value;  //set属性
   }

   public override string ToString() => GetType().Name;

   ~Location() => Console.WriteLine($"The {ToString()} finalizer is executing."); //析构函数

   private string[] types = { "Baseball", "Basketball", "Football",
                              "Hockey", "Soccer", "Tennis",
                              "Volleyball" };

   public string this[int i] 
   {
      get => types[i];            //索引器
      set => types[i] = value;
   }
}

out 變數

[編輯]

能夠直接宣告一個變數在它要傳入的地方,當成一個 out 的引數[8]

static void AssignVal(out string strName)
        {
            strName = "I am from OUT";
        }
static void Main(string[] args)
        {
            AssignVal(out var szArgu); //无需提前声明该变量
        }

棄元

[編輯]

元組/對象的解構:

var tuple = (1, 2, 3, 4, 5);
(_, _, _, _, var fifth) = tuple;

使用 is/switch 的模式匹配:

var obj = CultureInfo.CurrentCulture.DateTimeFormat;
switch (obj)
{
 case IFormatProvider fmt:
   Console.WriteLine($"{fmt} object");
   break;
 case null:
   Console.Write("A null object reference");
   break;
 case object _:
   Console.WriteLine("Some object type without format information");
   break;
}

if (obj is object _)
{
 ...
}

對具有 out 參數的方法的調用:

var point = new Point(10, 10);
// 只要 x, 不关心 y
point.GetCoordinates(out int x, out _);

作用域內獨立使用場景:

void Test(Dto dto)
{
   _ = dto ?? throw new ArgumentNullException(nameof(dto));
}

元組

[編輯]
( string, string, string, string) getEmpInfo()
{
    //read EmpInfo from database or any other source and just return them
    string strFirstName = "abc";
    string strAddress = "Address";
    string strCity= "City";
    string strState= "State";
     return (strFirstName, strAddress, strCity, strState); // tuple literal
}
 
//Just call above method and it will return multiple values 
 var empInfo= getEmpInfo();
Console.WriteLine($"Emp info as  {empInfo .Item1} {empInfo .Item2} {empInfo .Item3} {empInfo .Item4}.");

//也可以给元组中各项命名:
(string strFName, string strAdd, string strC, string strSt) getEmpInfo_named()
{
    //code goes here
    //直接在元组文字中返回其名称,如下所示
    return (strFName: strFirstName, strAdd: strAddress, strCity: strC, strState: strSt);
} 
//Now when you call method get values with specific name as below 
var empInfo1= getEmpInfo();
Console.WriteLine($"Emp info as {empInfo1.strFName} {empInfo1.strAdd} {empInfo1.strC} {empInfo1.strSt}.");

.NET有Tuple類型,但是它是一個引用類型,會導致性能問題,但是C#7.0帶來了具有值類型的Tuple,該類型具有更快的性能和可變的類型。

元組的解構

大多數時候,不想訪問整個元組包,或者只需要內部值,那麼可以使用元組解構並獲取所需的值

    ( string strFName,  string strAdd,  string strC, string strSt) = getEmpInfo(); 
    Console.WriteLine($"Address: { strAdd }, Country: { strC }");

記錄類型

[編輯]

記錄類型只不過是屬性和變量的容器。藉助記錄類型可以減少工作量。

//一个带有属性,构造函数和变量的类,因此访问和声明变量需要编写更多代码:
    class studentInfo
    {
        string _strFName;
        string _strMName;
        string _strLName;
        studentInfo(string strFN, string strMN, string strLN){
            this._strFName = strFN;
            this._strMName = strMN;
            this._strLName = strLN;
        }
        public string StudentFName {get{ return this._strFName;}}
        public string StudentMName {get{ return this._strMName;}}
        public string StudentLName {get{ return this._strLName;}}
    }

//具有相同功能的记录类型:
class studentInfo(string StudentFName, string StudentMName, string StudentLName);

不可為空的引用類型

[編輯]
int objNullVal;     //non-nullable value type
int? objNotNullVal;    //nullable value type
string! objNotNullRef; //non-nullable reference type 不可为空的引用类型
string objNullRef;  //nullable reference type

本地方法

[編輯]

可以在其中使用out或ref。

private static void Main(string[] args)
{
    int local_var = 100;
    int LocalFunction(int arg1)
    {
        return local_var * arg1;
    } 
    Console.WriteLine(LocalFunction(100));
}

數值字面量中包含下劃線分隔符以提高可讀性

[編輯]
        static void Main(string[] args)
        {
            var lit1 = 478_1254_3698_44;
            var lit2 = 0Xab_bc47;
            //C# also come with binary literal for bunary values
            var binLit = 0b1_00;
        }

模式匹配

[編輯]

允許在IS語句和SWITCH語句中把模式與任何數據類型匹配。模式可以是常量模式、類型模式、變量模式。

    public  void Method1( object obj)
    {
        //following null is constant pattern
         if (obj  is null)  return;
        //datatype pattern, string is datatype that we check directly     
         if (obj  is  string st)
        { //code goes here }
        else
        return; 
    }

class Calculate();
class Add(int a, int b, int c) : Calculate;
class Substract(int a, int b) : Calculate;
class Multiply(int a, int b, int c) : Calculate;
 
Calculate objCal = new Multiply(2, 3, 4);
switch (objCal)
{
    case Add(int a, int b, int c):
        //code goes here
        break;
    case Substract(int a, int b):
        //code goes here
        break;
    case Multiply(int a, int b, int c):
        //code goes here
        break;
    default:
        //default case
        break;
}

變量作為Ref從方法返回

[編輯]

通過Ref傳遞一個變量,將它作為Ref返回,並將它們存儲為Ref

   class Program
    {
        static string emptyString;
        static ref string getFromList(string strVal, string[] Values)
        {            
            var kk = Values.GetLength(0);
            for (int i = 0; i < Values.GetLength(0); i++)
            {
                if (strVal == Values[i])
                    return ref Values[i]; //return location as ref not actual value
            }
            return ref emptyString;
        }
        static void Main(string[] args)
        {
            string[] values = { "a", "b", "c", "d" };
            ref string strSubstitute = ref getFromList("b", values);
            strSubstitute = "K"; // replaces 7 with 9 in the array
            Console.WriteLine(values[1]); // it prints "K"
        }
    }

在上面的示例中,我們通過從方法返回Ref找到並替換了字符串。

從表達式中拋出異常

[編輯]
public string getEmpInfo( string EmpName)
    {
        string[] empArr = EmpName.Split(",");
        return (empArr.Length > 0) ? empArr[0] : throw new Exception("Emp Info Not exist");
    }

C# 7.1的特性

[編輯]

async Main方法

[編輯]
static async Task Main()//注意返回Task
{
    await AsyncMethod1();
}

default常值運算式

[編輯]
Func<int, string> whereClause = default;

推斷的 tuple 項目名稱

[編輯]
int count = 3;
string colors = "colors of the flag";
var tupleCol = (count,colors); // here syntax gets trimmed

C# 7.2的特性

[編輯]

有條件的「ref」

[編輯]
ref var finalVal = ref (value1 != null ? ref val1_arr[0] : ref val2_arr[0]);

無需名稱的非尾部命名參數

[編輯]

如果參數的順序正確,則無需為參數配上名稱

//suppose I have written a function and used below named arguments
EmpDetails(EmpID: 3, firstName: "Manavya", City: "Daswel");
 
//here in C#7.2, I can write the above code line as
EmpDetails(EmpID: 3, "Manavya", City: "Daswel");
 
//if you observed that firstName: "Manavya" is replaced by only "manavya" 
// and it is the beauty of the C#7.2

數值常值中的前置下劃線分隔符

[編輯]
// 在C# 7.2中,_可以用在`0x`或`0b`之后
0x_1_2 // C# 7.2及更高版本可用
0b_1_0_1 // C# 7.2及更高版本可用

私有保護訪問修飾符

[編輯]

只有當前項目里的繼承關係才能用,出了這個程序集就沒辦法用了,即 private protected 為私有繼承(protected 且 internal)。

C# 7.3的特性

[編輯]

改進stackalloc運算符

[編輯]
int* Array1 = stackalloc int[3] {5, 6, 7};//老语法

Span<int> Array = stackalloc [] {10, 20, 30};//新语法

固定語句支持更多類型

[編輯]

固定語句是防止垃圾回收器清除可移動變量的語句,可以使用fixed關鍵字創建這些語句。從C#7.3開始,固定語句支持具有以下內容的任何類型:GetPinnableReference()方法,返回的ref T是固定的。

非託管約束

[編輯]

一個成員是一個非託管型,如果它是byte,short,sbyte,int,long,char,bool類型。可以使用非託管約束來顯示類型參數必須是具有非指針的非託管類型。

    unsafe public static byte[] convert_to_byte<T>(this T argument) where T : unmanaged
    {
        var size = sizeof(T);
        var result = new Byte[size];
        return result1;
    }

還可以使用System.Enum和System.Delegate作為基類約束。

元組支持等於和不等於運算符

[編輯]
    var exp1 = (val1: 100, val2: 20);
    var exp2 = (val1: 100, val2: 20);
    exp1 == exp2; //it will return displays as 'true'

重載的「in」方法的更改

[編輯]

為「按引用」和「按值」方法命名相同的方法時,它會拋出歧義異常,為避免此錯誤,C#7.3使用了「in」方法:

    static void calculateArea(var1 arg);
    static void calculateArea(in var1 arg);

擴展參數邊界

[編輯]

可以在構造函數初始值設定項、屬性初始值設定項中定義out參數。

    //here, we have defined the out parameter in constructor
    public class parent
    {
       public parent(int Input, out int Output)
       {
          Output = Input;
       }
    }
     
    //now let's use the above class in the following class
    public class Child : parent
    {
       public Child(int i) : base(Input, out var Output)
       {
          //"The value of 'Output' is " + Output
       }
    }

多個編譯器選項

[編輯]
  • 使用公鑰簽名程序集:添加參數作為-public簽名。
  • 從構建環境替換路徑:用較早的映射源路徑替換構建中的源路徑:-pathmap:path1=sourcePath1,path2=sourcePath2

C# 8.0的特性

[編輯]
  1. 遞歸的模式匹配
  2. 在編譯器可做類型推斷的情況下,允許進一步省略類型聲明

可釋放的引用結構

[編輯]

聲明為ref 的struct 無權實現任何接口,因為無權實現IDisposable 接口。必須使我們需要使用可釋放的ref結構來訪問void Dispose()方法。也可以配置readonly ref結構。

表達式形式的Switch關鍵字

[編輯]

switch用於模式匹配的表達式及其語法有所變化。

    public enum RandomNum
    {
        One,
        Three,
        Five,
        Seven,
        Six,
    }

    public static string getRandomNum(RandomNum iNum) =>
        iNum switch //switch关键字在变量之后使用
        {
            RandomNum.One => return "1",
            RandomNum.Three => return "3",
            RandomNum.Five => return "5",
            RandomNum.Seven => return "7",
            RandomNum.Six => return "6",
            _              => throw new Exception("invalid number value"), //默认关键字由_(下划线)符号代替
        };

空值結合賦值運算符

[編輯]

??=空值結合賦值運算符(null-coalescing):

some_Value ??= some_Value2;

以代替囉嗦的寫法:

some_Value = some_Value ?? some_Value2;
    List<int> lstNum = null;
    int? a = null;
     
    (lstNum ??= new List<int>()).Add(100);
    Console.WriteLine(string.Join(" ", lstNum)); 
     
    // the output would be : 100
     
    lstNum.Add(a ??= 0);
    Console.WriteLine(string.Join(" ", numbers));  // output: 100 0
    // the output would be : 100 0

局部靜態函數

[編輯]

將局部函數設為static函數,可證明局部函數不包含內部封閉循環中的變量。

    int Add()
    {
        int A = 5;
        return Sum(A);
     
        static int Sum(int val) => val + 3;
    }

默認接口方法

[編輯]

允許在聲明接口時為接口成員提供默認實現。

=將結構成員設為只讀

[編輯]

現在可以將struct成員設為只讀,而不是將整個struct成員設置為只讀。

    public readonly struct getSqure1 //老的实现
    {
        public int InputA { get; set; }
        public int InputB { get; set; }
        public int output => Math.Pow(A,B);
     
        public override string ToString() =>
            $"The answer is : {output} ";
    }

    public struct getSqure //新的实现
    {
        public int InputA { get; set; }
        public int InputB { get; set; }
        public readonly int output => Math.Pow(A,B);
     
        public override string ToString() =>
            $"The answer is : {output} ";
    }

插值字符串的增強

[編輯]

string以$開頭,這樣的string則稱為插值string,可包含插值表達式:

    string szName = "ABC";
    int iCount = 15;
     
    // String interpolation:
    Console.WriteLine($"Hello, {szName}! You have {iCount} apples");//输出将为“ Hello, ABC! You have 15 apples”

逐字字符串以@"開頭。在C#8中,$@""或者@$""表示可以通過@或$來開始string。在早期版本的C#中,@僅在$之後被允許。

stackalloc作為表達式

[編輯]

語法:stackalloc T[E] 其中T是非託管類型,E是類型int的表達式。stackalloc用作表達式,如果返回結果System.Span<T>。

    Span<int> num = stackalloc[] { 20, 30, 40 };
    var index = num.IndexOfAny(stackalloc[] { 20 });
    Console.WriteLine(index);  // output: 0

新型索引(Index )和區間(Range)類型

[編輯]

System.Index和System.Range用於從列表/數組中獲取項。

^(脫字符)表示索引符號。通常,從數組中獲取項目時,索引以開頭0,但是當使用這種新類型時,其索引(indices)從結尾開始並向上。

..範圍運算符獲取其操作數的開始和結束。

    var simArray = new string[]
    {                 
        "one",         // 0      ^4
        "two",         // 1      ^3
        "three",       // 2      ^2
        "four",        // 3      ^1
    };

    Console.WriteLine("fetch using simple array " + simArray[1]);
    //above code gives output as "two"
    Console.WriteLine("fetch using indices caret operator " + simArray[^1]);
    //above code gives output as "four"

            var dd1 = simArray[..];
            //above code gives output as "one" to "four"
            var dd2 = simArray[..3];
            //above code gives output as "one" to "three"
            var dd3 = simArray[2..];
            //above code gives output as "three" to "four"

在上面的示例中,數組中有四個項;索引從0開始,以3結束;但是對於索引(indices)從^ 4()到^ 1。

using 關鍵字聲明變量

[編輯]

using關鍵字聲明變量,並在使用該變量的地方結束之前釋放該變量:

    var ReadFile(string szpath)
    {
     using StreamReader StrFile = new StreamReader(szpath);
     string iCount;
     string szLine;
     while ((szLine = StrFile.ReadLine()) != null) 
      { 
      Console.WriteLine(szLine); 
      iCount++; 
      } 
     // 达到该方法的闭合括号,便会立即disposed该变量。
    }

    var ReadFile_OldStyle(string szpath)
    {
    using (StreamReader StrFile = new StreamReader(szpath))
     {
      string iCount;
      string szLine;
      while ((szLine = StrFile.ReadLine()) != null) 
      { 
      Console.WriteLine(szLine); 
      iCount++; 
      }     
     } //StrFile 对象在using 作用域结束后就被释放了
    }

消費異步流

[編輯]

異步流由一個方法執行,該方法使用IAsyncEnumerable<T>枚舉器返回異步流。此方法聲明為anync修飾符,此函數還包含yield return 語句用於返回異步流。為了消費anync流,我們需要在實際返回值之前使用await關鍵字。

    public async System.Collections.Generic.IAsyncEnumerable<int> asyncStream()
    {
        for (int iCount = 0; iCount < 10; iCount++)
        {
            await Task.Delay(10);
            yield return iCount;
        }
    }
    //call above method in mail method
    await foreach (var num in asyncStream())
    {
        Console.WriteLine(num);
    }

C# 9.0的特性

[編輯]

新的「Record」類型

[編輯]

記錄類型, 是一種引用類型, 默認是不可變的。 記錄類型的相等判斷可以通過引用或者結構進行判斷的。

  • 優點:記錄類型是輕量級的不可變類型,可以減少大量的代碼, 可以按照結構和引用進行比較;
  • 缺點:需要實例化大量的對象;
// 默认不可变的记录类型
public record Person(string Name, int Age);
// 可变记录类型
public record MutablePerson(string Name, int Age) {
 public string Name { get; set; } = Name;
 public int Age { get; set; } = Age;
}
 
var person1 = new Person("Alice", 40);
var person2 = new Person("Alice", 40);
 
Console.WriteLine(person1 == person2); // True 结构相同
Console.WriteLine(person1.Equals(person2)); // True 结构相同
Console.WriteLine(ReferenceEquals(person1, person2)); // False, 引用不同
 
// 改变默认的记录! --> 创建一个新的记录。
var person3 = person1 with { Age = 43 };
Console.WriteLine(person3 == person1); // False 结构不同
 
// 解构 (Destruct) 一个记录, 将记录的属性提取为本地变量
var (name, age) = person3;
 
var person4 = new MutablePerson("Alice", 40);
person4.Age = 43;
 
// 记录类型也可以被继承
public record Citizen(string Name, int Age, string Country) : Person(Name, Age);
var citizen = new Citizen("Alice", 40, "China");
Console.WriteLine(person1 == citizen); // False 类型不同;

「init」訪問子

[編輯]

init存取子表示該屬性所屬類型僅能在建構函式(Constructor)中或是屬性初始化式子中賦予其值,如果嘗試在其他地方設定該屬性的值,在編譯時便會遭編譯器阻止。

範例如下: 在這個範例中,建立了一個Student類型,並且屬性StudentNameStudentID只能在初始化時賦予其值。

public class Student
{
	public string StudentName { get; init; } = "Default Name";
	public string StudentID { get; init; } = "00000000";
	public Student()
    {
		
	}
	public Student(string studentName,string studentID)
	{
		StudentName = studentName;
		StudentID = studentID;
	}
}

如果在此時撰寫以下程式碼:

Student DemoStudent = new Student();
DemoStudent.StudentName = "Test Name";

編譯器便會無法編譯並且擲回錯誤。

而如果要建立學生名稱為「Test Name」,學生ID為「0001」的學生,則需要寫成:

Student DemoStudent = new Student() //物件初始化運算式
{
    StudentName = "Test Name";
    StudentID = "0001"
};

或是

Student DemoStudent = new Student("Test Name","0001"); //藉由類型的建構式初始化StudentName以及StudentID。

最上層陳述式或稱頂級語句

[編輯]

在以前的版本,開發者在撰寫最上層陳述式(如Program.cs)程式碼時,需要包含完整的namespace與class架構,因此如果要撰寫Hello World程式時,程式碼就會是:

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

但是在C#9.0之後,最上層語句(top-level statement)的程式碼不需要包含namespace以及class,可將其簡化為:

using System;

Console.WriteLine("Hello World!");
//或者简化为一行语句:
System.Console.WriteLine(Hello World!);

注意, 一個程序中, 只能有一個文件使用頂級語句, 並且頂級語句必須位於命名空間或類型定義之前!

lambda棄元參數

[編輯]
Func<int, int, int> zero = (_, _) => 0;
Func<int, int, int> func = delegate (int _, int _) { return 0; };

在 C# 9 之前,即便不使用的 Lambda 參數也需要給它命名。C# 9 支持棄元參數一方面簡化了命名,另一方面也節省了內存分配。更重要的是它使得編程的意圖更明確,讓人一看就知道這個參數是不用的,增強了代碼的可讀性和可維護性。

只能初始化的設置器

[編輯]

Init only setters,只能通過對象初始化進行賦值的屬性。

public class InitDemo {
 public string Start { get; init; }
 public string Stop { get; init; }
}
 
// initDemo.Start = "Now"; // Error
// initDemo.End = "Tomorrow"; // Error
 
var initDemo = new InitDemo {
 Start = "Now",
 Stop = "Tomorrow"
};

函數指針

[編輯]

使用 delegate* 可以聲明函數指針。

unsafe class FunctionPointer {
 static int GetLength(string s) => s.Length;
 delegate*<string, int> functionPointer = &GetLength;
}
 
public void Test() {
 Console.WriteLine(functionPointer("test")); // 4;
}

跳過本地初始化

[編輯]
[System.Runtime.CompilerServices.SkipLocalsInit]
static unsafe void DemoLocalsInit() {
 int x;
 // 注意, x 没有初始化, 输出结果不确定;
 Console.WriteLine(*&x);
}

原生整數類型

[編輯]

兩個新的整數類型 nint 和 nunit , 依賴宿主機以及編譯設定。

協變返回類型

[編輯]

協變返回類型為重寫方法的返回類型提供了靈活性。覆蓋方法可以返回從被覆蓋的基礎方法的返回類型派生的類型。

class Person { public virtual Person GetPerson() { return new Person(); } }
class Student : Person { public override  Student GetPerson() { return new Student(); } }

模塊初始化代碼

[編輯]

ModuleInitializerAttribute 為組件 (assembly) 定義初始化代碼, 當初始化/加載時執行, 可以類比類的靜態構造函數, 但是是組件級別的。

  • 必須是靜態的、無參數的、無返回值的方法;
  • 不能是范型方法,也不能包含在范型類中;
  • 不能是私有函數,必須是公開 (public) 或者內部 (internal) 的函數;

靜態 lambda 表達式

[編輯]

static 修飾符添加到 lambda 表達式或匿名方法 。這將無法捕獲局部變量或實例狀態,從而防止意外捕獲其他變量。

分部方法擴展

[編輯]

移除了分部方法的下述限制:

  • 必須具有 void 返回類型。
  • 不能具有 out 參數。
  • 不能具有任何可訪問性(隱式 private )。

初始化表達式的簡化

[編輯]

如果創建對象的類型已知時,可以在new表達式中省略該類型。

Point p = new(1, 1);
Dictionary<string, int> dict = new();
 
Point[] points = { new(1, 1), new (2, 2), new (3, 3) };
var list = new List<Point> { new(1, 1), new(2, 2), new(3, 3)};

在本地函數上添加標記

[編輯]
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
namespace CoreApp2
{  
    class Program
    {
        static void Main(string[] args)
        {
            [Conditional("DEBUG")]
            static void DoSomething([NotNull] string test)
            {
                System.Console.WriteLine("Do it!");
            }
            DoSomething("Doing!");
        }
   }
}

GetEnumerator 擴展

[編輯]

可以為任意類型添加一個 GetEnumerator 擴展方法, 返回一個 IEnumerator 或者 IAsyncEnumerator 實例, 從而在 foreach 循環中使用。

using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace CoreApp2
{
    public static class Extensions
    {
        public static IEnumerator<T> GetEnumerator<T>(this IEnumerator<T> enumerator) => enumerator;
    }

    class Program
    {
        static void Main(string[] args)
        {
            IEnumerator<string> enumerator = new Collection<string> {"A", "B", "C"}.GetEnumerator();
            foreach (var item in enumerator)
            {
                Console.WriteLine(item);
            }
        }
    }
}

模式匹配增強

[編輯]

Type patterns 類型匹配,判斷一個變量的類型

object obj = new int();
var type = obj switch {
 string => "string",
 int => "int",
 _ => "obj"
};
Console.WriteLine(type); // int

Relational patterns 關係匹配:

class Person { 
        public string name; 
        public int age; 
        public Person(string a, int b) { name = a;age = b; }
        public void Deconstruct(out string a,out int b){a = name;b = age; }
    }
class Program
    {        
        static void Main(string[] args)
        {
            var person1 = new Person("Alice", 40);
            var inRange = person1 switch
            {
                (_, < 18) => "less than 18",
                (_, > 18) => "greater than 18",
                (_, 18) => "18 years old!"
            };
            Console.WriteLine(inRange); // greater than 18
         }
     }

Conjunctive and patterns 邏輯合取匹配:

// And pattern
var person1 = new Person("Alice", 40);
var ageInRange = person1 switch {
 (_, < 18) => "less than 18",
 ("Zhang Zhimin", _) and (_, >= 18) => "Alice is greater than 18"
};
Console.WriteLine(ageInRange); // Alice is greater than 18

Disjunctive or patterns 邏輯析取匹配:

// Or pattern
var person1 = new Person("Alice", 40);
var ageInRange = person1 switch {
 (_, < 18) => "less than 18",
 (_, 18) or (_, > 18) => "18 or greater"
};
Console.WriteLine(ageInRange); // 18 or greater

Negated not patterns 邏輯非匹配

// Not pattern
var person1 = new Person("Alice", 40);
var meOrNot = person1 switch {
 not ("Alice", 40) => "Not me!",
 _ => "Me :-)"
};
Console.WriteLine(meOrNot); // Me :-)

Parenthesized patterns 帶括號的優先級匹配:

// Parenthesized patterns
var is10 = new IsNumber(true, 10);
var n10 = is10 switch {
 ((_, > 1 and < 5) and (_, > 5 and < 9)) or (_, 10) => "10",
 _ => "not 10"
};
Console.WriteLine(n10); // 10

C# 10.0的特性

[編輯]

record struct

[編輯]

解決了 record 只能給 class 而不能給 struct 用的問題:

record struct Point(int X, int Y);

sealed record ToString 方法

[編輯]

可以把 record 里的 ToString 方法標記成 sealed

結構的無參數構造函數

[編輯]

無參數的結構體構造函數(Parameterless constructors in structs)。無參構造函數使得new struct() 和 default(struct) 的語義不一樣。

        struct Person
        {
            public string Name { get; }
            public int Age { get; }
            public Person(string a, int b) { Name = a; Age = b; }
            public Person() : this("Joe", 40) {}
        }

用with創建新的匿名類型對象

[編輯]
   var x = new { A = 1, B = 2 };
   var y = x with { A = 3 };

這裡 y.A 將會是 3 。

全局的 using

[編輯]

可以給整個項目啟用 using,不需要每個文件都寫一份。

文件範圍的 namespace

[編輯]

以前寫 namespace 還得帶一層大括號。現在如果一個文件里只有一個 namespace 的話,直接在文件開頭寫: namespace MyNamespace;

常量字符串插值

[編輯]
   const string x = "hello";
   const string y = $"{x}, world!";

lambda的改進

[編輯]

lambda 可以帶 attributes

[編輯]
   f = [Foo] (x) => x; // 给 lambda 设置
   f = [return: Foo] (x) => x; // 给 lambda 返回值设置
   f = ([Foo] x) => x; // 给 lambda 参数设置

指定返回值類型

[編輯]

此前 C# 的 lambda 返回值類型靠推導,C# 10允許在參數列表之前顯式指定 lambda 返回值類型:

f = int () => 4;

支持 ref 、in 、out 等修飾

[編輯]
f = ref int (ref int x) => ref x; // 返回一个参数的引用

頭等函數

[編輯]

函數可以隱式轉換到 delegate,於是函數上升為頭等函數(first function):

   void Foo() { Console.WriteLine("hello"); }
   var x = Foo;
   x(); // hello

自然委託類型

[編輯]

lambda 可自動創建自然委託類型,於是不再需要寫出類型:

   var f = () => 1; // Func<int>
   var g = string (int x, string y) => $"{y}{x}"; // Func<int, string, string>
   var h = "test".GetHashCode; // Func<int>

CallerArgumentExpression

[編輯]

使用CallerArgumentExpression這個attribute,編譯器會自動填充調用參數的表達式字符串,例如:

   void Foo(int value, [CallerArgumentExpression("value")] string? expression = null)
   {
       Console.WriteLine(expression + " = " + value);
   }

當你調用 Foo(4 + 5) 時,會輸出 4 + 5 = 9。這對測試框架極其有用

tuple 的混合定義和使用

[編輯]
   int y = 0;
   (var x, y, var z) = (1, 2, 3);

於是 y 就變成 2 了,同時還創建了兩個變量 x 和 z,分別是 1 和 3 。

接口支持抽象靜態方法

[編輯]

.NET 6中這個特性為preview特性。

泛型 attribute

[編輯]

在方法上指定 AsyncMethodBuilder

[編輯]

在方法上用 [AsyncMethodBuilder(...)],來使用自己實現的 async method builder,代替自帶的 Task 或者 ValueTask 的異步方法構造器。有助於實現零開銷的異步方法。

line 指示器支持行列和範圍

[編輯]

以前 #line 只能用來指定一個文件中的某一行,現在可以指定行列和範圍:

   #line (startLine, startChar) - (endLine, endChar) charOffset "fileName"
    
   // 比如 #line (1, 1) - (2, 2) 3 "test.cs"

嵌套屬性模式匹配改進

[編輯]

以前在匹配嵌套屬性的時候需要這麼寫:

if (a is { X: { Y: { Z: 4 } } }) { ... }

現在只需要簡單的:

if (a is { X.Y.Z: 4 }) { ... }

改進的字符串插值

[編輯]

實現接近零開銷的字符串插值。

Source Generator v2

[編輯]

包括強類型的代碼構建器,以及增量編譯的支持等

參見

[編輯]

參考文獻

[編輯]
  1. ^ An Introduction to C# Generics. [2020-09-25]. (原始內容存檔於2019-09-24). 
  2. ^ Anonymous Methods (C#). [2008-10-24]. (原始內容存檔於2008-04-17). 
  3. ^ Covariance and Contravariance in Delegates (C#). [2008-10-24]. (原始內容存檔於2008-10-12). 
  4. ^ Tim Anderson. C# pulling ahead of Java - Lead architect paints rosy C# picture. Reg Developer. The Register. 2006-11-14 [2007-01-20]. (原始內容存檔於2007-01-21). 
  5. ^ LINQ. Microsoft MSDN. 2007 [2007-08-13]. (原始內容存檔於2007-01-16) (英語). 
  6. ^ The Mellow Musings of Dr. T : What is a collection?. [2008-10-24]. (原始內容存檔於2008-12-18). 
  7. ^ Partial Methods. [2007-10-06]. (原始內容存檔於2007-10-16). 
  8. ^ 一覽 C# 7.0 中的新功能. [2016-09-14]. (原始內容存檔於2018-10-02). 
  1. Archer, Tom. Inside C#. Microsoft Press. 2001. ISBN 0-7356-1288-9. 
  2. Bart de Smet on Spec#頁面存檔備份,存於網際網路檔案館

外部連結

[編輯]