返回列表 发新帖

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

480.8k 9
swmozowtfl 发表于 2015-7-10 20:46:02|湖北 | 查看全部 阅读模式
阅读目录:
4 U" w" Y2 b0 F, @4 C6 q5 i+ u) m动态创建函数: ^  l. T  N1 I; C, [' c) I
匿名函数不足之处* t: n7 w5 p: W; u, S
理解c#中的闭包' |: n1 A4 S6 E  ?( K9 J: z' E
闭包的优点' k) |6 e) z$ s' K/ s% X. E: T
动态创建函数) w1 O0 z8 _+ |. w8 j* j0 T- R3 W
大多数同学,都或多或少的使用过。回顾下c#中动态创建函数的进化:; ~# z: W2 Z8 S4 N0 [
c# 1.0中:$ e) u2 B3 C5 u* t
public delegate string dynamicfunction(string name);
" G& a- {" t8 Y7 s* {public static dynamicfunction getdynamicfunction()
7 d! X; }( A: N" |6 r" c( ]" @% _{& ^# Q3 q' A- \  f# }2 |5 w
return getname;
8 L" y) J- J0 a- O7 f4 x2 n" d}
1 g( h6 y% o" ~+ L( _( Lstatic string getname(string name): b* l: o0 I; ]+ P5 g
{
/ o" E8 v) J" k6 I5 U0 Lreturn name;
# R% k% j* F! l' Z}0 J, O% J% n) S+ w' k
var result = getdynamicfunction()(mushroom);7 _+ m. G* X8 }1 Q: p* |( S: w; N
3.0写惯了是不是看起来很繁琐、落后。 刚学委托时,都把委托理解成函数指针,也来看下用函数指针实现的:
9 C$ f8 t( n$ i0 d- P; Achar getname(char p);8 d5 r. h/ M3 k( y, q
typedef char (*dynamicfunction)(char p);8 E* I0 u# v9 g7 O5 @
dynamicfunction getdynamicfunction()
# V+ \% {: t& |6 k, A* u  ?{! D+ @& @1 s; E, M( n( q% _! p
return getname;; {8 i5 U+ v* N  C" u9 A
}
6 Q! E0 I# k  ]char getname(char p)% e/ }. H$ R" b+ w) f
{
% g# _' C0 m6 W" P  q2 creturn p;) A' D& ~. p9 L/ t6 u
};% G7 t+ T) t6 ]2 s' |
char result = getdynamicfunction()('m');6 K; P0 P/ k6 Y- e9 T3 ]
对比起来和c# 1.0几乎一模一样了(引用/指针差别),毕竟是同一家族的。2 m! k. r% Q. N
c# 2.0中,增加匿名函数:0 f" C: B8 O: s) V* s4 r
public delegate string dynamicfunction(string name);: }- C! y( P+ I! E8 s( b
dynamicfunction result2 = delegate(string name)3 A" i+ H' L4 C
{
- t! ?6 i  w, O1 W0 x' e7 h% {return name;. f% K5 t" L& E6 a  A
};3 W9 A5 {. R7 C; g8 l
c# 3.0中,增加lambda表达式,华丽的转身:
& E$ h4 a" m1 X" Hpublic static func<string, string> getdynamicfunction()
$ H# T9 t# C; L  E3 \! B{+ r+ B# F0 }; h% G6 u) L3 A' X* v
return name => name;- }1 w" g5 e( ]6 D
}5 @2 L" Q' }* ~( f6 H
var result = getdynamicfunction()(mushroom);
; j( R7 l6 `/ X( H9 R- o匿名函数不足之处
+ i' U, q' A0 Q0 W  Z/ K- z! O# P虽然增加lambda表达式,已经极大简化了我们的工作量。但确实有些不足之处:( h: I+ S$ s# c. Y. P8 J
var result = name => name;
9 r( m  r5 q: g, _) c这些写编译时是报错的。因为c#本身强类型语言的,提供var语法糖只是为了省去声明确定类型的工作量。 编译器在编译时必须能够完全推断出各参数的类型才行。代码中的name参数类* M8 f7 B# @0 K/ _, ^) w
型,显然在编译时无法推断出来的。
+ w% L0 x2 t. F4 gvar result = (string name) => name;4 o1 T% F1 E) G1 i
func<string, string> result2 = (string name) => name;. i0 d+ ]/ ~2 p1 m
expression<func<string, string>> result3 = (string name) => name;
3 e( n; _" h% Q  k' ?+ W上面直接声明name类型呢,很遗憾这样也是报错的。代码中已经给出答案了,编译器推断不出右边表达式是属于func<string, string>类型还是expression<func<string, string>>类型+ ]+ f8 E& b& X* z! C
. D. e, {  s1 N, Z( [2 t% y
dynamic result = name => name;
# Q, i  j" C) m* \% hdynamic result1 = (func<string,string>)(name => name);! c4 n+ V% V$ ]: [
用dynamic呢,同样编译器也分不出右边是个委托,我们显示转换下就可以了。
- A8 b- P' Z; ~/ J6 X" i, j2 rfunc<string, string> function = name => name;) A$ |. ~9 v  \
dynamicfunction df = function;* s6 S' a1 G1 b" H+ z
这里定义个func委托,虽然参数和返回值类型都和dynamicfunction委托一样,但编译时还是会报错:不能隐式转换func<string, string>到dynamicfunction,2个类型是不兼容的。. h3 M" r( i$ [6 O
理解c#中的闭包
( z1 \6 ]1 E/ Y2 [3 p谈论到动态创建函数,都要牵扯到闭包。闭包这个概念资料很多了,理论部分这里就不重复了。 来看看c#代码中闭包:9 @9 s$ O! o% B% x- ?4 Y6 a
func<func<int>> a = () =>* @# r$ W" k5 D( T$ z  P
{
* y' E& T  j. v' i# \var age = 18;
7 w0 g' X4 ^# u1 O& t9 xreturn () =>  //b函数: p# k+ x2 @* f9 P. |2 o
{2 d" ~! x1 t- b
return age;1 o/ A/ L$ m/ Y1 |- c
};
# P% b5 b& W# h};
0 R& w7 [) u( B3 h7 i: `* pvar result = a()();1 z5 L1 E  j. p0 U% B+ ^8 a% q
上面就是闭包,可理解为就是: 跨作用域访问函数内变量,也有说带着数据的行为。
9 \+ d( @  `+ E1 k' {8 V1 Lc#变量作用域一共有三种,即:类变量,实例变量,函数内变量。子作用域访问父作用域的变量(即函数内访问实例/类变量)在我们看来理所当然的,也符合我们一直的编程习惯。
, R, d9 {  r; Y. h例子中匿名函数b是可以访问上层函数a的变量age。对于编译器而言,a函数是b函数的父作用域,所以b函数访问父作用域的age变量是符合规范的。+ ]8 P1 Y+ W" Y9 _% ]
int age = 16;
# V7 g" v5 y' O" [( t3 }void display()" a' \* E6 L' q) P; s- J7 Z% J
{6 r8 j, L( I% E9 q% Z4 W3 k
console.writeline(age);
5 b$ V3 M+ `+ n  P; mint age = 18;1 n1 X6 D$ l# q) L
console.writeline(age);
+ K- y9 U+ A4 E. ]  B}! }& |( s: E) `) ]
上面编译会报错未声明使用,编译器检查到函数内声明age后,作用域就会覆盖父作用域的age,(像js就undefined了)。
' ~# I- D1 T" l# Y/ tfunc<int> c = () =>5 H0 c- i3 r5 t. m& @" B
{5 w# ]/ Z5 W; \0 F8 f+ |
var age = 19;* [$ h: k' E  O5 @
return age;8 X/ e# {+ I  V# p% {9 [
};& O' z7 w; q0 F( N' u( n2 n. }& ~
上面声明个同级函数c,那么a函数是无法访c函数中的age变量的。 简单来说就是不可跨作用域访问其他函数内的变量。 那编译器是怎么实现闭包机制的呢?, F/ c& K% Z2 z7 h; t0 s, x1 u
如上图,答案是升级作用域,把a函数升级为一个实例类作用域。 在编译代码期间,编译器检查到b函数使用a函数内变量时,会自动生成一个匿名类x,把原a函数内变量age提升为x类的" ?- ~# X  y- a
字段(即实例变量),a函数提升为匿名类x的实例函数。下面是编译器生成的代码(精简过):
3 q: z6 J" @( Oclass program1$ x; B: Y; k! F. g! ~4 a
{; A4 V3 {1 c+ j# Z" ?+ h* z* ?6 }
static func<func<int>> cachedanonymousmethoddelegate2;
$ k7 k( R. a* G& Nstatic void main(string[] args)
& c6 b+ V- F8 {' E{: F5 x, V8 ]- _9 |0 l
func<func<int>> func = new func<func<int>>(program1.b);/ x3 v2 y5 V% E+ o* |- W
int num = func()();4 E; U/ g. I9 N' d$ S# X* V5 Y8 Z
}
, r  m  k4 h1 d' Z0 k, s! }static func<int> b()9 f1 J# c8 k  G4 g+ m" n8 G1 u
{( k( b  i$ F: y; y9 S- {3 u
displayclass cl = new displayclass();, Z  w# D/ |' }( k) k% u9 z
cl.age = 18;) `. C9 f) m/ Q7 o' I% s; h1 ]
return new func<int>(cl.a);4 ]" H) H" q9 O! r) u0 _& }$ s
}7 m8 ]& f  O/ j$ s( W
}4 Y, }. s- n" c1 T- l- h2 s
sealed class displayclass
) X7 t. b: @& Z9 @0 S2 p{" B4 ?/ P- e% }- Q
public int age;- E+ c1 s4 }# Y, ]( Q/ U/ S
public int a()2 m, l- `7 @* o. v1 O
{! B0 |# [5 \" K- L& I+ G9 z  ?8 V. k
return this.age;& S: D3 r' Z3 o1 M& @% l
}
* @; z$ ]& `4 J- u}  [. g& t! Y) }7 ^& [
我们再来看个复杂点的例子:
1 l6 {1 I( w: T* x  G" astatic func<int, int> getclosurefunction()
- d7 c2 z; D/ p/ u5 }$ U! W8 M{' Q, u5 \( Y1 |) j9 C/ J! c
int val = 10;+ i+ r/ V" `, H- B& K$ @
func<int, int> interadd = x => x + val;' q: H* E8 r* g/ N1 P) Z
console.writeline(interadd(10));* q# b  Z/ W  v6 G2 v, I
val = 30;$ j+ }) T; s" G3 [1 N! t7 r
console.writeline(interadd(10));6 U( i  f# i5 U+ |
return interadd;2 ^, L7 F! Q+ A. b) P0 X$ y* D
}3 {0 \8 }: t' V$ Q" R8 s
console.writeline(getclosurefunction()(30));
5 N$ C: h$ Z. p3 ~& S/ c输出结果是20、40、60。 当看到这个函数内变量val通过闭包被传递的时候,我们就知道val不仅仅是个函数内变量了。之前我们分析过编译器怎么生成的代码,知道val此时是一个匿名
8 Q" V& J" p3 `; X8 ?) ]7 a类的实例变量,interadd是匿名类的实例函数。所以无论val传递多少层,它的值始终保持着,直到离开这个(链式)作用域。3 W( U2 Y. Y; m2 v
关于闭包,在js当中谈论的比较多,同理,可以对比理解下:/ C0 {; M" {; X. [0 o7 |: r
function a() {! a: x6 T2 U, Y0 o& \. ~; h
var age = 18;
2 F2 ^- r) R3 {, o  mreturn function () {! m$ |- q" M' y/ t0 o
return age;' D6 T2 l0 @9 j1 e
}3 ~( X2 x# y* I, v( M# ~& N
}
5 U9 R* M5 P; g  d4 W$ La()();! ?. X) F  u5 {1 u: t; x  x
闭包的优点- m- h; V) n6 l0 c5 M
对变量的保护。想暴露一个变量值,但又怕声明类或实例变量会被其他函数污染,这时就可以设计个闭包,只能通过函数调用来使用它。" j  `9 s. B" v8 Y
逻辑连续性和变量保持。 a()是执行一部分逻辑,a()()仅接着a()逻辑继续走下去,在这个逻辑上下文期间,变量始终都被保持着,可以随意使用。
6 m- d6 U( `; b$ Y0 G9 {" u9 B6 P& P6 Q
更多网页制作信息请查看: 网页制作

回复|共 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客服 返回顶部
快速回复 返回顶部 返回列表