1. 程式人生 > >C#語法糖(Csharp Syntactic sugar)

C#語法糖(Csharp Syntactic sugar)

目錄

總結

一、C#語法糖大彙總

首先需要宣告的是“語法糖”這個詞絕非貶義詞,它可以給我帶來方便,是一種便捷的寫法,編譯器會幫我們做轉換;而且可以提高開發編碼的效率,在效能上也不會帶來損失。這讓java開發人員羨慕不已,呵呵。

1.  經過簡化的Property

早些時候我們這樣宣告Property

private string _myName;
public string MyName
{
    get { return _myName; }
    set { _myName = value; }
}

千篇一律的這樣宣告,沒有多大意義,於是C#的設計人員將這個千篇一律的工作交給了編譯器幫我們做了,我們現在可以這樣宣告

public string MyName { get; set; }

當然他不會犧牲靈活性,我們可以單獨給get或者set設定訪問限制符,例如
public string MyName { get; protected internal set; }

2.  經過兩次變異的委託寫法

在.net 1.1時我們不得不宣告方法後才在委託中使用,在.net 2.0之後我們可以使用匿名委託,他不單可以簡化寫法,還可以在匿名委託中訪問範圍內的變數;再後來拉姆達表示式來了,寫法就更簡便了。

class MyClass
{
    public delegate void DoSomething(int a);
    //定義方法委託
    private void DoIt(int a) {
        Console.WriteLine(a);
    }
    private void HowtoDo(DoSomething doMethod,int a) {
        doMethod(a);
    }
    public static void Main(string[] args) {
        MyClass mc = new MyClass();
        //呼叫定義的方法委託
        mc.HowtoDo(new DoSomething(mc.DoIt), 10);
        int x = 10;
        //使用匿名委託
        mc.HowtoDo(delegate(int a){
            Console.WriteLine(a + x);
        },10);
        //使用lamda表示式
        mc.HowtoDo(a=>Console.WriteLine(a+x),10);
        Console.ReadLine();
    }
}

3.  集合類的宣告

之前我們宣告一個List並給list賦初始值,必須得這麼寫:

List<string> list = new List<string>();
list.Add("a一");
list.Add("b二");
list.Add("c三");

現在不需要了,直接寫就可以了

List<string> list = new List<string> {"def","OK"};

4.  集合類各個項的操作

我們為了逐個處理集合中的項,需要這麼寫:

foreach (string item in list)
{
     Console.WriteLine(item);
}

現在不需要了,這樣就可以了

list.ForEach(a => Console.WriteLine(a));

程式碼是不是清爽了很多。

5.  using == try finally

為了在使用完畢時釋放資源,我們經常要用using,using實質上就是try fiannaly的一個語法糖而已。例如

StreamWriter sw = null;
try
{
    sw = new StreamWriter("d:\abc.txt");
    sw.WriteLine("test");
}
finally {
    if(sw!= null) sw.Dispose();
}

//上面的程式碼可以簡化為:
using (var sw = new StreamWriter("d:\abc.txt")) {
    sw.WriteLine("test");
}

6.  可愛的var

var的意義時不必寫宣告的型別,編譯器會根據後面對var的賦值判斷它的型別,var的型別一旦確認就不能再改變,它只能作為區域性變數使用,不能用做欄位也不能用做引數宣告。

var writer = new StreamWriter(path);

for(var i=0;i<100;i++){}

7.  問號的演變

老掉牙的一個問號+冒號

var b = 3;
var a = b > 9?b.ToString():”0”+b;

新寶寶兩個問號 ??,它表示左邊的變數如果為null則值為右邊的變數,否則就是左邊的變數值

string a = null;
var b = a??””;

8.  型別例項化的語法糖

public class Abc
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Url { get; set; }
}

我們沒有為上面的類宣告建構函式,但是我們可以像下面的形式來例項化它

public static void Main(string[] args) {
        var abc = new Abc{
            ID=1,
            Name="yukaizhao",
            Url="http://yukaizhao.cnblogs.com/"
        };
    }

9.  傳說中的擴充套件方法

在c#3.5時引入了擴充套件方法,我們可以在不修改類原始碼的情況下給類增加例項方法,這個很有意義。它的實質也是一種語法糖的實現
例如我們給String類擴充套件一個IsNumber的方法:

public static class StringExt {
    static private Regex regexNumber = new Regex("\\d+");
    static public bool IsNumber(this string input)
    {
        if (string.IsNullOrEmpty(input))
        {
            return false;
        }
        return regexNumber.IsMatch(input);
    }
}

我們可以在String例項上呼叫這個方法了

var abc = “123”;
var isNumber = abs.IsNumber();

10.使用匿名類

var a = new {
    ID = 1,Name=”yukaizhao”,BlogUrl=”http://www.cnblogs.com/yukaizhao/”
};

匿名類在linq to sql或者entity framework中返回查詢資料時很好用。

C#6

