C#语法糖学习总结
C#語法糖(Csharp Syntactic sugar)大匯總
http://www.cnblogs.com/yukaizhao/archive/2010/05/25/csharp-Syntactic-sugar.html首先需要聲明的是“語法糖”這個詞絕非貶義詞,它可以給我帶來方便,是一種便捷的寫法
,編譯器會幫我們做轉換;而且可以提高開發編碼的效率,在性能上也不會帶來損失。這讓
java開發人員羨慕不已,呵呵。
1. ?經過簡化的Property
早些時候我們這樣聲明Propertyprivate string _myName;
?
public string MyName
{
? ? get { return _myName; }
? ? set { _myName = value; }
}
千篇一律的這樣聲明,沒有多大意義,于是C#的設計人員將這個千篇一律的工作交給了編譯
器幫我們做了,我們現在可以這樣聲明
1
public string MyName { get; set; }
當然他不會犧牲靈活性,我們可以單獨給get或者set設定訪問限制符,例如
1
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);
}
現在不需要了,這樣就可以了
1
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的類型一
旦確認就不能再改變,它只能作為局部變量使用,不能用做字段也不能用做參數聲明。
例如:
1
var writer = new StreamWriter(path);
1
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.0中那些語法糖都干了些什么(中篇)
http://www.cnblogs.com/huangxincheng/p/4179204.html?
接著上篇繼續扯,其實語法糖也不是什么壞事,第一個就是吃不吃隨你,第二個就是最
好要知道這些糖在底層都做了些什么,不過有一點
叫眼見為實,這樣才能安心的使用,一口氣上五樓,不費勁。
一:字符串嵌入值
我想String.Format方法就是化成灰大家都認識,比如下面代碼:
? ? ?class Bird
? ? ?{
? ? ? ? ?private string Name = "swallow";
?
? ? ? ? ?public void Fly()
? ? ? ? ?{
? ? ? ? ? ? ?var result = string.Format("hello {0}", Name);
? ? ? ? ?}
? ? ?}
這個Format有一個不好的地方就是,如果占位符太多,就特別容易搞錯,如果你少了一
個參數,代碼就會報錯。
接下來跟趟順風車,去看看string.Format底層代碼,還是蠻驚訝的發現,其實底層不過調用
的就是StirngBuilder.AppendFormat方法。
因為容易報錯,所以為了保險起見就用字符串拼接的方式來實現,但是我也知道字符串拼接
是耗時的一種操作,寫個StringBuilder又嫌麻煩,
還好C#6.0中提供了一種新鮮玩法,先看代碼:
? ? ?class Bird
? ? ?{
? ? ? ? ?private string Name = "swallow";
?
? ? ? ? ?public void Fly()
? ? ? ? ?{
? ? ? ? ? ? ?//var result = string.Format("hello {0}{1}", Name);
?
? ? ? ? ? ? ?var result = "\{"hello"}:\{Name}";
?
? ? ? ? ? ? ?Console.WriteLine(result);
? ? ? ? ?}
? ? ?}
?
然后就迫不及待的去看看底層怎么玩的,其實在下面的IL圖中可以看到,在底層最終還是調
用了String.Format方法來實現的。
二:using靜態類
這種寫法看起來有點不倫不類的,乍一看也沒有什么用處,不過可以告訴我們一個原理
,就是不管你上層怎么變,編譯器還是一樣使用
全命名,這就叫萬變不離其宗吧。
三:空值判斷
先還是來看看這種玩法的真容。
? ? ?class Bird
? ? ?{
? ? ? ? ?public void Fly(string name)
? ? ? ? ?{
? ? ? ? ? ? ?var result = name?.Length;
? ? ? ? ?}
? ? ?}
是不是看著有點眼暈?那就對了,編譯器就是這樣靜靜的端著碗看著我們寫這些裝逼的代碼
,不過再怎么裝逼,也逃不過ILdasm的眼睛。
其實仔細看IL代碼之后,覺得一切還是那么的熟悉,重點就是這個brtrue.s。它的狀態也決定
了兩條執行流,不過在IL上面也看到了V_1這個編譯
器給我們單獨定義的一個變量,代碼還原如下:
?1 ? ? class Bird
?2 ? ? {
?3 ? ? ? ? public void Fly(string name)
?4 ? ? ? ? {
?5 ? ? ? ? ? ? int? r;
?6?
?7 ? ? ? ? ? ? if (name == null)
?8 ? ? ? ? ? ? {
?9 ? ? ? ? ? ? ? ? int? V_1 = new Nullable<int>();
10?
11 ? ? ? ? ? ? ? ? r = V_1;
12 ? ? ? ? ? ? }
13 ? ? ? ? ? ? else
14 ? ? ? ? ? ? {
15 ? ? ? ? ? ? ? ? r = new Nullable<int>(name.Length);
16 ? ? ? ? ? ? }
17 ? ? ? ? }
18 ? ? }
?
四:nameof表達式
當我知道這個關鍵字的用途時,我的第一反應就是公司框架里面的LogManager類,當
我們new LogManager的時候,會同時把當前的類名
傳遞下去,然后做些后期處理,但是在以前我們只能這么做,要么用反射,要么寫死。
?1 namespace ConsoleApplication3
?2 {
?3 ? ? class Program
?4 ? ? {
?5 ? ? ? ? static void Main(string[] args)
?6 ? ? ? ? {
?7 ? ? ? ? ? ? //第一種:使用反射
?8 ? ? ? ? ? ? var ilog = new LoggerManager(typeof(Program));
?9?
10 ? ? ? ? ? ? //第二種:寫死
11 ? ? ? ? ? ? ilog = new LoggerManager("Program");
12?
13 ? ? ? ? ? ? Console.WriteLine("world");
14 ? ? ? ? }
15 ? ? }
16?
17 ? ? class LoggerManager
18 ? ? {
19 ? ? ? ? /// <summary>
20 ? ? ? ? /// 構造函數記錄下類名
21 ? ? ? ? /// </summary>
22 ? ? ? ? /// <param name="type"></param>
23 ? ? ? ? public LoggerManager(Type type)
24 ? ? ? ? {
25 ? ? ? ? ? ? //todo
26 ? ? ? ? ? ? Console.WriteLine(type.Name);
27 ? ? ? ? }
28 ? ? ? ? public LoggerManager(string className)
29 ? ? ? ? {
30 ? ? ? ? ? ? //todo
31 ? ? ? ? ? ? Console.WriteLine(className);
32 ? ? ? ? }
33 ? ? }
34 }
復制代碼
我想大家也能看到,第一種使用了反射,這是需要讀取元數據的,性能你懂的,第二個雖然是
字符串,你也看到了,是寫死的方式,這個時候就
急需一個加強版,就像下面這樣。
看到IL后,反正我是雞動了。。。nameof具有上面兩者的優點,既靈活,性能又高。。。。
不錯不錯,贊一下。
========
探索C#之6.0語法糖剖析
http://www.cnblogs.com/mushroom/p/4666113.html閱讀目錄:
自動屬性默認初始化
自動只讀屬性默認初始化
表達式為主體的函數
表達式為主體的屬性(賦值)
靜態類導入
Null條件運算符
字符串格式化
索引初始化
異常過濾器when
catch和finally代碼塊內的Await
nameof表達式
擴展方法
總結
自動屬性默認初始化
使用方法:
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;
? ? }
}
}?
復制代碼
?從生成代碼中可以看出編譯器是在實例構造函數時,初始化屬性信息的。
自動只讀屬性默認初始化
使用方法:
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; }
?}
復制代碼
由于初始化默認值實在構造函數中賦值的,所以跟屬性只讀沒關系。
表達式為主體的函數
使用方法:
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"));
表達式為主體的屬性(賦值)
使用方法:
public string Name2 => "hello world";
編譯器生成代碼如下:
public string Name2?
{?
get { return "mushroomsir"; }
?}
編譯器只生成了個只讀屬性。
靜態類導入
這個特性可以一次性導入某類型的所有靜態成員,使靜態成員在后面的代碼中沒有類型限制
直接使用,像使用本類型下面的靜態方法一樣。
復制代碼
using static System.Console;
?class Program?
{?
static void Main(string[] args)?
{
?WriteLine("hello wolrd");?
}
}
復制代碼
編譯器生成代碼如下:
private static void Main(string[] args)
?{
?Console.WriteLine("hello wolrd");?
}
省去了類型名稱的重復編寫。
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?。
如果對象不為空,則調用對象的成員取值,并賦值給變量。
字符串格式化
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";
索引初始化
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;
異常過濾器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語法就可以直接定位到錯誤源。?
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剖析再去深究。
nameof表達式
使用方法:
string name = "";
Console.WriteLine(nameof(name));
控制臺輸出 "name"。
有時候會需要程序中一些成員的字符串名稱,比如拋出ArgumentNullException異常的時候
,想知道ArgumentNullException類型的字符串名稱,這時候就可以用nameof獲取字符
串“ArgumentNullException”?,F在做法都是手動復制一下,但重構改名的時候容易忘記
變更字符串,使用nameof就可以避免了。
當如下使用的時候,編譯器會只取最后的ZipCode。
nameof(person.Address.ZipCode)
編譯器生成如下代碼:
Console.WriteLine("name");
擴展方法
復制代碼
? ? 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的新功能。
等同于把擴展方法的命名空間導入,所以在集合上可以打點調用擴展方法。這是之前就有的
功能,而不是把擴展方法轉成單純的靜態方法導入使用。
總結
看到園子里有介紹的文章,一時來興趣了,下班后安裝個社區版就研究分享下。 雖然微軟一
直出新東西,但都是由下至上迭代的,所以學習起來是非??斓摹?
========
總結
- 上一篇: VS 工具-选项对话框 调试选项相关学习
- 下一篇: C# 大型对象堆学习总结