1. 程式人生 > >C# 7.0 新特性(2): 本地方法

C# 7.0 新特性(2): 本地方法

本文參考Roslyn專案中的Issue:#259.

簡而言之,【本地方法】就是在方法體內部定義一個方法
其實咋眼一看,這個新特新並沒有什麼新意,因為目前大量C#的專案中,都可以使用delegate或基於delegate變形的各種方案(lambda, Fun, Action, Action …)。
但是請仔細推敲一下,方法體內部delegate,是否真的那麼完美無缺……

目前的C#

我們看一下,通常方法內部設定子邏輯單元的做法。

C#
123456789 publicvoidBar(){vararr=newint[]{5,8,10,20};Func<int,string>handler=i=>$"當前值是:{i}。";foreach(variinarr){Console.WriteLine(handler(i));}}

看起來有什麼問題嗎?當然,很多。

1. 首先,handler是無法進行遞迴呼叫的,看下面的程式碼。

C#
123456789 publicintBar(intn){if(n<0)thrownewArgumentException();Func<int,int>handler=n=>{if(n==0)return1;returnn*handler(n-1);};returnhandler(n);}

這時handler(n-1) 的呼叫會報錯(Use of unassigned local variable ‘handler’),原因是handler還未被賦值。通常,這種問題我們會嘗試用一種非常難看的做法變通解決:

C#
12345 Funcint,int>handler=null;handler=n=>{if(n==0)return1;returnn*handler(n-1);};

咳咳咳,不多說了,心裡一萬隻羊駝狂奔而過。

2. 其次,Lambda表示式的使用,非常有侷限,它不允許在引數中新增行為修飾 out, ref, params, 以及可選引數,均不能在Lambda表示式的引數表中出現。引數無法使用泛型。

3. 分配了一個委託物件來指向函式,為了能夠在Lambda表示式中訪問本地變數,會為其分配一個新的物件,間接的增加了GC的壓力。

說到這裡,可能有的童鞋會自然的想到更原始的解決方案,在外部宣告一個私有函式,就不會存在以上一系列的問題。

C#
123456789101112 classFoo{publicvoidBar(int[]arr){foreach(variinarr){Console.WriteLine(Handler(i));}}privatestringHandler(inti)=>$"當前值是:{i}";}

這的確是一種簡單粗暴的解決方案,但是依然存在一些小問題,一個僅在Bar方法中有引用的方法,邏輯上也沒必要暴露給this的其他成員。這種做法其實是結構上的一種不合理。

本地方法(Local Function)

在C#7.0中,允許程式碼直接在一個方法體(方法,構造,屬性的Getter和Setter)裡宣告並呼叫子方法。

廢話不說,上程式碼:

C#
12345678910 publicvoidBar(int[]arr){varlength=arr.Length;stringLength(){return$"length is {length}";}//或://string Length() => $"length is {length}";Length();}

上面例子中的Length(),在編譯後會轉化成當前類的私有成員方法(IL: this.g__Length(): string()),但由於在C#語言層面做出了限制,只被允許在Bar方法中訪問。

由於對this而言,是以類似匿名方法的形態存在,所以,在當前類中仍然可以定義同名且同樣宣告的成員方法,從所在的方法體中呼叫,會執行本地方法。

Okay,話說回來,由於它的本質成員方法,所以它可以避免 [委託 / Lambda表示式] 的種種限制,可以非同步,可用泛型,可用out, ref, param, 可以yield, 特性引數, 等等。。

來個例子:

C#
123456789101112131415161718192021222324 classFoo{publicIEnumerable<T>Bar<T>(paramsT[]items){if(items==null)thrownewArgumentException(nameof(items));IEnumerable<T2>Enumerate<T2>([CallerMemberName]T2[]array)//使用泛型及特性引數{//本地方法邏輯foreach(varitem inarray){yield returnitem;//使用迭代器}}returnEnumerate<T>(items);//呼叫本地方法//return this.Enumerate<T>(items);  //呼叫成員方法}IEnumerable<T2>Enumerate<T2>([CallerMemberName]T2[]array){//成員方法邏輯}}

總結

這個feature可以看出,C#在朝著函式式語言謹慎的調整。回想起很多人在首次接觸程式碼的懵懂期,經常犯一種比較低階的錯誤,傻傻的嘗試在Main方法中寫函式宣告,現在看來,那才是最直接的思維邏輯。

目前(2016年6月)C#7.0還未正式釋出,大家如果想體驗部分特性,可以去下載VS15預覽版,最終釋出的語法可能和本文中提及的有所不同,最新動態請大家關注Roslyn專案。