在C#的世界里,委托是无处不在,尤其在.NET自己封装的框架里到处都有其身影,所以掌握委托就很有必要了!那什么是委托呢?其实委托就是一种数据类型,跟int等东东差不多,只不过在使用时要自己先去构建一个委托数据类型(不像int微软已为你准备好),然后声明一个委托类型的变量,并为其赋值,赋值的对象只能是方法,最后通过委托变量就可以进行方法调用了!

委托的简单使用

如下定义了一个委托类型 - Calculator:

delegate int Calculator (int x);

此委托适用于任何有着int返回类型和一个int类型参数的方法,如:

static int Double (int x) { return x * 2; }

创建一个委托实例,将该此方法赋值给该委托实例:

Calculator c = new Calculator(Double);

也可以简写成:

Calculator c = Double;

这个方法可以通过委托调用:

int result = c(2);

下面是完整代码:

delegate int Calculator(int x);

class Program {

    static int Double(int x) { return x * 2; }
static void Main(string[] args) {
Calculator c = Double;
int result = c(2); Console.Write(result);
Console.ReadKey();
}
}

一、多路委托

委托类实际继承于MulticastDelegate,这使委托对象支持多路委托,即委托对象可以绑定多个方法。当输入参数后,每个方法会“按顺序”执行!这里不建议委托方法有返回值,即使有返回值,多路委托中的方法也只能返回最后一个方法的返回值!

例如:

MyDelegate d = MyMethod1;
// “+=” 用来添加,同理“-=”用来移除。
d += MyMethod2;
// d -= MyMethod2

调用时,按照方法被添加的顺序依次执行。注意,对于委托,+= (实质是调用的Delegate的Combine方法)和 -= (实质是调用的Delegate的Remove方法)

MyDelegate d;
d += MyMethod1;// 相当于MyDelegate d = MyMethod1;

我们先定义一个委托(ProgressReporter),然后定义一个匹配方法(Match)来执行该委托中的所有方法。如下:

public delegate void ProgressReporter(int percentComplete);

public class Utility {
public static void Match(ProgressReporter p) {
if (p != null) {
for (int i = 0; i <= 10; i++) {
p(i * 10);
System.Threading.Thread.Sleep(100);
}
}
}
}

然后我们需要两个监视进度的方法,一个把进度写到Console,另一个把进度写到文件。如下:

class Program {
static void Main(string[] args) {
ProgressReporter p = WriteProgressToConsole;
p += WriteProgressToFile;
Utility.Match(p);
Console.WriteLine("Done.");
Console.ReadKey();
} static void WriteProgressToConsole(int percentComplete) {
Console.WriteLine(percentComplete+"%");
}
static void WriteProgressToFile(int percentComplete) {
System.IO.File.AppendAllText("progress.txt", percentComplete + "%");
}
}

运行结果:

注意:同一类型的委托变量可以相加减!

二、委托体现的插件式编程思想

其实就是把委托(方法)作为一种方法的参数来进行传递,由于同一委托可以接收多种不同实现的方法(插件),从而实现了一种插件式编程,实现动态的扩展。

例如,我们有一个Utility类,这个类实现一个通用方法(Calculate),用来执行任何有一个整型参数和整型返回值的方法。这样说有点抽象,下面来看一个例子:

delegate int Calculator(int x);

class Program {

    static int Double(int x) { return x * 2; }
static void Main(string[] args) {
int[] values = { 1,2,3,4};
Utility.Calculate(values, Double); foreach (int i in values)
Console.Write(i + " "); // 2 4 6 8 Console.ReadKey();
}
} class Utility {
public static void Calculate(int[] values, Calculator c) {
for (int i = 0; i < values.Length; i++)
values[i] = c(values[i]);
}
}

这个例子中的Utility是固定不变的,程序实现了整数的Double功能。我们可以把这个Double方法看作是一个插件,如果将来还要实现诸如求平方、求立方的计算,我们只需向程序中不断添加插件就可以了。