靜態類匯入using static System.Console;

11. NULL條件運算子

//使用程式碼
Customer customer = new Customer();
string name = customer?.Name;

//編譯程式碼
Customer customer = new Customer();
if (customer != null)
{
    string name = customer.Name;
}

也可以和??組合起來使用

if (customer?.Face()??false)

還可以兩個一起組合來使用

 int? contactNameLen = contact?.Name?.Length; 

這個語法糖的目的是在物件使用前檢查是否為null。如果物件為空,則賦值給變數為空值,所以例子中需要一個可以為空的int型別、即int?。如果物件不為空,則呼叫物件的成員取值,並賦值給變數。 

12. 字串格式化

String.Format有些不方便的地方是:必須輸入"String.Format",使用{0}佔位符、必須順序來格式化、這點容易出錯。

var contactInfo = string.Format("Id:{0} Name:{1} EmailAddr:{2} PhoneNum:{3}", contact.Id, contact.Name, contact.EmailAddress, contact.PhoneNum);

新的語法

var contactInfo2 = $"Id:{contact.Id} Name:{contact.Name} EmailAddr:{contact.EmailAddress} PhoneNum:{contact.PhoneNum}";

使用起來順手多了,贊!
新格式化方式還支援任何表示式的直接賦值:

var contactInfo = $"Id:{contact.Id} Name:{(contact.Name.Length == 0 ? "Frank" : contact.Name)} EmailAddr:{contact.EmailAddress} PhoneNum:{contact.PhoneNum}";

二、C#之6.0語法糖剖析

2.1 自動屬性預設初始化

使用方法:

public string Name { get; set; } = "hello world";

為了便於理解使用2.0語法展示,編譯器生成程式碼如下:

 public class Customer 
{
 [CompilerGenerated] 
private string kBackingField = "hello world"; 
public Customer() 
{ 
this.kBackingField = "hello world"; 
}

public string Name
{
    [CompilerGenerated]
    get
    {
        return this.<Name>k__BackingField;
    }
    [CompilerGenerated]
    set
    {
        this.<Name>k__BackingField = value;
    }
}
} 

 從生成程式碼中可以看出編譯器是在例項建構函式時,初始化屬性資訊的。

2.2 自動只讀屬性預設初始化

使用方法:

public string Name1 { get; } = "hello world";

編譯器生成程式碼如下:

[CompilerGenerated] 
private readonly string kBackingField; 
public Customer() 
{
    this.kBackingField = "hello world";
} 
public string Name1 
{
    [CompilerGenerated] 
    get { return this.k__BackingField; }
}

由於初始化預設值實在建構函式中賦值的,所以跟屬性只讀沒關係。

2.3 表示式為主體的函式

使用方法:

Body Get(int x, int y) => new Body(1 + x, 2 + y);

編譯器生成如下:

private Program.Body Get(int x, int y)
{
    return new Program.Body(1 + x, 2 + y);
}

簡化了單行方法的編寫,省去寫大括號的功夫。

同時支援沒有返回值的寫法: 

void OutPut(int x, int y) => Console.WriteLine("hello world");

也支援非同步函式的編寫:

async void OutPut(int x, int y) => await new Task(() => Console.WriteLine("hello wolrd"));

2.4 表示式為主體的屬性(賦值)

使用方法:

public string Name2 => "hello world";
編譯器生成程式碼如下:

public string Name2 

get { return "mushroomsir"; }
 }
編譯器只生成了個只讀屬性。

2.5 靜態類匯入

這個特性可以一次性匯入某型別的所有靜態成員,使靜態成員在後面的程式碼中沒有型別限制直接使用,像使用本型別下面的靜態方法一樣。

using static System.Console;
 class Program 

static void Main(string[] args) 
{
 WriteLine("hello wolrd"); 
}
}
編譯器生成程式碼如下:

private static void Main(string[] args)
 {
 Console.WriteLine("hello wolrd"); 
}
省去了型別名稱的重複編寫。

2.6 Null條件運算子

使用方法:

Customer customer = new Customer();
 string name3 = customer?.Name;
等同於:

Customer customer = new Customer();
if (customer1 != null)
{
    string name = customer1.Name;
}
可以和??組合起來使用:

if (customer?.Face2()??false)
還可以2個一起用:

int? Length = customer?.Name?.Length;
也可以方法呼叫:

customer?.Face();
這個語法糖的目的是在物件使用前檢查是否為null。如果物件為空,則賦值給變數為空值,所以例子中需要一個可以為空的int型別、即int?。

如果物件不為空,則呼叫物件的成員取值,並賦值給變數。

2.7 字串格式化

String.Format有些不方便的地方是:必須輸入"String.Format",使用{0}佔位符、必須順序來格式化、這點容易出錯。

var s = String.Format("{0} is {1} year {{s}} old", p.Name, p.Age);
新的語法糖使用起來相對更輕鬆些:

