返回列表 发新帖

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

480.7k 9
swmozowtfl 发表于 2015-7-10 20:46:02|湖北 | 查看全部 阅读模式
阅读目录:9 a) o/ x! F* x2 `/ R5 T6 z
动态创建函数& B! ~" w' O  E$ m1 X5 B+ s" [
匿名函数不足之处
# `0 F1 L# j) k, K* k理解c#中的闭包& @- T# t, ~- V! h3 W+ M4 k
闭包的优点+ q3 P. P3 z# |+ a8 ^7 e) ^0 Z
动态创建函数
  Y' G- k/ F7 u4 {6 w  D) C大多数同学,都或多或少的使用过。回顾下c#中动态创建函数的进化:
! [3 ~4 I/ J: }, f/ k6 X* Fc# 1.0中:
4 v/ k$ S: x* d7 h4 L# \- K8 }3 {public delegate string dynamicfunction(string name);
7 l0 P  k$ s: [" i9 [public static dynamicfunction getdynamicfunction()% r5 a0 i% ^, H! v
{
8 b. w. l) `1 \- Sreturn getname;: |/ F4 Y9 K2 {( ~) M( U
}
" G8 n4 V: i' wstatic string getname(string name)
- W4 ?" z9 E% f1 W{; F' Y! E* R0 Z3 o. R
return name;
: c9 C6 E' l/ {8 d}, \3 K1 z% A) S- p; M; X" M
var result = getdynamicfunction()(mushroom);; w* @) }( b! n$ J- f) Q) J
3.0写惯了是不是看起来很繁琐、落后。 刚学委托时,都把委托理解成函数指针,也来看下用函数指针实现的:
% b9 @0 n# z' o! G4 p3 Hchar getname(char p);; e' ^: c+ U0 D
typedef char (*dynamicfunction)(char p);* q3 U2 X+ N: P1 x- n
dynamicfunction getdynamicfunction()0 O( C, u( o' Z/ Q0 T+ J  G, q
{
" i" o8 E" c; \& X# ^. T- ureturn getname;
) h4 B! d# `7 M5 M) a/ r" g}: Z5 s2 q5 y- W) o$ m# g3 Z! C, L
char getname(char p)7 _8 H" J( R* [' Z
{& k- A4 P) q2 A# Y7 x8 s# o
return p;
0 y6 @& U5 D: x  j( }$ h  W};, Z% F5 n! U! |1 o
char result = getdynamicfunction()('m');
9 p% B3 s. w+ ^- C+ d" }* J对比起来和c# 1.0几乎一模一样了(引用/指针差别),毕竟是同一家族的。; ?; X% I; y- w
c# 2.0中,增加匿名函数:
- `% A8 I: f, E; }/ tpublic delegate string dynamicfunction(string name);, l2 P( V6 d( b' m
dynamicfunction result2 = delegate(string name)$ z% U8 {4 F$ y6 V3 K/ c5 R0 Y( a3 V
{3 G6 g/ t: f% ?
return name;
; k( v0 o5 [3 U! b/ H};
5 n" [% o. N8 c) o7 g7 rc# 3.0中,增加lambda表达式,华丽的转身:
! L1 @( m- o8 _; @  rpublic static func<string, string> getdynamicfunction()# ~( S! O" O% ^; L& S
{; r2 [9 P4 q# Z
return name => name;
- j6 V4 o  m. Y8 f. I}% ~0 A1 j& o- p% ~
var result = getdynamicfunction()(mushroom);
# \! g/ j* B* z$ s' K匿名函数不足之处1 Q$ J) h" K) F  A
虽然增加lambda表达式,已经极大简化了我们的工作量。但确实有些不足之处:* C) @# H# O1 k# P
var result = name => name;
8 A% y, ~* m. K' n% E/ `这些写编译时是报错的。因为c#本身强类型语言的,提供var语法糖只是为了省去声明确定类型的工作量。 编译器在编译时必须能够完全推断出各参数的类型才行。代码中的name参数类4 M' g7 E2 |/ t+ X% Y) d6 o8 h# N
型,显然在编译时无法推断出来的。0 S3 h9 h9 W7 T; t. L
var result = (string name) => name;# U- u8 m! k4 E7 t- `5 B/ y
func<string, string> result2 = (string name) => name;
8 k" H4 Z8 }: S! Eexpression<func<string, string>> result3 = (string name) => name;
$ L! @& @1 X% r! G' j( l- R6 h上面直接声明name类型呢,很遗憾这样也是报错的。代码中已经给出答案了,编译器推断不出右边表达式是属于func<string, string>类型还是expression<func<string, string>>类型, Y* @1 U6 J6 N