如果Double方法是临时的,只调用一次,若在整个程序中不会有第二次调用,那么我们可以在Main方法中更简洁更灵活的使用这种插件式编程,无需先定义方法,使用λ表达式即可,如:

...
Utility.Calculate(values, x => x * 2);
...

三、静态方法和实例方法对于委托的区别

当一个类的实例的方法被赋给一个委托对象时,在上下文中不仅要维护这个方法,还要维护这个方法所在的实例。System.Delegate 类的Target属性指向的就是这个实例。举个例子:

class Program {
static void Main(string[] args) {
X x = new X();
ProgressReporter p = x.InstanceProgress;
p(1);
Console.WriteLine(p.Target == x); // True
Console.WriteLine(p.Method); // Void InstanceProgress(Int32)
} static void WriteProgressToConsole(int percentComplete) {
Console.WriteLine(percentComplete+"%");
}
static void WriteProgressToFile(int percentComplete) {
System.IO.File.AppendAllText("progress.txt", percentComplete + "%");
}
} class X {
public void InstanceProgress(int percentComplete) {
// do something
}
}

但对于静态方法,System.Delegate 类的Target属性是Null,所以将静态方法赋值给委托时性能更优。

四、泛型委托

如果你知道泛型,那么就很容易理解泛型委托,说白了就是含有泛型参数的委托,例如:

public delegate T Calculator<T> (T arg);

我们可以把前面的例子改成泛型的例子,如下:

public delegate T Calculator<T>(T arg);

class Program {

    static int Double(int x) { return x * 2; }
static void Main(string[] args) {
int[] values = { 1, 2, 3, 4 };
Utility.Calculate(values, Double); foreach (int i in values)
Console.Write(i + " "); // 2 4 6 8 Console.ReadKey();
}
} class Utility {
public static void Calculate<T>(T[] values, Calculator<T> c) {
for (int i = 0; i < values.Length; i++)
values[i] = c(values[i]);
}
}

Func委托

委托 Func 支持 0~16 个参数,Func 必须具有返回值

public delegate TResult Func<TResult>()
public delegate TResult Func<T1,TResult>(T1 obj1)
public delegate TResult Func<T1,T2,TResult>(T1 obj1,T2 obj2)
public delegate TResult Func<T1,T2,T3,TResult>(T1 obj1,T2 obj2,T3 obj3)
............
public delegate TResult Func<T1,T2,T3,......,T16,TResult>(T1 obj1,T2 obj2,T3 obj3,......,T16 obj16)

static void Main(string[] args)
{
Func<double, bool, double> func = Account;
double result=func(, true);
Console.WriteLine("Result is : "+result);
Console.ReadKey();
} static double Account(double a,bool condition)
{
if (condition)
return a * 1.5;
else
return a * ;
}

Action委托

Action<T> 的返回值为 void,也就是没有返回值!
Action 支持0~16个参数,可以按需求任意使用。

public delegate void Action()
public delegate void Action<T1>(T1 obj1)
public delegate void Action<T1,T2> (T1 obj1, T2 obj2)
public delegate void Action<T1,T2,T3> (T1 obj1, T2 obj2,T3 obj3)
............
public delegate void Action<T1,T2,T3,......,T16> (T1 obj1, T2 obj2,T3 obj3,......,T16 obj16)

static void Main(string[] args)
{
Action<string> action=ShowMessage;
action("Hello World");
Console.ReadKey();
} static void ShowMessage(string message)
{
MessageBox.Show(message);
}

Predicate<T>委托

Predicate只有一个参数,且返回值总是为bool类型!

public delegate bool Predicate<T>(T obj)

internal class PredicateDelegate
{
public bool PredicateMethod(int x )
{
return x > ;
}
}
PredicateDelegate predicateDelegate = new PredicateDelegate();
// 只有一个参数 并返回bool 值
Predicate<int> predicate = predicateDelegate.PredicateMethod;
bool results =predicate();
Console.WriteLine(results);

Comparison委托

public delegate int Comparison<in T>(T x, T y)

