返回列表 发新帖

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

480.8k 9
swmozowtfl 发表于 2015-7-10 20:46:02|湖北 | 查看全部 阅读模式
阅读目录:! x" h! u7 a& o. e
动态创建函数
& T0 }" j- [+ D+ Q匿名函数不足之处
6 J' U4 l, h- E" ^% v5 r理解c#中的闭包# b% l- `. N) q0 P
闭包的优点' h( ^5 c- |' i* B6 v
动态创建函数5 \8 ^7 P" |0 Z0 g) e7 W
大多数同学,都或多或少的使用过。回顾下c#中动态创建函数的进化:  h- k) y) v2 T
c# 1.0中:
/ r: T2 `8 Z& |& c) f. h5 Xpublic delegate string dynamicfunction(string name);+ N4 H/ k! J8 D* J& h( `& s" j
public static dynamicfunction getdynamicfunction()
7 J$ N  h! q$ j4 s! B8 x{1 x9 U3 p  I  T7 U& d  s( G$ n
return getname;  v- K8 h. E6 j# |5 B7 i# p1 ]
}
7 u& }2 P" @6 _/ istatic string getname(string name)# o& i. \6 O4 I4 j9 G: M' J) v; R
{3 d/ x: G6 H) W  r- X! ?; C4 Q7 k
return name;
$ E9 M% ?7 [, ~$ J6 ~}
& h) t$ {( @8 M1 R! y# _6 Nvar result = getdynamicfunction()(mushroom);/ `/ k. Y6 d: j* L
3.0写惯了是不是看起来很繁琐、落后。 刚学委托时,都把委托理解成函数指针,也来看下用函数指针实现的:5 N2 Q5 z) K0 K, i$ ?
char getname(char p);
8 G/ c+ V0 Q& E* Htypedef char (*dynamicfunction)(char p);
4 o3 _7 w, a/ l( Ldynamicfunction getdynamicfunction()8 ^3 A6 n4 T& Q' e4 _. m
{
+ m7 t! V$ {* F7 F8 I/ Lreturn getname;. S' U8 W* ]0 G. X$ i0 O" k& q$ o
}; g; [' }4 P: E' h4 H' _
char getname(char p)
- t/ O( @0 B. u' l( U{
0 [# [2 M4 c1 C! f& V) L7 v+ ?return p;
5 Z! @3 }+ Y9 B  [4 ?& `, z};
0 Z4 B/ l' l' uchar result = getdynamicfunction()('m');5 n6 M$ `# x* P& l9 J# [* T
对比起来和c# 1.0几乎一模一样了(引用/指针差别),毕竟是同一家族的。+ r$ ^' j* \* e! [8 {  C6 ?! a
c# 2.0中,增加匿名函数:8 a* q2 A, M/ n6 l
public delegate string dynamicfunction(string name);& _% L1 V+ U- q5 F+ m" J! v
dynamicfunction result2 = delegate(string name)5 J, i  L- H3 T
{4 b2 c( M( i: O; `$ Z
return name;
' H, D. Z. ?4 e* ~};
; s0 l* \0 G, M6 R0 U/ k! gc# 3.0中,增加lambda表达式,华丽的转身:7 g% V) ~$ r1 Q: v
public static func<string, string> getdynamicfunction()8 x' R7 T' W; d
{
1 j8 _( n1 J9 \* o" Dreturn name => name;
: {3 _; S* `+ u% M) V% E. ~}
5 q$ f3 j" O% d4 ?/ Y; j( {var result = getdynamicfunction()(mushroom);
! n: l6 G( u. Y+ A; Q. g匿名函数不足之处$ c. ~; Y4 ]0 V2 s$ F( w' `
虽然增加lambda表达式,已经极大简化了我们的工作量。但确实有些不足之处:8 r) o+ x, A8 i6 G2 K
var result = name => name;3 Z* T: ~# w' A6 O
这些写编译时是报错的。因为c#本身强类型语言的,提供var语法糖只是为了省去声明确定类型的工作量。 编译器在编译时必须能够完全推断出各参数的类型才行。代码中的name参数类$ T: T& o3 v' ^2 B* M
型,显然在编译时无法推断出来的。
' G3 K* l. |) P. Fvar result = (string name) => name;
5 q9 ^8 u- _# @  q& j  E; sfunc<string, string> result2 = (string name) => name;
) k5 W5 X( o0 J! w6 l5 Pexpression<func<string, string>> result3 = (string name) => name;
- G' a; R* E3 @6 L6 Y4 K上面直接声明name类型呢,很遗憾这样也是报错的。代码中已经给出答案了,编译器推断不出右边表达式是属于func<string, string>类型还是expression<func<string, string>>类型1 C5 I3 g7 L& T( }
# g) i" Y, y  E
dynamic result = name => name;& b4 j0 U2 t' \4 L; w# Q6 Y: T
dynamic result1 = (func<string,string>)(name => name);
% @, v- t0 E. Q( b  A' s% o9 S0 g用dynamic呢,同样编译器也分不出右边是个委托,我们显示转换下就可以了。9 i+ r# a( w: b- f" K1 M
func<string, string> function = name => name;9 f/ y. n  J* u0 a$ h0 J) ]
dynamicfunction df = function;
" `/ w. m  L# j# R7 i- V4 q这里定义个func委托,虽然参数和返回值类型都和dynamicfunction委托一样,但编译时还是会报错:不能隐式转换func<string, string>到dynamicfunction,2个类型是不兼容的。8 O6 |  u" z" f& A
理解c#中的闭包/ A' q1 n. n3 g( l2 W" G2 y
谈论到动态创建函数,都要牵扯到闭包。闭包这个概念资料很多了,理论部分这里就不重复了。 来看看c#代码中闭包:
/ }- l+ V% q) t4 c* R, c. R; ufunc<func<int>> a = () =>$ J( v) G' n" Z( L+ k7 @
{- P( R% m* x3 J7 g
var age = 18;" Y! U5 o7 @8 z0 E: t" x( [1 u
return () =>  //b函数! _- N. L  L$ I6 a+ c
{  j, H6 n1 B, \% J% Y) }* M/ P
return age;5 L  i" z, }5 C1 x
};6 K4 ]0 X" \; D# c
};
; j# |6 ], k5 }# l3 b: cvar result = a()();& k% q( F# c: T+ ]$ B1 }* H" p
上面就是闭包,可理解为就是: 跨作用域访问函数内变量,也有说带着数据的行为。
1 }4 B# |: k  N# O* C* m& Wc#变量作用域一共有三种,即:类变量,实例变量,函数内变量。子作用域访问父作用域的变量(即函数内访问实例/类变量)在我们看来理所当然的,也符合我们一直的编程习惯。9 h5 \( \2 [) u
例子中匿名函数b是可以访问上层函数a的变量age。对于编译器而言,a函数是b函数的父作用域,所以b函数访问父作用域的age变量是符合规范的。4 U2 b6 V  c4 H
int age = 16;9 ^4 i9 a  Q( j5 N) v/ y4 ^) J% n
void display()
+ F% e& Z9 @2 ^% X' }# L{' q6 d0 f. i; G
console.writeline(age);
) f1 N$ b* \; o( Z' z, Tint age = 18;$ q7 M, K2 ~+ {
console.writeline(age);% d  R( K- i6 k5 Q3 l# y0 j; P
}
. j. N( o- j% c2 n: `- z% b上面编译会报错未声明使用,编译器检查到函数内声明age后,作用域就会覆盖父作用域的age,(像js就undefined了)。' d$ r6 \3 I  i! Y$ `
func<int> c = () =>
% S# P5 s9 k6 Q0 P( I1 B( s+ b/ M{% z3 N* i" r1 z  ^+ z
var age = 19;
$ t$ p3 g, _  o7 [3 xreturn age;
2 J& d5 x3 p( M8 G; [) V};
' Z/ e- V5 W& Q- t, C0 }8 D) {7 d: y7 i上面声明个同级函数c,那么a函数是无法访c函数中的age变量的。 简单来说就是不可跨作用域访问其他函数内的变量。 那编译器是怎么实现闭包机制的呢?: a9 D5 B/ b- q
如上图,答案是升级作用域,把a函数升级为一个实例类作用域。 在编译代码期间,编译器检查到b函数使用a函数内变量时,会自动生成一个匿名类x,把原a函数内变量age提升为x类的
( ?# K, j1 K0 w" a9 F" @字段(即实例变量),a函数提升为匿名类x的实例函数。下面是编译器生成的代码(精简过):) @8 R  _( X, Q" r, y
class program1
4 E' H$ B6 m' ?% q3 M8 P{% W3 X# ]6 Y7 ?- S5 J; Q/ B
static func<func<int>> cachedanonymousmethoddelegate2;; N# I. p( L' o5 A2 p6 z8 c
static void main(string[] args)
( H1 H$ _4 k- h+ J{
. E9 _" z$ A4 D. @; gfunc<func<int>> func = new func<func<int>>(program1.b);5 w5 O% o" i1 r# s
int num = func()();% L7 c  A  @' T& O1 R, w  x
}
' M5 {6 [- S  A8 L/ E( T8 Astatic func<int> b()
, o( @1 O9 M& \" ^' _5 a{& h. d6 g$ X. G/ f" y# t1 R# g' q* L
displayclass cl = new displayclass();, H% t/ Y" `! M2 r7 v/ ?0 p
cl.age = 18;
8 C- t5 N# M4 D' |* Qreturn new func<int>(cl.a);
8 L- c3 w  g9 s3 w) V  P}
) c8 A1 |; P% B1 c4 y1 V4 x4 L- U; [}
/ j8 q5 K* a4 Hsealed class displayclass
5 t# [# F( n4 d/ K# O% v{1 G2 a3 C% m% ]7 L
public int age;6 l1 @, J# X. H
public int a()- F, ^8 k4 H: d& q+ r$ Q
{
  K: W7 z) Y! t9 J9 ]; ^return this.age;
* n4 o$ d: H, V, w- V}5 `7 ~4 a7 ~' A/ {0 m- T
}5 F4 F; ]7 @" J2 Q. z
我们再来看个复杂点的例子:
  L6 g: {$ F: s* Cstatic func<int, int> getclosurefunction()
. T: a& {4 p4 t7 r1 W{8 w0 c' v7 s9 r' }
int val = 10;' P4 ]) F7 j0 E" S* Y6 p/ c
func<int, int> interadd = x => x + val;! u% L+ _, c1 @2 D1 o0 g% _. C" \
console.writeline(interadd(10));
" G* @6 ?- `- {8 r1 L: w# jval = 30;
+ Q: U) r% k7 b& o  i/ u& F; Oconsole.writeline(interadd(10));: [% A4 Q+ b! x4 J' \
return interadd;
. Y8 y& d8 \% `7 w4 h* ?}
. Q$ C" z& k3 X: k9 i) V- o/ O, O2 y0 cconsole.writeline(getclosurefunction()(30));
/ L( W% F8 n4 e( u$ R4 M: `- G输出结果是20、40、60。 当看到这个函数内变量val通过闭包被传递的时候,我们就知道val不仅仅是个函数内变量了。之前我们分析过编译器怎么生成的代码,知道val此时是一个匿名
0 J0 d1 U5 D- `" P4 q5 `8 [, n类的实例变量,interadd是匿名类的实例函数。所以无论val传递多少层,它的值始终保持着,直到离开这个(链式)作用域。
* C- W0 O8 {* P5 y+ g1 T  y$ O关于闭包,在js当中谈论的比较多,同理,可以对比理解下:
3 y2 ^4 v% _* A; y1 mfunction a() {9 G( V  H) O% H. U# |1 B8 T! S
var age = 18;
( _* c$ H) R9 x  Yreturn function () {8 Y+ {5 w! R+ M' e6 _' y1 p2 C
return age;4 p$ s3 g  x9 d8 E' y; ?# D2 x* g9 _$ o
}
+ f' h- O! e1 |}
2 {( [9 {' C  Y6 X  B% _8 Y" Ma()();1 B' x9 q& r, i, {
闭包的优点
9 l: g- ~) O$ n& D# R4 I1 O$ @# W对变量的保护。想暴露一个变量值,但又怕声明类或实例变量会被其他函数污染,这时就可以设计个闭包,只能通过函数调用来使用它。* I9 E2 j' E1 `5 E1 H% Z
逻辑连续性和变量保持。 a()是执行一部分逻辑,a()()仅接着a()逻辑继续走下去,在这个逻辑上下文期间,变量始终都被保持着,可以随意使用。+ D2 h# V% c- f9 ~1 n

. u+ n+ {- Z: s4 E, Y更多网页制作信息请查看: 网页制作

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