: P& Q; m, C' Q: @4 ^dynamic result = name => name;* X6 N: f, e6 p* P2 @
dynamic result1 = (func<string,string>)(name => name);
0 i3 g* B6 ]+ Y* c用dynamic呢,同样编译器也分不出右边是个委托,我们显示转换下就可以了。+ L% K: X0 T# s& a! L; P
func<string, string> function = name => name;
- h' x" Q/ H& b: t) j% Sdynamicfunction df = function;7 J5 J+ T. O1 k% `8 `
这里定义个func委托,虽然参数和返回值类型都和dynamicfunction委托一样,但编译时还是会报错:不能隐式转换func<string, string>到dynamicfunction,2个类型是不兼容的。
$ ^0 k; |. F! T: |理解c#中的闭包
" i) l* F2 B) v' ]: X谈论到动态创建函数,都要牵扯到闭包。闭包这个概念资料很多了,理论部分这里就不重复了。 来看看c#代码中闭包:! R; s) T6 H7 C, W9 ?
func<func<int>> a = () =>3 Y! z  Z1 J; X! i, A
{
6 R1 T5 ?; \, ?. \2 J$ evar age = 18;
% a% r" a7 I& W  m, freturn () =>  //b函数
3 X4 M/ w4 v% s% R) M5 N{
1 y8 W; ?7 s8 k* lreturn age;
; {! Y+ B0 J7 D, b};' r9 o* ~" U' F& K9 o; W% _8 e
};
3 k  t! n  Y, q4 dvar result = a()();
( A& I9 V# z5 M上面就是闭包,可理解为就是: 跨作用域访问函数内变量,也有说带着数据的行为。
5 i* }) v( |: o- _5 ?+ @# b) zc#变量作用域一共有三种,即:类变量,实例变量,函数内变量。子作用域访问父作用域的变量(即函数内访问实例/类变量)在我们看来理所当然的,也符合我们一直的编程习惯。
$ Z* \. z9 H5 w( ~  Z; S例子中匿名函数b是可以访问上层函数a的变量age。对于编译器而言,a函数是b函数的父作用域,所以b函数访问父作用域的age变量是符合规范的。% {5 m* `3 w2 [3 b* c/ L% w3 A5 j* k
int age = 16;& {8 a; b8 k3 \: f9 ?( q
void display()6 O) z1 d" l2 y( f
{
) ?, R/ t1 m' h( x# ]2 W4 Z* Xconsole.writeline(age);2 I2 j1 l) M# g
int age = 18;
1 ]" i& W8 }  T, {- p$ M# Zconsole.writeline(age);0 c8 D+ h( K0 m2 b6 I% H* j3 P* \% Q+ o, ?4 f
}- E, _; p$ e  j" p. F% e, s$ e
上面编译会报错未声明使用,编译器检查到函数内声明age后,作用域就会覆盖父作用域的age,(像js就undefined了)。9 I6 y0 Y7 R  F* B- x! q* v5 T# B' G
func<int> c = () =>2 F6 F0 W: ]0 @8 u
{4 r! J6 Z! }0 p3 F2 ]0 y
var age = 19;
  E6 p, C- y' x0 i# ~/ }return age;
+ ~1 c+ W! P- `4 x2 ~};
" M8 d: h2 Y% A& a上面声明个同级函数c,那么a函数是无法访c函数中的age变量的。 简单来说就是不可跨作用域访问其他函数内的变量。 那编译器是怎么实现闭包机制的呢?5 ?: t! ]% P( t
如上图,答案是升级作用域,把a函数升级为一个实例类作用域。 在编译代码期间,编译器检查到b函数使用a函数内变量时,会自动生成一个匿名类x,把原a函数内变量age提升为x类的
9 d% S5 A9 O/ l7 r) g字段(即实例变量),a函数提升为匿名类x的实例函数。下面是编译器生成的代码(精简过):0 k4 P; ^- J! e
class program1
) _! M: Z: l" `9 Q, D{! o* l, R/ [9 @2 U/ o8 `
static func<func<int>> cachedanonymousmethoddelegate2;
2 f: E0 t$ c- Cstatic void main(string[] args)) [9 Q- t* b9 Z5 h* d
{
: @: |. h! ?3 M0 ]! H3 }0 i) yfunc<func<int>> func = new func<func<int>>(program1.b);
. G' L) }* r& o% zint num = func()();+ g9 `0 V) }0 ?* a3 Q
}9 Y- Q; n* F' a
static func<int> b()* ?. W, W3 s2 d0 q$ l& ?
{. y4 z$ p! I: Z6 J; R
displayclass cl = new displayclass();
, B. b* @$ [% l, _8 kcl.age = 18;
- Z! [# o) V, A( H! u' G. oreturn new func<int>(cl.a);; V. i  T4 a: Y- @3 u6 m
}8 Y7 x& L& t2 Q& v' M. `
}
! n$ _; F! ?6 |2 Fsealed class displayclass
4 l7 b& T: T7 S" \5 K{' H1 d0 n) Y! i2 }! u1 a8 Q! {& j
public int age;9 U1 m3 C) i# q3 M) G
public int a()- K$ k6 ^# x: L7 |
{
2 h3 x/ c1 C8 y0 T' e0 hreturn this.age;
, Z5 Y; _, H4 e$ Y  ^' E( J}! ^6 W: x* G. c! Y
}4 G! R% I" U' f
我们再来看个复杂点的例子:4 ~& r+ N% c2 M- P7 U6 m; [
static func<int, int> getclosurefunction(): l. B5 j* m/ P9 J, p
{+ ~  X+ J, c& [# j* ^5 l& g
int val = 10;
2 E# J2 B# ~. q( V/ P9 [- r9 Hfunc<int, int> interadd = x => x + val;
6 I! C0 `* N8 n4 Gconsole.writeline(interadd(10));
. F! q" `5 k4 W# u8 Vval = 30;6 R+ q; Y7 ]6 ^! Q+ _
console.writeline(interadd(10));: S/ |6 U* g! b
return interadd;
5 E+ j) m( z9 t. }. i6 a3 }, y}
7 D7 Z' |# a* h- P7 j3 a2 \console.writeline(getclosurefunction()(30));7 V( `2 e7 H' ~# s
输出结果是20、40、60。 当看到这个函数内变量val通过闭包被传递的时候,我们就知道val不仅仅是个函数内变量了。之前我们分析过编译器怎么生成的代码,知道val此时是一个匿名: k3 q5 J9 Y* P) C" C; o, Z
类的实例变量,interadd是匿名类的实例函数。所以无论val传递多少层,它的值始终保持着,直到离开这个(链式)作用域。
" N. Z1 l  Y; N' w5 u- P# t9 i关于闭包,在js当中谈论的比较多,同理,可以对比理解下:. @% E+ s& V8 W( r$ U7 Z' X. I% q" r
function a() {
* G0 ~0 S5 m4 M2 `var age = 18;8 n  P% B6 i7 `; p' y* J) j+ Y
return function () {1 P( z2 a4 P7 @6 B. ~" t/ I
return age;! [- ^0 i0 W6 s- \+ K& m( f2 l% D
}5 |4 n! J# r9 B
}
# r4 H7 C$ {; Qa()();
; S( }5 U2 |7 l; W* L: \0 s9 _7 f3 u闭包的优点
$ i. J$ }+ K1 D$ I) i7 {对变量的保护。想暴露一个变量值,但又怕声明类或实例变量会被其他函数污染,这时就可以设计个闭包,只能通过函数调用来使用它。
0 Y! E! `2 f6 E: E6 e7 ~逻辑连续性和变量保持。 a()是执行一部分逻辑,a()()仅接着a()逻辑继续走下去,在这个逻辑上下文期间,变量始终都被保持着,可以随意使用。. a0 H" o- m. j$ U
, @2 [: t! l/ p% ~; H$ c
更多网页制作信息请查看: 网页制作

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