var s = $"{p.Name} is {p.Age} year{{s}} old";
編譯器生成如下,和之前沒有區別:

var s = String.Format("{0} is {1} year{{s}} old", p.Name, p.Age);
有趣的是,新格式化方式還支援任何表示式的直接賦值:

var s = $"{p.Name} is {p.Age} year{(p.Age == 1 ? "" : "s")} old";

2.8 索引初始化

List雖然這樣寫可以編譯通過,但是會拋異常的,使用方法:

var numbers = new List<string> { [7] = "seven", [9] = "nine", [13] = "thirteen" };
編譯器生成程式碼如下:

List list = new List(); 
list[7] = "seven";
 list[9] = "nine"; 
list[13] = "thirteen";
Dictionary可以執行,因為二者內部索引機制不一樣:

 var numbers = new Dictionary<int, string> {[7] = "seven",[9] = "nine",[13] = "thirteen" };
編譯器生成程式碼:

 Dictionary<int, string> dictionary2 = new Dictionary<int, string>();
    dictionary2[7] = "seven";
    dictionary2[9] = "nine";
    dictionary2[13] = "thirteen";
    Dictionary<int, string> dictionary = dictionary2;

2.9 異常過濾器when

使用方法:

 try 

throw new ArgumentException("string error");
 }
 catch (ArgumentException e) when (myfilter(e))
 { 
Console.WriteLine(e.Message);
 }

static bool myfilter(ArgumentException e)
 { 
return false;
 }
複製程式碼
When語法作用是:在進入到catch之前、驗證when括號裡myfilter方法返回的bool,如果返回true繼續執行,false不走catch直接丟擲異常。

使用這個filter可以更好的判斷一個錯誤是繼續處理還是重新丟擲去。按照以前的做法,在catch塊內如需再次丟擲去,需要重新throw出去,這時的錯誤源是捕捉後在拋的,而不是原先的,有了when語法就可以直接定位到錯誤源。 

2.10 catch和finally程式碼塊內的Await

Await非同步處理是在c#5.0提出的,但不能在catch和finally程式碼塊內使用,這次在C#6.0更新上支援了。

使用方法:

複製程式碼
    async void Solve()
    {
        try
        {
            await HttpMethodAsync();
        }
        catch (ArgumentException e)
        {
            await HttpMethodAsync();
        }
        finally
        {
            await HttpMethodAsync();
        }
    }
複製程式碼
編譯器把catch和finally的await生成到狀態機裡面的MoveNext()裡面。原來裡面只有 TaskAwaiter,現在多了2個。狀態機裡面的程式碼和原先的一樣,只是更復雜了下,有興趣的童鞋可以先看下Async、Await剖析再去深究。

2.11 nameof表示式

使用方法:

string name = "";
Console.WriteLine(nameof(name));
控制檯輸出 "name"。

有時候會需要程式中一些成員的字串名稱,比如丟擲ArgumentNullException異常的時候,想知道ArgumentNullException型別的字串名稱,這時候就可以用nameof獲取字元

串“ArgumentNullException”。現在做法都是手動複製一下,但重構改名的時候容易忘記變更字串,使用nameof就可以避免了。

當如下使用的時候,編譯器會只取最後的ZipCode。

nameof(person.Address.ZipCode)
編譯器生成如下程式碼:

Console.WriteLine("name");

2.12 擴充套件方法

    using static System.Linq.Enumerable; //引入型別,而不是名稱空間
    class Program
    {
        static void Main()
        {
            var range = Range(5, 17);                // Ok: 不是擴充套件方法
            var odd = Where(range, i => i % 2 == 1); // Error, 不在全域性作用域裡
            var even = range.Where(i => i % 2 == 0); // Ok
        }
    }
首先Enumerable是個靜態類,裡面是各種擴充套件方法,比如range。static的作用是把型別的靜態成員一次性匯入,rang雖然是靜態方法,但不能匯入,比如where。

因為擴充套件方法雖然是一個靜態方法,但是語法規定它作為一個例項方法使用(打點),所以不能在全域性作用域裡當靜態方法用,因此var odd = Where(range, i => i % 2 == 1)是錯誤的。

但是static卻能把型別的擴充套件方法作為擴充套件方法本身角色的功能匯入進去,所以var even = range.Where(i => i % 2 == 0)是ok的。

這裡可能稍微有點繞,lz儘量寫清楚,static新用法有2個功能:

把靜態成員匯入,但擴充套件方法比較特殊、排除在外。這時static是c# 6.0的新功能。
等同於把擴充套件方法的名稱空間匯入,所以在集合上可以打點呼叫擴充套件方法。這是之前就有的功能,而不是把擴充套件方法轉成單純的靜態方法匯入使用。

總結

看到園子裡有介紹的文章,一時來興趣了,下班後安裝個社群版就研究分享下。 雖然微軟一直出新東西,但都是由下至上迭代的,所以學習起來是非常快的。