为返回int类型的内置委托。T是要比较的对象的类型,而返回值是一个有符号整数,指示 x 与 y 的相对值,如下表所示:

含义

小于 0

x 小于 y。

0

x 等于 y。

大于 0

x 大于 y。

  此委托由 Array 类的 Sort<T>(T[], Comparison<T>) 方法重载和 List<T> 类的 Sort(Comparison<T>) 方法重载使用,用于对数组或列表中的元素进行排序

五、匿名方法和Lambda表达式

匿名方法

如果某个委托变量需要绑定的方法只用一次的话,其实是没有必要为方法起名的,直接用匿名方法会比较好!我们总是使用 delegate(){......} 的方式建立匿名方法!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace DelegateSamples
{
//声明一个委托,参数为string,无返回值
delegate void DelSamples(string msg);
class Program
{
static void Main(string[] args)
{
//匿名委托
DelSamples delSample4 = delegate(string msg)
{
Console.WriteLine("你好,我是{0}", msg);
};
delSample4("KoalaStudio"); //利用Lambda表达式实现匿名委托
DelSamples delSample5 = (string msg) => {
Console.WriteLine("你好,我是{0}", msg);
};
delSample5("KoalaStudio"); Console.ReadKey();
} }
}

Lambda表达式

匿名方法的优雅写法就是Lambda表达式!

注意事项:

①Lambda表达式中的参数列表(参数数量、类型和位置)必须与委托相匹配

②表达式中的参数列表不一定需要包含类型,除非委托有ref或out关键字(此时必须显示声明);

如果没有参数,必须使用一组空的圆括号

static void Main(string[] args)
{
Action<int> action = (x) =>
{
x = x + ;
Console.WriteLine("Result is : " + x);
};
action.Invoke();
Console.ReadKey();
}

六、异步委托

public delegate int Ad(int x,int y);
static void Main(string[] args)
{
xu xus = new xu();
Ad a = new Ad(Add);
Console.WriteLine(a(, ));
Console.WriteLine("start");
Console.ReadLine();
}
static int Add(int x, int y)
{
Thread.Sleep();
return x + y;
}

运行这段代码 会先停顿2秒钟之后再显示6 和start 因为我使用了sleep这个方法 它使该线程休眠2秒钟,所以会在2秒之后显示信息,但是这对用户体验来说是非常糟糕的,那我们怎么改善呢?看看如下代码

public delegate int Ad(int x,int y);
static void Main(string[] args)
{
xu xus = new xu();
Ad a = new Ad(Add);
Console.WriteLine(a(, ));
// Console.WriteLine("start");
IAsyncResult isa= a.BeginInvoke(, , null, null);
while (!isa.IsCompleted)
{
Console.WriteLine("未完成");
}
int s= a.EndInvoke(isa);
Console.WriteLine(s.ToString());
Console.ReadLine();
}
static int Add(int x, int y)
{
Thread.Sleep();
return x + y;
}
static int ex(int x, int y)
{
//Thread.Sleep(5000);
return x - y;
}

这里我们使用了begininvoke方法来异步执行 委托方法返回一个IAsyncResult 类型的值 代表委托执行的状态,使用一个while循环 来判断IsCompleted 如果没有完成异步调用则不断显示“未完成” 如果完成endinvoke 则返回结果。但是这里需要不断的询问操作完成状态 那么我们怎样让委托异步调用完成之后主动通知我们呢? 看看如下代码

public delegate int Ad(int x,int y);
static void Main(string[] args)
{
xu xus = new xu();
Ad a = new Ad(Add);
Console.WriteLine(a(, ));
IAsyncResult isa= a.BeginInvoke(, , new AsyncCallback(call), "edit by xyl");
//执行你想执行的代码 这里我们还是用IsCompleted来代替
while (!isa.IsCompleted)
{
Console.WriteLine("未完成");
}
Console.ReadLine();
}
static void call(IAsyncResult isa)
{
AsyncResult ar = (AsyncResult)isa;
Ad a = (Ad)ar.AsyncDelegate;
Console.WriteLine("this is {0},{1}",a.EndInvoke(isa),ar.AsyncState);
} static int Add(int x, int y)
{
Thread.Sleep();
return x + y;
}
static int ex(int x, int y)
{
//Thread.Sleep(5000);
return x - y;
}
}

