本文參考Roslyn項目中的Issue:#259.
1. C# 7.0 新特性1: 基于Tuple的“多”傳回值方法
2. C# 7.0 新特性2: 本地方法
3. C# 7.0 新特性3: 模式比對
4. C# 7.0 新特性4: 傳回引用
簡而言之,【本地方法】就是在方法體内部定義一個方法。
其實咋眼一看,這個新特新并沒有什麼新意,因為目前大量C#的項目中,都可以使用delegate或基于delegate變形的各種方案(lambda, Fun<*>, Action, Action<*> ...)。
但是請仔細推敲一下,方法體内部delegate,是否真的那麼完美無缺……
目前的C#
我們看一下,通常方法内部設定子邏輯單元的做法。
1 public void Bar()
2 {
3 var arr= new int[] { 5,8 ,10, 20 };
4 Func<int, string> handler = i => $"目前值是:{i}。";
5 foreach (var i in arr)
6 {
7 Console.WriteLine(handler(i));
8 }
9 }
看起來有什麼問題嗎?當然,很多。
1. 首先,handler是無法進行遞歸調用的,看下面的代碼。
1 public int Bar(int n)
2 {
3 if (n < 0) throw new ArgumentException();
4 Func<int, int> handler = n => {
5 if (n == 0) return 1;
6 return n * handler(n - 1);
7 };
8 return handler(n);
9 }
這時handler(n-1) 的調用會報錯(Use of unassigned local variable 'handler'),原因是handler還未被指派。通常,這種問題我們會嘗試用一種非常難看的做法變通解決:
1 Func<int, int> handler = null;
2 handler = n =>{
3 if (n == 0) return 1;
4 return n * handler(n - 1);
5 };
咳咳咳,不多說了,心裡一萬隻羊駝狂奔而過。
2. 其次,Lambda表達式的使用,非常有局限,它不允許在參數中添加行為修飾 out, ref, params, 以及可選參數,均不能在Lambda表達式的參數表中出現。參數無法使用泛型。
3. 配置設定了一個委托對象來指向函數,為了能夠在Lambda表達式中通路本地變量,會為其配置設定一個新的對象,間接的增加了GC的壓力。
說到這裡,可能有的童鞋會自然的想到更原始的解決方案,在外部聲明一個私有函數,就不會存在以上一系列的問題。
1 class Foo
2 {
3 public void Bar(int[] arr)
4 {
5 foreach(var i in arr)
6 {
7 Console.WriteLine(Handler(i));
8 }
9 }
10
11 private string Handler(int i) => $"目前值是:{i}";
12 }
這的确是一種簡單粗暴的解決方案,但是依然存在一些小問題,一個僅在Bar方法中有引用的方法,邏輯上也沒必要暴露給this的其他成員。這種做法其實是結構上的一種不合理。
本地方法(Local Function)
在C#7.0中,允許代碼直接在一個方法體(方法,構造,屬性的Getter和Setter)裡聲明并調用子方法。
廢話不說,上代碼:
1 public void Bar(int[] arr)
2 {
3 var length = arr.Length;
4 string Length() {
5 return $"length is {length}";
6 }
7 //或:
8 //string Length() => $"length is {length}";
9 Length();
10 }
上面例子中的Length(),在編譯後會轉化成目前類的私有成員方法(IL: this.<Bar>g__Length(): string()),但由于在C#語言層面做出了限制,隻被允許在Bar方法中通路。
由于對this而言,是以類似匿名方法的形态存在,是以,在目前類中仍然可以定義同名且同樣聲明的成員方法,從所在的方法體中調用,會執行本地方法。
Okay,話說回來,由于它的本質是成員方法,是以它可以避免 [委托 / Lambda表達式] 的種種限制,可以異步,可用泛型,可用out, ref, param, 可以yield, 特性參數, 等等。。
來個例子:
1 class Foo
2 {
3 public IEnumerable<T> Bar<T>(params T[] items)
4 {
5 if (items == null) throw new ArgumentException(nameof(items));
6
7 IEnumerable<T2> Enumerate<T2> ([CallerMemberName] T2[] array) //使用泛型及特性參數
8 {
9 //本地方法邏輯
10 foreach (var item in array)
11 {
12 yield return item; //使用疊代器
13 }
14 }
15
16 return Enumerate<T>(items); //調用本地方法
17 //return this.Enumerate<T>(items); //調用成員方法
18 }
19
20 IEnumerable<T2> Enumerate<T2>([CallerMemberName] T2[] array)
21 {
22 //成員方法邏輯
23 }
24 }
總結
這個feature可以看出,C#在朝着函數式語言謹慎的調整。回想起很多人在首次接觸代碼的懵懂期,經常犯一種比較低級的錯誤,傻傻的嘗試在Main方法中寫函數聲明,現在看來,那才是人最直接的思維邏輯。
本文連結:http://www.cnblogs.com/ylvict/p/5579350.html (轉載請注明)
目前(2016年6月)C#7.0還未正式釋出,大家如果想體驗部分特性,可以去下載下傳VS15預覽版,最終釋出的文法可能和本文中提及的有所不同,最新動态請大家關注Roslyn項目。