C#中函數的創立和閉包的懂得。本站提示廣大學習愛好者:(C#中函數的創立和閉包的懂得)文章只能為提供參考,不一定能成為您想要的結果。以下是C#中函數的創立和閉包的懂得正文
靜態創立函數
年夜多半同窗,都或多或少的應用過。回想下c#中靜態創立函數的退化:
C# 1.0中:
public delegate string DynamicFunction(string name);
public static DynamicFunction GetDynamicFunction()
{
return GetName;
}
static string GetName(string name)
{
return name;
}
var result = GetDynamicFunction()("mushroom");
3.0寫慣了是否是看起來很繁瑣、落伍。 剛學拜托時,都把拜托懂得成函數指針,也來看下用函數指針完成的:
char GetName(char p);
typedef char (*DynamicFunction)(char p);
DynamicFunction GetDynamicFunction()
{
return GetName;
}
char GetName(char p)
{
return p;
};
char result = GetDynamicFunction()('m');
比較起來和c# 1.0簡直如出一轍了(援用/指針差異),究竟是統一家族的。
C# 2.0中,增長匿名函數:
public delegate string DynamicFunction(string name);
DynamicFunction result2 = delegate(string name)
{
return name;
};
C# 3.0中,增長Lambda表達式,華美的回身:
public static Func<string, string> GetDynamicFunction()
{
return name => name;
}
var result = GetDynamicFunction()("mushroom");
匿名函數缺乏的地方
固然增長Lambda表達式,曾經極年夜簡化了我們的任務量。但確切有些缺乏的地方:
var result = name => name;
這些寫編譯時是報錯的。由於c#自己強類型說話的,供給var語法糖只是為了省去聲明白定類型的任務量。 編譯器在編譯時必需可以或許完整揣摸出各參數的類型才行。代碼中的name參數類型,明顯在編譯時沒法揣摸出來的。
var result = (string name) => name;
Func<string, string> result2 = (string name) => name;
Expression<Func<string, string>> result3 = (string name) => name;
下面直接聲明name類型呢,很遺憾如許也是報錯的。代碼中曾經給出謎底了,編譯器揣摸不出左邊表達式是屬於Func<string, string>類型照樣Expression<Func<string, string>>類型。
dynamic result = name => name;
dynamic result1 = (Func<string,string>)(name => name);
用dynamic呢,異樣編譯器也分不出左邊是個拜托,我們顯示轉換下便可以了。
Func<string, string> function = name => name;
DynamicFunction df = function;
這裡界說個func拜托,固然參數和前往值類型都和DynamicFunction拜托一樣,但編譯時照樣會報錯:不克不及隱式轉換Func<string, string>到DynamicFunction,2個類型是不兼容的。
懂得c#中的閉包
議論到靜態創立函數,都要牽扯到閉包。閉包這個概念材料許多了,實際部門這裡就不反復了。 來看看c#代碼中閉包:
Func<Func<int>> A = () =>
{
var age = 18;
return () => //B函數
{
return age;
};
};
var result = A()();
下面就是閉包,可懂得為就是: 跨感化域拜訪函數內變量,也有說帶著數據的行動。
C#變量感化域一共有三種,即:類變量,實例變量,函數內變量。子感化域拜訪父感化域的變量(即函數內拜訪實例/類變量)在我們看來天經地義的,也相符我們一向的編程習氣。
例子中匿名函數B是可以拜訪下層函數A的變量age。關於編譯器而言,A函數是B函數的父感化域,所以B函數拜訪父感化域的age變量是相符標准的。
int age = 16;
void Display()
{
Console.WriteLine(age);
int age = 18;
Console.WriteLine(age);
}
下面編譯會報錯未聲明應用,編譯器檢討到函數內聲明age後,感化域就會籠罩父感化域的age,(像JS就undefined了)。
Func<int> C = () =>
{
var age = 19;
return age;
};
下面聲明個同級函數C,那末A函數是沒法訪C函數中的age變量的。 簡略來講就是弗成跨感化域拜訪其他函數內的變量。 那編譯器是怎樣完成閉包機制的呢?
如上圖,謎底是進級感化域,把A函數進級為一個實例類感化域。 在編譯代碼時代,編譯器檢討到B函數應用A函數內變量時,會主動生成一個匿名類x,把原A函數內變量age晉升為x類的字段(即實例變量),A函數晉升為匿名類x的實例函數。上面是編譯器生成的代碼(精簡過):
class Program1
{
static Func<Func<int>> CachedAnonymousMethodDelegate2;
static void Main(string[] args)
{
Func<Func<int>> func = new Func<Func<int>>(Program1.B);
int num = func()();
}
static Func<int> B()
{
DisplayClass cl = new DisplayClass();
cl.age = 18;
return new Func<int>(cl.A);
}
}
sealed class DisplayClass
{
public int age;
public int A()
{
return this.age;
}
}
我們再來看個龐雜點的例子:
static Func<int, int> GetClosureFunction()
{
int val = 10;
Func<int, int> interAdd = x => x + val;
Console.WriteLine(interAdd(10));
val = 30;
Console.WriteLine(interAdd(10));
return interAdd;
}
Console.WriteLine(GetClosureFunction()(30));
輸入成果是20、40、60。 當看到這個函數內變量val經由過程閉包被傳遞的時刻,我們就曉得val不只僅是個函數內變量了。之前我們剖析過編譯器怎樣生成的代碼,曉得val此時是一個匿名類的實例變量,interAdd是匿名類的實例函數。所以不管val傳遞若干層,它的值一直堅持著,直到分開這個(鏈式)感化域。
關於閉包,在js傍邊議論的比擬多,同理,可以比較懂得下:
function A() {
var age = 18;
return function () {
return age;
}
}
A()();
閉包的長處
1.對變量的掩護。想裸露一個變量值,但又怕聲明類或實例變量會被其他函數淨化,這時候便可以設計個閉包,只能經由過程函數挪用來應用它。
2.邏輯持續性和變量堅持。 A()是履行一部門邏輯,A()()僅接著A()邏輯持續走下去,在這個邏輯高低文時代,變量一直都被堅持著,可以隨便應用。