返回列表 发新帖

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

480.7k 9
swmozowtfl 发表于 2015-7-10 20:46:02|湖北 | 查看全部 阅读模式
阅读目录:
0 ^( D" I. E4 t6 s  e1 A动态创建函数
1 W/ c2 M' V0 V! S3 g& l. x; @匿名函数不足之处
, `! V3 b" H5 D, A( v1 I5 D& ?' Y理解c#中的闭包$ ?9 K6 Y$ h+ q2 T. E5 x
闭包的优点* z7 {9 ?# u5 L2 `0 W
动态创建函数
/ J# e) t  B' ^大多数同学,都或多或少的使用过。回顾下c#中动态创建函数的进化:
7 B- Q- t! m: X& p% O) w, b3 @) mc# 1.0中:
: K1 h1 C( g  W  |) G- s4 `public delegate string dynamicfunction(string name);- C" H1 X6 o) |! W& J
public static dynamicfunction getdynamicfunction()
- W0 v7 k! k# U{
" J5 j. t7 O! W2 sreturn getname;
( B4 }- z* L; ]5 R# U6 Y" \) p+ K" p}  B6 }* Q0 `1 I* R$ a+ V: E9 V
static string getname(string name)8 J6 x3 a6 K2 P& B. _
{8 {5 D! S7 O. m4 e6 r' y
return name;! _3 W4 b5 _! @6 Q
}6 u1 b, b; ]7 r# T: {
var result = getdynamicfunction()(mushroom);; c' B8 S% B' x6 s
3.0写惯了是不是看起来很繁琐、落后。 刚学委托时,都把委托理解成函数指针,也来看下用函数指针实现的:
0 F3 x6 z* i' p. [char getname(char p);, H  r4 ]' q1 ~& s( e) u! ~+ G
typedef char (*dynamicfunction)(char p);# S8 Q3 ?# i+ |% l' ?8 b
dynamicfunction getdynamicfunction()
; J/ d4 X. L2 z8 E9 W{7 z! g, N0 w+ j- s
return getname;4 X% d' G. m! w+ B- l8 e( d! C" k
}
* Q$ }9 H1 e; u) r& g' U' ?char getname(char p)
! c. W1 f8 c; j. F{0 s& S1 C( n5 K, P
return p;9 {8 P# m9 M0 O4 m
};
$ c+ e# a% M& F5 q& V# jchar result = getdynamicfunction()('m');* ?1 D- [1 i# g, A) o
对比起来和c# 1.0几乎一模一样了(引用/指针差别),毕竟是同一家族的。
+ n3 o2 |9 p6 ~- h! B7 y7 Oc# 2.0中,增加匿名函数:
/ W) J5 i$ b( Epublic delegate string dynamicfunction(string name);
9 ]6 s4 P* n; a2 W$ H  D; w4 X' wdynamicfunction result2 = delegate(string name)
) x# [: ?' p! z, ]' r{
4 i7 E" f6 z3 t1 x) F$ P1 q/ [  z! preturn name;
9 r) D6 y; y1 y$ Z: j* z8 ^};
' H$ ]5 x  T8 w4 d7 o! ec# 3.0中,增加lambda表达式,华丽的转身:; {8 {; h' h8 A7 u
public static func<string, string> getdynamicfunction()
* Y1 Z+ C8 I  p; |/ _, \+ E2 D3 V/ O{
6 K( Q$ G% M( ?/ `& V+ z. O, Lreturn name => name;; s* e" {9 m( v
}
' M8 B8 y. s! ^4 C/ ]5 C/ \3 B$ Qvar result = getdynamicfunction()(mushroom);8 U! u# c1 n/ z5 t- ~  g
匿名函数不足之处
# p- {( g6 ]. d& u/ |; b虽然增加lambda表达式,已经极大简化了我们的工作量。但确实有些不足之处:
1 A4 F! V  h1 _7 E9 _var result = name => name;) C' [2 p! H& d* F6 Z) A7 H
这些写编译时是报错的。因为c#本身强类型语言的,提供var语法糖只是为了省去声明确定类型的工作量。 编译器在编译时必须能够完全推断出各参数的类型才行。代码中的name参数类
1 z# t. B5 Z8 d; D型,显然在编译时无法推断出来的。9 ^: G: y6 ~# j1 t( j
var result = (string name) => name;
2 J! G9 C& W, s& p6 Xfunc<string, string> result2 = (string name) => name;
/ p, X+ M/ \5 A/ Cexpression<func<string, string>> result3 = (string name) => name;8 K4 N: z8 ]1 n9 M" @7 p
上面直接声明name类型呢,很遗憾这样也是报错的。代码中已经给出答案了,编译器推断不出右边表达式是属于func<string, string>类型还是expression<func<string, string>>类型7 H2 M  k. r$ r* H! A

% b: q- z. X8 X0 `9 ~" t. _dynamic result = name => name;
* T& g' l# E& }& T6 fdynamic result1 = (func<string,string>)(name => name);
, `; y8 W  _' p$ S! S用dynamic呢,同样编译器也分不出右边是个委托,我们显示转换下就可以了。( k$ W  E# g# `* ^. V* O
func<string, string> function = name => name;! J3 o/ I+ U( Y& U
dynamicfunction df = function;. D1 a  i6 M1 W  Z4 H5 J! k5 X- I
这里定义个func委托,虽然参数和返回值类型都和dynamicfunction委托一样,但编译时还是会报错:不能隐式转换func<string, string>到dynamicfunction,2个类型是不兼容的。
4 D. K/ b& o5 y  J( \" a理解c#中的闭包! |; ?$ C6 x7 D0 b2 N; s
谈论到动态创建函数,都要牵扯到闭包。闭包这个概念资料很多了,理论部分这里就不重复了。 来看看c#代码中闭包:5 Y8 ~: N$ [2 n) h, q8 v5 v; u
func<func<int>> a = () =>* Z) Y. ~6 f* _; h& M
{! u' f* B* T$ m+ W8 o1 c
var age = 18;2 D' f7 y3 v9 `" e5 r1 v. y
return () =>  //b函数* k: y% i7 x. D. @
{5 c; f; e* g/ j; G
return age;( m% k& ?  n% E( `7 ~, M
};
. j( n( t2 @5 e( q7 m};1 e8 Y  O+ F9 d( m
var result = a()();4 A% A# }+ V* N$ k+ f$ W
上面就是闭包,可理解为就是: 跨作用域访问函数内变量,也有说带着数据的行为。
! S8 }  B/ a6 G/ |$ r4 Oc#变量作用域一共有三种,即:类变量,实例变量,函数内变量。子作用域访问父作用域的变量(即函数内访问实例/类变量)在我们看来理所当然的,也符合我们一直的编程习惯。
+ d; u8 }' l* @& I( T; ^例子中匿名函数b是可以访问上层函数a的变量age。对于编译器而言,a函数是b函数的父作用域,所以b函数访问父作用域的age变量是符合规范的。6 g4 a$ z6 C% ?  x) o
int age = 16;' `0 K1 M, W& w( U2 ]  G- @
void display()
4 I8 i. Y. _4 E# _{
2 F& A3 d- y% g0 N" iconsole.writeline(age);' X  g: b) N9 W/ c" l
int age = 18;
, v% e& P  N+ ?2 E; z& H0 Zconsole.writeline(age);
& R: n! s6 B# K# V2 Z& a5 a}4 S2 |" n/ K7 H1 ^% k( s) W
上面编译会报错未声明使用,编译器检查到函数内声明age后,作用域就会覆盖父作用域的age,(像js就undefined了)。' A4 k0 |2 w4 Q0 |# N8 B
func<int> c = () =>
8 @$ z1 m5 y: h4 ^{4 o; r& H  [0 d. d
var age = 19;" W$ @( [1 o# C: @# s6 t8 p6 y
return age;" q0 h: c- q# o9 e5 _$ K% g/ X4 Z8 D
};: F- s& w3 ]8 s7 F) Z5 i: s# v0 Q
上面声明个同级函数c,那么a函数是无法访c函数中的age变量的。 简单来说就是不可跨作用域访问其他函数内的变量。 那编译器是怎么实现闭包机制的呢?
  i" @9 _# C& x0 U! r如上图,答案是升级作用域,把a函数升级为一个实例类作用域。 在编译代码期间,编译器检查到b函数使用a函数内变量时,会自动生成一个匿名类x,把原a函数内变量age提升为x类的. R/ _2 X8 H1 b0 N
字段(即实例变量),a函数提升为匿名类x的实例函数。下面是编译器生成的代码(精简过):
# T) O/ O9 G- |8 rclass program12 z+ c. |4 w# k# n- y  k
{
' c* o6 r# u( g6 }/ E; Kstatic func<func<int>> cachedanonymousmethoddelegate2;: M8 R& p8 M# x. p
static void main(string[] args)
" |+ g5 X( |, U3 o$ M8 |6 {{
! j: T5 v3 P, W3 Q5 D6 w* Ifunc<func<int>> func = new func<func<int>>(program1.b);! `2 m: I( H& u3 y* K& J
int num = func()();
2 r0 z7 q7 H) B}3 N. z, Z$ A9 P
static func<int> b()
0 F. `+ j: o2 G, Q/ r2 R: I{$ l4 j# R+ ~% b/ B8 i* O
displayclass cl = new displayclass();5 s! X, P+ D6 C
cl.age = 18;4 W8 x/ H3 T/ M5 n, o) {
return new func<int>(cl.a);
$ B/ [. z/ X1 E1 M+ s4 W}
0 f8 m+ o8 }$ A( ]7 c' D}
4 p+ Y' q- T# `9 u4 _! Lsealed class displayclass  L9 X. A7 o; F8 D  ^+ K& r! H* |) h
{/ K; s0 Y8 Q& C; y# ~5 Y
public int age;' ?7 \$ C% L; Z/ O& R; p- k: Y. X
public int a()
( `, ~- p3 v5 Q+ v3 I2 A) i{4 n% L% F$ C, Q# O# v3 |- L* J0 s
return this.age;
! \  u3 F; s0 l4 c& @}' y; b" U* v/ z! x
}. o! q7 w& z# U( x4 `/ t
我们再来看个复杂点的例子:
' S- p- Z1 |5 u  {2 R" Jstatic func<int, int> getclosurefunction()* r8 `; d: X* R+ ~3 W
{
2 j0 V" a  T, }1 \8 S8 ^+ uint val = 10;
7 c4 D7 {5 `: }. O$ b8 V6 Q, q' g2 S3 rfunc<int, int> interadd = x => x + val;" L& f2 D3 n& _
console.writeline(interadd(10));
0 F3 \( i6 v; c' D( r8 ^6 y1 @5 Jval = 30;3 u9 [! T& r9 W9 |* ]3 g$ y+ Z, J
console.writeline(interadd(10));) Y/ E  r! `( k7 }
return interadd;
( f! N# w# ~5 i! J9 ]}
, W, U! Q5 F) z$ v7 z, Fconsole.writeline(getclosurefunction()(30));- b# E: x' |9 ]% |9 h* C
输出结果是20、40、60。 当看到这个函数内变量val通过闭包被传递的时候,我们就知道val不仅仅是个函数内变量了。之前我们分析过编译器怎么生成的代码,知道val此时是一个匿名3 I$ i4 G! n$ V, s' k
类的实例变量,interadd是匿名类的实例函数。所以无论val传递多少层,它的值始终保持着,直到离开这个(链式)作用域。
6 y1 {4 S; l) [% r! `关于闭包,在js当中谈论的比较多,同理,可以对比理解下:* q4 D1 ?7 Y& d! r; Z% @- }, t. m
function a() {/ o7 l( L, i0 L
var age = 18;# w* S7 k  j0 C5 }# K2 S
return function () {
# i- l+ R/ l6 C6 ~) i3 _; F* Hreturn age;& L( y% q% k$ P
}/ k. K( G4 c* ]. N- N
}* p. y5 K- p/ c% k
a()();- F9 _* V" C: b
闭包的优点6 h& {2 u! b$ W
对变量的保护。想暴露一个变量值,但又怕声明类或实例变量会被其他函数污染,这时就可以设计个闭包,只能通过函数调用来使用它。
3 ~; N0 E, m8 a; N+ V- N逻辑连续性和变量保持。 a()是执行一部分逻辑,a()()仅接着a()逻辑继续走下去,在这个逻辑上下文期间,变量始终都被保持着,可以随意使用。
. \. |7 C* ?) e" S- j3 h
3 D; W7 H! E0 o5 t% J  ~  P6 t% O更多网页制作信息请查看: 网页制作

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