返回列表 发新帖

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

480.6k 9
swmozowtfl 发表于 2015-7-10 20:46:02|湖北 | 查看全部 阅读模式
阅读目录:
) f3 J6 k) S% A动态创建函数
# n0 ~0 ^( E7 R( R% y匿名函数不足之处
6 X2 n8 S# g  H4 e0 A理解c#中的闭包9 I/ f  V0 M" P
闭包的优点9 I* a6 J: L* v) {2 `* E
动态创建函数8 g& o4 s6 P7 u* K$ j5 F" F7 U2 C
大多数同学,都或多或少的使用过。回顾下c#中动态创建函数的进化:0 z  s* x0 h2 H
c# 1.0中:# n9 c" w! `3 b: e
public delegate string dynamicfunction(string name);
& l( r9 `: N( x5 V  N2 Wpublic static dynamicfunction getdynamicfunction()
# D2 b% w" E- h: `{4 q, e3 D  a4 [6 h
return getname;  v- c4 `& s8 T" h
}
- Z; q! H/ x7 \- s/ P" Wstatic string getname(string name)+ ^+ O$ I, E" @
{( l# M# D3 H; O/ o6 O$ p
return name;4 q% r- B! k. V9 k
}% L! i2 i1 Q$ l  x" V  J
var result = getdynamicfunction()(mushroom);- |+ s7 [: g* t% c; ?# B
3.0写惯了是不是看起来很繁琐、落后。 刚学委托时,都把委托理解成函数指针,也来看下用函数指针实现的:& L* u% c* d# v& B: d; i/ W3 D
char getname(char p);  i6 Q$ Q  U- _; @7 ?- @
typedef char (*dynamicfunction)(char p);
& T( b# y7 p* d: a8 L: ?dynamicfunction getdynamicfunction()
- N3 x9 s$ b3 \; k- O5 _{
, ~, L! V+ Y$ vreturn getname;, J$ {3 Z  V( A" `; w$ A. r
}9 y  {! y* i5 P9 j6 L( Q( p
char getname(char p)% v- E& Z/ N, k5 e
{9 q- k: k& C7 f% p4 X
return p;
$ t% `3 [. [1 y2 \% j};
- g7 `- N# w$ ~- D+ @char result = getdynamicfunction()('m');" K3 B5 F' S1 ~( [0 o) N
对比起来和c# 1.0几乎一模一样了(引用/指针差别),毕竟是同一家族的。' h- [! F9 O- y
c# 2.0中,增加匿名函数:
2 t$ F' i8 }; H) s/ v# [public delegate string dynamicfunction(string name);
0 Y3 e: X. U' r. ^dynamicfunction result2 = delegate(string name)
$ t  A! X4 p; E5 {" d{. \) H2 t7 M! w  @2 }
return name;
2 C# T1 h7 B7 ^, _: G' F: [. s};
* h( p9 g, p& q  Z' u* _. K! a. }c# 3.0中,增加lambda表达式,华丽的转身:4 C  y7 c9 c' r& |
public static func<string, string> getdynamicfunction()$ w# a7 g0 X. E5 M
{* P1 `) O) j% t3 A
return name => name;
! V$ k4 `1 I1 x4 q' z% K}' `4 B! t- K7 r
var result = getdynamicfunction()(mushroom);
# B% H4 ~/ m9 Q( X匿名函数不足之处6 I; _: |# v  ~: x  Z
虽然增加lambda表达式,已经极大简化了我们的工作量。但确实有些不足之处:* {6 B; a9 a$ i/ A
var result = name => name;
( @% {6 D5 D+ I6 C这些写编译时是报错的。因为c#本身强类型语言的,提供var语法糖只是为了省去声明确定类型的工作量。 编译器在编译时必须能够完全推断出各参数的类型才行。代码中的name参数类
$ V# @) b& ?3 z/ v" r3 @型,显然在编译时无法推断出来的。
# D9 N, q2 Q& [5 c$ `/ ^var result = (string name) => name;
# F$ l) O* |- P5 Qfunc<string, string> result2 = (string name) => name;+ G, B4 I, z% g5 p7 c
expression<func<string, string>> result3 = (string name) => name;
5 \4 w' J% C% r) p上面直接声明name类型呢,很遗憾这样也是报错的。代码中已经给出答案了,编译器推断不出右边表达式是属于func<string, string>类型还是expression<func<string, string>>类型
2 T7 d: _! j( ~, ~5 d- F9 [
+ ]1 T" i- x0 A% F; j' }dynamic result = name => name;( ~+ Y0 D! _! z! k5 g# f
dynamic result1 = (func<string,string>)(name => name);
& s' y. K% ^( w) l! f8 ^: w用dynamic呢,同样编译器也分不出右边是个委托,我们显示转换下就可以了。% s1 I% v% C  P0 d0 Z$ s
func<string, string> function = name => name;' Y) ~5 J$ V9 b3 l3 }
dynamicfunction df = function;) i$ S% Y. l$ H5 N1 u$ G/ e0 ]
这里定义个func委托,虽然参数和返回值类型都和dynamicfunction委托一样,但编译时还是会报错:不能隐式转换func<string, string>到dynamicfunction,2个类型是不兼容的。
( C+ F$ R- |7 D3 w8 S3 f9 t理解c#中的闭包. t) g3 n- a# o- j1 R& q
谈论到动态创建函数,都要牵扯到闭包。闭包这个概念资料很多了,理论部分这里就不重复了。 来看看c#代码中闭包:
! _& j( k# w' R4 n+ Tfunc<func<int>> a = () =>% P$ D1 C# h6 h7 @6 Z5 W
{
5 h7 n8 x5 q' U" L) ~4 J- Ovar age = 18;
' ?9 Z# h8 z% W! I  ~% creturn () =>  //b函数% J, r! P" c/ _8 z, T- g
{
. a$ B+ `# Y4 Zreturn age;
4 e- P/ U( \% \. \, G};' d6 k' Q' J! I, f# n
};1 q, l0 m4 a3 S
var result = a()();: x2 _6 }+ A+ }! h" J6 L' }6 i
上面就是闭包,可理解为就是: 跨作用域访问函数内变量,也有说带着数据的行为。; X( x; X& X5 T3 T# z  z2 P0 ^
c#变量作用域一共有三种,即:类变量,实例变量,函数内变量。子作用域访问父作用域的变量(即函数内访问实例/类变量)在我们看来理所当然的,也符合我们一直的编程习惯。
. T1 D. w# k2 y1 J; e( p例子中匿名函数b是可以访问上层函数a的变量age。对于编译器而言,a函数是b函数的父作用域,所以b函数访问父作用域的age变量是符合规范的。% u: _  i' B+ f$ c: m' C
int age = 16;
9 o: ]7 h/ I4 gvoid display()
; x' n& O! c4 a& Q{: n7 {6 Y" ]) @
console.writeline(age);/ P/ F' I2 W) t: j$ w  ~, r" @
int age = 18;$ X* E5 p( f8 b
console.writeline(age);
% |7 }% ~9 O3 U" a3 K, c5 O. T}6 ^% E# r4 [4 I
上面编译会报错未声明使用,编译器检查到函数内声明age后,作用域就会覆盖父作用域的age,(像js就undefined了)。1 V8 H  {# t/ Y( R: o
func<int> c = () =>6 D2 B$ ?) R$ k9 W5 G
{
1 O6 f( r$ \) t4 R  n( Z5 J" I( bvar age = 19;
& p6 x' i* B7 d+ \/ ^9 \return age;) Z& d; p7 k; n- w
};- j: T. G/ U4 f8 P: g- `9 c5 Y, ?
上面声明个同级函数c,那么a函数是无法访c函数中的age变量的。 简单来说就是不可跨作用域访问其他函数内的变量。 那编译器是怎么实现闭包机制的呢?" B, v1 y" a, f$ C
如上图,答案是升级作用域,把a函数升级为一个实例类作用域。 在编译代码期间,编译器检查到b函数使用a函数内变量时,会自动生成一个匿名类x,把原a函数内变量age提升为x类的& e* D: G1 i! ^7 {$ F9 w
字段(即实例变量),a函数提升为匿名类x的实例函数。下面是编译器生成的代码(精简过):' n+ a* X; q, i
class program1
# ~- b) k* g8 N5 t{
5 k/ s6 T4 V  A( U- Cstatic func<func<int>> cachedanonymousmethoddelegate2;
9 V* I) ?$ i* r( d  o: e0 Gstatic void main(string[] args)' V& A* Y/ `9 i6 y- N9 B8 i
{
: M  Y1 V, i0 l9 ]3 u. ffunc<func<int>> func = new func<func<int>>(program1.b);: J* a( Q) k0 n+ e( ?4 E% T- n1 W
int num = func()();
7 O+ r3 R+ b( q" b! V}
! r1 A$ n. B& E' m7 O; wstatic func<int> b()7 C) t: O0 \: h8 O
{
" G) W, ~: n! l& M* |* l$ Q( d4 J& Ydisplayclass cl = new displayclass();8 j( x5 N  o8 H9 X
cl.age = 18;
6 e! N0 m9 {4 v. Z' T2 vreturn new func<int>(cl.a);/ P/ Y  V2 ?9 P0 r) K) I1 \
}1 q) {: r$ n) f3 A4 {
}
$ Q- E: f& P! ^' o1 ssealed class displayclass$ ~, F( N; {+ s4 Y  m  f
{
$ Y  k0 U2 D5 C; o5 a; T; i( bpublic int age;$ a" V  I5 [/ E
public int a()
8 j4 C; ?: e" V{
0 m' c4 b0 ^  Y, B: k' z# lreturn this.age;7 \4 C; P6 s2 E6 y8 d- d6 x
}
, F* Z/ z6 l$ B; O7 }: K}2 u+ `, F: {8 d1 k3 O$ b0 d6 w* D
我们再来看个复杂点的例子:
  j) [; V0 \4 z; }6 e5 J7 Lstatic func<int, int> getclosurefunction()
3 ]# V  M! f, L( n- a5 j{2 b" S# d' ?+ [# R5 v
int val = 10;
1 d: z  M& [/ ?' o5 hfunc<int, int> interadd = x => x + val;
$ x3 Z" P" s/ E+ f' p- r& |& Rconsole.writeline(interadd(10));
0 R5 E* r0 R4 o% [8 x8 rval = 30;
. O5 \" `$ P7 M3 B* Qconsole.writeline(interadd(10));# o9 {# m, g2 R7 [' ?0 `
return interadd;
$ k" A$ n- U+ p: N9 _; x}
5 ?! k, U5 P' R7 `- aconsole.writeline(getclosurefunction()(30));7 l- }0 I, y- @4 s0 u
输出结果是20、40、60。 当看到这个函数内变量val通过闭包被传递的时候,我们就知道val不仅仅是个函数内变量了。之前我们分析过编译器怎么生成的代码,知道val此时是一个匿名
2 _6 `2 t* W4 b2 A. h0 |类的实例变量,interadd是匿名类的实例函数。所以无论val传递多少层,它的值始终保持着,直到离开这个(链式)作用域。
1 m8 a# i, @0 V. r; @关于闭包,在js当中谈论的比较多,同理,可以对比理解下:4 g& j/ j5 {6 o# M
function a() {
1 `5 G  z6 l6 y  nvar age = 18;
! i5 u, @  Z0 @+ x5 W6 \return function () {9 e2 w% ?# J/ f  O
return age;
2 p$ x) z2 W0 T}
8 u) \2 p5 y. L1 I}! h8 Q# S+ s) K  @+ [1 G
a()();
. h* d# b4 E, C* T( T闭包的优点1 {2 D/ y! X& {# u/ l2 ^# C2 r
对变量的保护。想暴露一个变量值,但又怕声明类或实例变量会被其他函数污染,这时就可以设计个闭包,只能通过函数调用来使用它。
, S+ h2 j# E9 T4 K( G逻辑连续性和变量保持。 a()是执行一部分逻辑,a()()仅接着a()逻辑继续走下去,在这个逻辑上下文期间,变量始终都被保持着,可以随意使用。, x/ Y! V5 h# i3 m* i# T
) @3 {- `. X3 E/ c/ _# p6 N; V
更多网页制作信息请查看: 网页制作

回复|共 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 站长技术交流论坛|互联网技术交流平台 版权所有 All Rights Reserved. Powered by Discuz! X5.0 鄂ICP备15006301号-5|鄂公网安备 42018502006730号
关灯 在本版发帖 扫一扫添加QQ客服 返回顶部
快速回复 返回顶部 返回列表