这里我们使用了一个call方法 注意它是没有返回值的。把IAsyncResult转换成AsyncResult注意少了个I然后转换成AD 类型的委托 最后endinvoke 来返回值 这样在委托异步执行完成之后会自动通知方法。

注意:回调函数是在 ThreadPool线程上进行的,因此主线程将继续执行。ThreadPool线程是后台线程,这些线程不会在主线程结束后保持应用程序的运行,因此主线程必须休眠足够长的时间以便回调完成。我们也可以在主线程完成操作后调用IsCompleted属性判断委托函数是否完成。

七、事件

事件其实就是某种类型的委托,在给事件赋值时,用符合该委托的方法就行!

public delegate void PriceChangedHandler (decimal oldPrice, decimal newPrice);
public class IPhone6
{
public event PriceChangedHandler PriceChanged;
}

事件的使用和委托完全一样,只是多了些约束。下面是一个简单的事件使用例子:

public delegate void PriceChangedHandler(decimal oldPrice, decimal newPrice);

public class IPhone6 {
decimal price;
public event PriceChangedHandler PriceChanged;
public decimal Price {
get { return price; }
set {
if (price == value) return;
decimal oldPrice = price;
price = value;
// 如果调用列表不为空,则触发。
if (PriceChanged != null)
PriceChanged(oldPrice, price);
}
}
} class Program {
static void Main() {
IPhone6 iphone6 = new IPhone6() { Price = 5288 };
// 订阅事件
iphone6.PriceChanged += iphone6_PriceChanged; // 调整价格(事件发生)
iphone6.Price = 3999; Console.ReadKey();
} static void iphone6_PriceChanged(decimal oldPrice, decimal price) {
Console.WriteLine("年终大促销,iPhone 6 只卖 " + price + " 元, 原价 " + oldPrice + " 元,快来抢!");
}
}

运行结果:

有人可能会问,如果把上面的event关键字拿掉,结果不是一样的吗,到底有何不同?

没错可以用事件的地方就一定可以用委托代替。

但事件有一系列规则和约束用以保证程序的安全可控,事件只有 += 和 -= 操作,这样订阅者只能有订阅或取消订阅操作,没有权限执行其它操作。如果是委托,那么订阅者就可以使用 = 来对委托对象重新赋值(其它订阅者全部被取消订阅),甚至将其设置为null,甚至订阅者还可以直接调用委托,这些都是很危险的操作,广播者就失去了独享控制权。

事件保证了程序的安全性和健壮性。

事件的标准模式

.NET 框架为事件编程定义了一个标准模式。设定这个标准是为了让.NET框架和用户代码保持一致。System.EventArgs是标准模式的核心,它是一个没有任何成员,用于传递事件参数的基类。
按照标准模式,我们对于上面的iPhone6示例进行重写。首先定义EventArgs:

public class PriceChangedEventArgs : EventArgs {
public readonly decimal OldPrice;
public readonly decimal NewPrice;
public PriceChangedEventArgs(decimal oldPrice, decimal newPrice) {
OldPrice = oldPrice;
NewPrice = newPrice;
}
}

然后为事件定义委托,必须满足以下条件:

  • 必须是 void 返回类型;
  • 必须有两个参数,且第一个是object类型,第二个是EventArgs类型(的子类);
  • 它的名称必须以EventHandler结尾。

由于考虑到每个事件都要定义自己的委托很麻烦,.NET 框架为我们预定义好一个通用委托System.EventHandler<TEventArgs>:

public delegate void EventHandler<TEventArgs> (object source, TEventArgs e) where TEventArgs : EventArgs;

