怎样理解C#中的委托代理机制
很多书和文章都希望能够从原理层面把代理机制讲清楚,从C和C++的指针讲到线程安全,从通用类库讲到事件驱动,对于高端玩家,这是很有必要的,但对于初学者,就容易越看越晕,从云山雾罩,到云深不知处,本来只有一个概念不懂,现在变成N个了,最后只能投降,不了了之。
这个概念确实不太容易理解。下面尝试根据我的理解来说明。
我们在开发期间最常用的方法所传递的参数,一般情况下是某个类型的变量,比如:
public long op(long a,long b) { return a + b; }
现在我给你出个题,你帮我向方法A中传递一个方法B,并在A中执行B。
是不是不会了?
对部分开发者来讲,这确实是一个新的概念,或者说是一个新的操作方式,以前没这样做过。
聪明的你可能已经想到了,对,在C#中,此时要用到代理。
按照方法签名的定义方式,要传递的参数应该是“某个类型 形参”,比如上例中的long a。那么要传递的这个方法的“类型”是什么呢?
就是代理,delegate。
当你要向一个方法A传递另一个方法B的时候,要为这个B方法单独定义一个“类型”,它要求的有三个要素,参数类型、个数、返回值,只要符合这三项的,都可以传。
比如要向方法A传递上例中的op(long a,long b)方法,就要像下面这样定义:
public delegate long TwoLongsOp(long a, long b);
这时,一个新的“类型”就产生了,所有符合“两个long型参数、返回值为long”特点的方法,都符合这个类型定义。
现在就可以解答我上面出的题了,使用这个类型向方法A传递方法B。
首先要有方法A:
public long Atest(TwoLongsOp a) { return 0; }
显而易见,这个方法什么也没做,但它可以传一个代理了,离成功近了一点儿。
下一步开始调用传过来的参数a,要注意,这个参数是个带有两个参数和返回值的方法,调用它也就是执行它。
怎么调用呢?
就像在方法A中调用其它方法一样。
public long Atest(TwoLongsOp a) { var r = a(2,3); return r; }
到这里,程序是可以编译的,符合编译规则,但没什么用。因为我们还没有定义方法B,也就是方法A的那个“实参”。
下面我们来定义方法B,方法B一定要符合delegate所定义的格式,参数、返回值,一定要一致:
public long Btest(long a,long b) { //其实这里写点什么都行,不一定非得是对a和b的操作,只是对入参进行操作是对它们最大的尊重。 return a + b; }
接下来就可以正式调用了,当当当当~~向方法A中传入方法B:
public ActionResult Index() { ViewBag.Message = Atest(Btest).ToString(); return View(); }
我们再定义一个C:
public long Ctest(long a, long b) { return a * b; }
接下来可以这样调用:
public ActionResult Index() { ViewBag.Message1 = Atest(Btest).ToString(); ViewBag.Message2 = Atest(Ctest).ToString(); return View(); }
有一点思考一下,代理与方法B和方法C,到底是B和C被代理限制,还是代理为B和C服务?
想必有人会问,这样操作的意义是什么,直接调用Btest不就可以了?
对,在本例中,是的。但在很多团队合作的情况下,A不是你写的,甚至不是你们公司写的,暴露给你的只是一个方法名,要求传入符合方法签名要求的方法。或者你是写A的人,要给写B的人留下余量。
“委托”或者“代理”一词应该怎样理解呢?
从方法的角度来讲,对于参数,它只认识“类型 参数”的模式,所以,方法的“类型”是什么呢?它没有内置的类型,所以无法直接传递。既然不能直接向A传递B,就需要一个“中间人”,定义一个新的“类型”,A和B委托deleate定义的那个东西作为媒介,把B传给A。这个新定义的类型,服从A和B的约定(参数类型、个数、返回值)。所以,委托的定义是为A、B、C服务,它们有需求,所以需要一个合格的中间人。
有些事情只能是在开发过程中遇到了才会意识到它的作用和价值,在遇到之前,不太容易理解。本例权当是一次智力游戏。
念念不忘,必有回响,诚不我欺,共勉。