返回列表 发新帖

探索c#之函数创建和闭包

480.7k 9
swmozowtfl 发表于 2015-7-10 20:46:02|湖北 | 查看全部 阅读模式
阅读目录:
7 N! g$ ^# w, C动态创建函数
9 N. ?* G' a5 C/ U/ y$ L匿名函数不足之处% O. V4 U6 Z% p# V$ V
理解c#中的闭包. D4 f! z& e$ B1 Q* ~5 u6 r& e( V7 W
闭包的优点& N3 n' o8 r& C+ q
动态创建函数
5 K" S- F, h& s' O- P( }" i# s3 @大多数同学,都或多或少的使用过。回顾下c#中动态创建函数的进化:( S0 X: C$ g% j8 y- l
c# 1.0中:
! }. F* @9 u# |6 ]public delegate string dynamicfunction(string name);4 m1 t9 z5 R" M; a; S, f/ ^
public static dynamicfunction getdynamicfunction()  j9 l9 C1 S! k9 {: u
{
, _; T3 |1 Y/ e: n$ b+ E7 Zreturn getname;
( y9 i! z( R# o}) K9 T) {, `  I: Q( F4 K9 U: @
static string getname(string name)
& U* q2 e, a9 c{
( u% Q0 Y1 }& W0 ?& ^0 creturn name;1 R7 _: x' `* m" t- c# K
}
' S- d2 j7 h' {5 N3 uvar result = getdynamicfunction()(mushroom);
8 `: }7 S4 E4 Y6 P: y# H3.0写惯了是不是看起来很繁琐、落后。 刚学委托时,都把委托理解成函数指针,也来看下用函数指针实现的:
! `1 U9 Q* d* n" C% O, b9 W) A# T: uchar getname(char p);2 I# v8 @, z* a1 n
typedef char (*dynamicfunction)(char p);
4 ?0 J, F" ]% V- ]5 Zdynamicfunction getdynamicfunction()' }9 X+ D2 F8 k/ y
{( Y: j) ]& q+ w2 P& p) o
return getname;
) g2 ?3 o1 L. H0 X/ n. c4 w2 {}
8 Z" V3 I. z4 lchar getname(char p)
" d# E& C* \4 n6 ]% W7 }& e1 W1 U{) u! @  g4 }" _+ ~2 z
return p;
$ E5 }' Y  i7 |};
& A- q! r5 z- ochar result = getdynamicfunction()('m');
$ i/ S4 d) u" B; p对比起来和c# 1.0几乎一模一样了(引用/指针差别),毕竟是同一家族的。- c3 Y" c" A" l+ N" ]' a
c# 2.0中,增加匿名函数:
" |2 a- v6 ]/ L3 K# N0 bpublic delegate string dynamicfunction(string name);' }- ^' d8 `% S$ w1 B
dynamicfunction result2 = delegate(string name)0 T2 Y2 }) y3 D! H: ?
{
) S( U0 L) ^: u( G" d( Mreturn name;
7 R9 q) U: F: E2 i. j7 u};! n& E! q* m/ K4 y8 }; l
c# 3.0中,增加lambda表达式,华丽的转身:
; S9 M1 F0 i8 U5 ~public static func<string, string> getdynamicfunction()
4 O1 u. A1 }4 n3 f$ [$ p3 c& p9 O{
5 [- P2 {% W3 L3 Y7 k, c4 Ureturn name => name;
; Q, {8 k, j2 s' e# N}
- `& z% m1 A0 z& v  `var result = getdynamicfunction()(mushroom);
& [9 k( ^* z- I8 n2 p: @匿名函数不足之处9 T6 G$ B5 v9 l6 K. ^# S& h# U- p
虽然增加lambda表达式,已经极大简化了我们的工作量。但确实有些不足之处:
1 i* @7 _# O: o% `var result = name => name;
" Z# U3 @3 N$ U, @8 |: I4 r这些写编译时是报错的。因为c#本身强类型语言的,提供var语法糖只是为了省去声明确定类型的工作量。 编译器在编译时必须能够完全推断出各参数的类型才行。代码中的name参数类: |6 w  Y) i  ^+ w) ]! S* Q
型,显然在编译时无法推断出来的。
8 Q1 A0 B3 P) j" `var result = (string name) => name;9 x+ j+ C9 L' W2 u
func<string, string> result2 = (string name) => name;+ W0 R: _% C: z4 Q: w
expression<func<string, string>> result3 = (string name) => name;7 J+ t  {  ~- H% t% E" y* G' _
上面直接声明name类型呢,很遗憾这样也是报错的。代码中已经给出答案了,编译器推断不出右边表达式是属于func<string, string>类型还是expression<func<string, string>>类型
7 p8 O. D$ L6 `! o$ ^, f" x( `2 R- v6 B+ F/ @
dynamic result = name => name;
$ h1 c& n# m: N2 H0 b* pdynamic result1 = (func<string,string>)(name => name);
; ?9 @( c. v3 q. k; L用dynamic呢,同样编译器也分不出右边是个委托,我们显示转换下就可以了。
# k, {. j  o' k* {func<string, string> function = name => name;
' W" y8 x. ~; @8 O8 h# b! X4 V0 Cdynamicfunction df = function;
- z9 _" ?  L% S这里定义个func委托,虽然参数和返回值类型都和dynamicfunction委托一样,但编译时还是会报错:不能隐式转换func<string, string>到dynamicfunction,2个类型是不兼容的。# Z5 n% e( C, y* }
理解c#中的闭包
+ O$ W/ F  x. F: K" U谈论到动态创建函数,都要牵扯到闭包。闭包这个概念资料很多了,理论部分这里就不重复了。 来看看c#代码中闭包:  B( F. M7 f. P. k
func<func<int>> a = () =>% h2 Z2 m( q: R8 r
{
% k0 z+ w; t* y6 j+ X2 Xvar age = 18;
0 K* g8 U! e: |; qreturn () =>  //b函数1 H. G! y6 |" V5 W
{# C) Q! k! @3 _  `' Q! |: U, c
return age;: N) T% s" o, Y7 C0 U' f
};
& u- O  ~8 X8 v9 g};
# Z% {4 N; \" D7 R7 v# w$ Dvar result = a()();7 i! d8 a# I* T, L; L/ T
上面就是闭包,可理解为就是: 跨作用域访问函数内变量,也有说带着数据的行为。
( o4 c* {# C; Y, m5 P- ^, Fc#变量作用域一共有三种,即:类变量,实例变量,函数内变量。子作用域访问父作用域的变量(即函数内访问实例/类变量)在我们看来理所当然的,也符合我们一直的编程习惯。
5 L! [; O. ?( V/ S$ p1 `4 q) ]例子中匿名函数b是可以访问上层函数a的变量age。对于编译器而言,a函数是b函数的父作用域,所以b函数访问父作用域的age变量是符合规范的。
" ]( i4 m" T% b1 z# Qint age = 16;* [5 Y; G3 O" _  T" D* n
void display()# k2 W3 g& S2 b4 d9 D' }
{$ {. g7 X( W# ]; l7 u' V4 C
console.writeline(age);
1 W+ ~- D3 C" {) A( T, ~int age = 18;, R  ^5 c$ ^$ I9 P3 T) O
console.writeline(age);' [3 k2 w& ?3 h2 Q
}& j: D+ Z2 Q) E! \
上面编译会报错未声明使用,编译器检查到函数内声明age后,作用域就会覆盖父作用域的age,(像js就undefined了)。% ^! s+ R: e! ~" y  _, V8 _9 B, M: X
func<int> c = () =>
: b/ |% p7 I0 D* g% n! I6 \2 U0 o{
9 k/ o, Z7 d) Y5 U) b' z+ P6 o# A7 ovar age = 19;  V8 M: N- W- i1 l) I
return age;) ?3 t% I9 e( p! `9 n
};. a+ k  J/ J  M, x8 X' O3 f- b
上面声明个同级函数c,那么a函数是无法访c函数中的age变量的。 简单来说就是不可跨作用域访问其他函数内的变量。 那编译器是怎么实现闭包机制的呢?, m1 E) M2 T+ T- a9 c
如上图,答案是升级作用域,把a函数升级为一个实例类作用域。 在编译代码期间,编译器检查到b函数使用a函数内变量时,会自动生成一个匿名类x,把原a函数内变量age提升为x类的9 |- ~  R% t+ V
字段(即实例变量),a函数提升为匿名类x的实例函数。下面是编译器生成的代码(精简过):
* Y# d: w3 A' x% g4 A- y7 Lclass program1; ]" u% m& A, s7 |9 {1 x
{, u) H4 Y" V" b1 {
static func<func<int>> cachedanonymousmethoddelegate2;7 [# G) t5 i8 y: x4 _( R: A
static void main(string[] args). `. M$ F- h4 ?! z% R
{& i+ ]. D, j9 F7 q6 n" S" P
func<func<int>> func = new func<func<int>>(program1.b);
9 t6 Z' A6 o2 `9 E( m. l! Qint num = func()();2 `6 A0 A7 s3 G( r7 V, E6 ^& V# T
}
% i4 N4 O* ^6 g- a: f; j7 |8 s; kstatic func<int> b()
, m) [5 P& ]7 t' q" {3 ^{
# V9 C5 w4 g* r8 Udisplayclass cl = new displayclass();
, a/ U+ `% a8 k: z3 h* rcl.age = 18;+ X8 s8 F: j* D5 {0 Y0 s# Z
return new func<int>(cl.a);# W+ |8 r! i+ G5 i( y' s" \
}
; G: f" Q7 [$ o1 B, T# |5 }}9 O% }, S  I7 J9 W7 s: h( K
sealed class displayclass& _, s% L  U9 K+ {1 \1 X  R
{6 m# k' X1 V# j; x4 n" _
public int age;
5 Y# |8 x2 O3 r5 t9 Y3 j8 \1 d/ jpublic int a()9 M4 r+ R' h* d7 W9 D  W; n0 o( X2 i
{
7 B) ^( W+ r( }/ H0 L. S* Kreturn this.age;
& q+ {/ Q  V1 X! \; P! {+ a$ [}
, _3 ]1 ?- Z2 {+ }9 G}4 u6 z. k4 o" p/ a: e
我们再来看个复杂点的例子:3 d( W( b1 |7 \2 D' u8 b3 v
static func<int, int> getclosurefunction()
0 T; ?/ |7 h% ~- H* L$ M8 M{
! `; f, o& g/ t5 I! v8 |. ^3 Dint val = 10;/ n; `7 l- g& m# G( D2 {( {( R' O
func<int, int> interadd = x => x + val;( `) c+ t& L& Y4 U6 }6 ~( c
console.writeline(interadd(10));) d$ B( V2 ?7 Q
val = 30;. A8 u9 `- w# g& \! H' a4 f
console.writeline(interadd(10));
$ N9 L% V* R, M4 Vreturn interadd;9 G# {9 g0 T1 M# `: B
}% h$ V/ {4 w4 z
console.writeline(getclosurefunction()(30));) e% {5 }' _, E2 U9 a. R/ [" k9 \+ n+ f
输出结果是20、40、60。 当看到这个函数内变量val通过闭包被传递的时候,我们就知道val不仅仅是个函数内变量了。之前我们分析过编译器怎么生成的代码,知道val此时是一个匿名! Z" J, D8 w5 ~4 o
类的实例变量,interadd是匿名类的实例函数。所以无论val传递多少层,它的值始终保持着,直到离开这个(链式)作用域。
6 G3 [/ f4 H2 f2 X9 U关于闭包,在js当中谈论的比较多,同理,可以对比理解下:$ M9 N# l! O. W
function a() {
( u/ I1 k( ~( B" w0 }  rvar age = 18;3 _2 t# }: E% {% ^9 R6 `
return function () {
, c* C5 }& d6 E' _return age;- G* \/ ?% V! ?& [) r: z* B1 f
}
, A( ]& n) e. l5 g}
3 V# L3 [# t. g& Xa()();
9 K! {5 [: ?& f0 [! x/ R- t* Q闭包的优点
! R. o% w) {( K8 ?2 e6 A对变量的保护。想暴露一个变量值,但又怕声明类或实例变量会被其他函数污染,这时就可以设计个闭包,只能通过函数调用来使用它。% t* P! N/ i8 n0 D8 v  y: [* ^" y
逻辑连续性和变量保持。 a()是执行一部分逻辑,a()()仅接着a()逻辑继续走下去,在这个逻辑上下文期间,变量始终都被保持着,可以随意使用。
3 v8 `# ~2 v1 K! W- Y- A
5 s* g) Y, T. f: e更多网页制作信息请查看: 网页制作

回复|共 9 个

tohme 发表于 2016-1-26 17:43:08|欧洲 | 查看全部
原来...发神经是这样的啊...
Acropozelan 发表于 2016-1-26 17:43:15|日本 | 查看全部
晚安 别让小嫁再郁闷了 -
wwdu926a 发表于 2016-1-26 17:43:27|马来西亚 | 查看全部
机会就像水中的鱼,耐心等待就能上钩。
bqtklouu 发表于 2016-1-26 17:43:56|西班牙 | 查看全部
什么啊
wwzcdenleclv 发表于 2016-1-26 17:44:13|美国 | 查看全部
baidu是相当能折腾我了
Acropozelan 发表于 2016-1-28 16:21:54|美国 | 查看全部
哎 天理何在啊??
mwxny 发表于 2016-1-28 16:22:12|美国 | 查看全部
这个得知互动技术交流论坛很不错
Mqokjdvq 发表于 2016-1-28 16:22:26|德国 | 查看全部
哈哈 小心被她看见哦~~~~
bqtklouu 发表于 2016-1-28 16:22:34|德国 | 查看全部
楼主说的是什么?我是小白,好像还没看明白!

回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

得知互动是一个融创意、设计、开发、营销、生活、互联网于一体的专业交流分享平台。
Copyright © 2026 得知互动-专注Ai与站长技术交流社区-Ai交流平台 版权所有 All Rights Reserved. Powered by Discuz! X5.0 鄂ICP备15006301号-5|鄂公网安备 42018502006730号
关灯 在本版发帖 扫一扫添加QQ客服 返回顶部
快速回复 返回顶部 返回列表