如果不使用框架的EventHandler<TEventArgs>,我们需要自己定义一个:

public delegate void PriceChangedEventHandler (object sender, PriceChangedEventArgs e);

如果不需要参数,可以直接使用EventHandler(不需要<TEventArgs>)。有了EventHandler<TEventArgs>,我们就可以这样定义示例中的事件:

public class IPhone6 {
...
public event EventHandler<PriceChangedEventArgs> PriceChanged;
...
}

最后,事件标准模式还需要写一个受保护的虚方法来触发事件,这个方法必须以On为前缀,加上事件名(PriceChanged),还要接受一个EventArgs参数,如下:

public class IPhone6 {
...
public event EventHandler<PriceChangedEventArgs> PriceChanged;
protected virtual void OnPriceChanged(PriceChangedEventArgs e) {
if (PriceChanged != null) PriceChanged(this, e);
}
...
}

下面给出完整示例:

public class PriceChangedEventArgs : System.EventArgs {
public readonly decimal OldPrice;
public readonly decimal NewPrice;
public PriceChangedEventArgs(decimal oldPrice, decimal newPrice) {
OldPrice = oldPrice;
NewPrice = newPrice;
}
} public class IPhone6 {
decimal price;
public event EventHandler<PriceChangedEventArgs> PriceChanged; protected virtual void OnPriceChanged(PriceChangedEventArgs e) {
if (PriceChanged != null) PriceChanged(this, e);
} public decimal Price {
get { return price; }
set {
if (price == value) return;
decimal oldPrice = price;
price = value;
// 如果调用列表不为空,则触发。
if (PriceChanged != null)
OnPriceChanged(new PriceChangedEventArgs(oldPrice, price));
}
}
} class Program {
static void Main() {
IPhone6 iphone6 = new IPhone6() { Price = 5288M };
// 订阅事件
iphone6.PriceChanged +=iphone6_PriceChanged; // 调整价格(事件发生)
iphone6.Price = 3999;
Console.ReadKey();
} static void iphone6_PriceChanged(object sender, PriceChangedEventArgs e) {
Console.WriteLine("年终大促销,iPhone 6 只卖 " + e.NewPrice + " 元, 原价 " + e.OldPrice + " 元,快来抢!");
}
}

运行结果:

C#秘密武器之委托的更多相关文章

  1. TypeScript: Angular 2 的秘密武器(译)

    本文整理自Dan Wahlin在ng-conf上的talk.原视频地址: https://www.youtube.com/watch?v=e3djIqAGqZo 开场白 开场白主要分为三部分: 感谢了 ...

  2. 第一章-第七题( 有人认为,“中文编程”, 是解决中国程序员编程效率一个秘密武器,请问它是一个 “银弹” 么? )--By 侯伟婷

    首先,“银弹”在百度百科中的解释是银色的子弹,我们更熟知的“银弹”一词,应该是在<人月神话>中提到的.银弹原本应该是指某种策略.技术或者技巧可以极大地提高程序员的生产力[1].此题目中关于 ...

  3. margin负值 – 一个秘密武器

    CSS盒模型中,margin是我们老熟悉的一个属性了, 它的负值你用过吗? 你知道 margin负值的秘密武器吗?我们一起看看吧! 1.带竖线分隔的横向列表(例如:网站底部栏目) 传统的分隔符是使用 ...

  4. C#秘密武器之扩展方法

    原文:C#秘密武器之扩展方法 为何要用扩展方法? 作为一个.NET程序猿,我们经常要跟.net自带类库或者第三方dll类库打交道,有时候我们未必能够通过反编译来查看它们的代码,但是我们通常需要给它们扩 ...

  5. 团队高效率协作开发的秘密武器-APIDOC

    团队高效率协作开发的秘密武器 1.前言 在团队协作开发中,不知道各位有没有遇到这样的问题: l 新人接手了项目代码,因没有项目文档,只能靠追踪路由,寻读代码分析业务逻辑 l 前端同学写好了页面,苦等后 ...

  6. 当3D打影人头”成为黑客的秘密武器,隐私该如何保护?

    在<碟中谍>系列电影中,除了超级敬业又帅气的阿汤哥之外,最让人津津乐道的桥段就是用3D打印做出来的"人头".通过这些惟妙惟肖的"人头",阿汤哥完成了 ...

  7. C#秘密武器之多线程——参数与返回值

    线程函数要么没有参数,要么只能有一个object参数,而且均没有返回值,这样就大大降低了程序的灵活性,其实我们想要的是能像普通方法一样正常使用参数和返回值!能不能实现这个需求呢?下面就介绍两种方法 一 ...

  8. C#秘密武器之多线程——基础

    多线程概述 什么是进程? 当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源.而一个进程又是由多个线程所组成的. 什么是线程? 线程是程序中的一个执行流,每个线程 ...

  9. C#秘密武器之异步编程

    一.概述 1.什么是异步? 异步操作通常用于执行完成时间可能较长的任务,如打开大文件.连接远程计算机或查询数据库.异步操作在主应用程序线程以外的线程中执行.应用程序调用方法异步执行某个操作时,应用程序 ...

随机推荐

  1. 使用mongo shell和客户端连接至MongoDB Atlas

    MongoDB Atlas是Mongo官方的一个集群服务,也可以注册并创建一个免费的集群,但DB的大小只有500M,如果数据量不是很大的应用,可以选择该集群方案 需要注意的是,目前我使用的这个集群,服 ...

  2. Sd - 数据库开发调优

    尤其是Sql写法上的技巧,以及常见Sql的写法

  3. HDU 6060 RXD and dividing(LCA)

    [题目链接] http://acm.hdu.edu.cn/showproblem.php?pid=6060 [题目大意] 给一个n个节点的树,要求将2-n号节点分成k部分, 然后将每一部分加上节点1, ...

  4. 【二分图】【并查集】XVII Open Cup named after E.V. Pankratiev Stage 14, Grand Prix of Tatarstan, Sunday, April 2, 2017 Problem L. Canonical duel

    给你一个网格(n<=2000,m<=2000),有一些炸弹,你可以选择一个空的位置,再放一个炸弹并将其引爆,一个炸弹爆炸后,其所在行和列的所有炸弹都会爆炸,连锁反应. 问你所能引爆的最多炸 ...

  5. 【最大团转最大点独立集(匈牙利算法+时间戳优化)】BZOJ2744-[HEOI2012]朋友圈

    [题目大意] 有两个国家A和B.存在以下朋友关系: 1.A国:每个人都有一个友善值,当两个A国人的友善值a.b,如果a xor b mod 2=1,那么这两个人都是朋友,否则不是: 2.B国:每个人都 ...

  6. Activity(活动)的启动模式

    在实际项目中我们应该根据特定的需求为每个活动指定相应的启动模式.启动模式一共分为4种:standar.singleTop.singleTask和singleInstance.可以在AndroidMan ...

  7. Java如何进行异常处理

    关键字:throws.throw.try.catch.finally的用法 Java通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接口.在Java中,每个异常都是一个对象,它 ...

  8. 利用Visual Studio Natvis 框架简化C++的变量调试工作

    相信用C++开发过UI界面的程序员都对其变量调试工作头痛不已,由于复杂的继承关系,要查看到某个变量往往需要一系列的层层深入的点击,如下图就是查看TextBox的Text的例子: 为了查看Text属性, ...

  9. .Net 有关程序集查找与加载的一点反思

    最近在做一款叫VICA产品,此产品采用了插件机制,插件在运行中加载,插件与插件之间存在依赖关系,所有的插件DLL为方便管理都放置在Plugins的文件夹下统一管理.这种处理方式不自觉的就让我想了解cl ...

  10. 配置安全证书的Etcd集群

    不知在哪篇技术文档中看到,kubernetes master和etcd分开部署模式,因为集群的状态都保存在etcd中,这样当kubernetes master挂掉后,通过API Server交互的Sc ...