希望大家记住,这里讲的所有的知识点,不仅仅是了解了就可以了,还要会灵活用,一定要多思考,撑握其中的编程思想。

本文讲的是委托和事件,这两个词可能你早就耳熟能详,但你是否真正撑握了呢?

本系列讲的C#高级知识点都是要求开发时能达到可以徒手写出来的水平(不依赖搜索引擎、找笔记等)。建议开发时尽量自己写(时间允许的话),如果觉得自己写的不好,再Google。写多了就自然会灵活运用。

本文目录:

委托的简单使用

用委托实现插件式编程

多播委托

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

泛型委托

Func 和 Action 委托

委托的兼容

事件

事件的基本使用

事件的标准模式

结尾

委托太常见了,能灵活运用可以使你在编程中游刃有余。

简单说它就是一个能把方法当参数传递的对象,而且还知道怎么调用这个方法,同时也是粒度更小的“接口”(约束了指向方法的签名)。

委托的简单使用

一个委托类型定义了该类型的实例能调用的一类方法,这些方法含有同样的返回类型和同样参数(类型和个数相同)。委托和接口一样,可以定义在类的外部。如下定义了一个委托类型 - 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(); } }

用委托实现插件式编程

我们可以利用“委托是一个能把方法作为参数传递的对象”这一特点,来实现一种插件式编程。

例如,我们有一个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); ...

以后我们会经常写这样的代码。

多播委托

所有的委托实例都有多播的功能。所谓多播,就像一群程序员在瞬聘网填好了求职意向后,某天有个公司发布了一个和这些程序员求职意向刚好相匹配的工作,然后 这些求职者都被通知了 - “有一份好工作招人啦,你们可以直接申请去上班了!”。PS:为了公司,我也算满拼的。恳请大家允许我在博文中不忘提到瞬聘网。在后续博文中会有不少案例 确实是我在瞬聘网系统开发时使用过的。:)

也就是说,一个委托实例不仅可以指向一个方法,还可以指向多个方法。例如:

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

调用时,按照方法被添加的顺序依次执行。注意,对于委托,+= 和 -= 对null是不会报错的,如:

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 + "%");
}
}

运行结果:

看到这里,是不是发现你已然更加爱上C#了。

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

当一个类的实例的方法被赋给一个委托对象时,在上下文中不仅要维护这个方法,还要维护这个方法所在的实例。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 和 Action 委托

有了泛型委托,就有了一能适用于任何返回类型和任意参数(类型和合理的个数)的通用委托,Func 和 Action。如下所示(下面的in表示参数,out表示返回结果):

delegate TResult Func <out TResult> ();
delegate TResult Func <in T, out TResult> (T arg);
delegate TResult Func <in T1, in T2, out TResult> (T1 arg1, T2 arg2);
... 一直到 T16

delegate void Action ();
delegate void Action <in T> (T arg);
delegate void Action <in T1, in T2> (T1 arg1, T2 arg2);
... 一直到 T16

有了这样的通用委托,我们上面的Calculator泛型委托就可以删掉了,示例就可以更简洁了:

public static void Calculate<T>(T[] values, Func<T,T> c) {
for (int i = 0; i < values.Length; i++)
values[i] = c(values[i]);
}

Func 和 Action 委托,除了ref参数和out参数,基本上能适用于任何泛型委托的场景,非常好用。

委托的兼容

1. 委托的类型兼容

delegate void D1();
delegate void D2();
...
D1 d1 = Method1;
D2 d2 = d1;

下面是被允许的:

D2 d2 = newD2 (d1);

对于具体相同的目标方法的委托是被视为相等的:

delegate void D();
...
D d1 = Method1;
D d2 = Method1;
Console.WriteLine (d1 == d2); // True

同理,对于多播委托,如果含有相同的方法和相同的顺序,也被视为相等。

2. 参数类型兼容

在OOP中,任何使用父类的地方均可以用子类代替,这个OOP思想对委托的参数同样有效。如:

delegate void StringAction(string s);
class Program {
static void Main() {
StringAction sa = new StringAction(ActOnObject);
sa("hello");
}
static void ActOnObject(object o) {
Console.WriteLine(o); // hello

}
}

3. 返回值类型兼容

道理和参数类型兼容一样:

delegate object ObjectRetriever();
class Program {
static void Main() {
ObjectRetriever o = new ObjectRetriever(RetriveString);
object result = o();
Console.WriteLine(result); // hello
}
static string RetriveString() { return "hello"; }
}

当我们使用委托场景时,我们很希望有这样两个角色出现:广播者和订阅者。我们需要这两个角色来实现订阅和广播这种很常见的场景。

广播者这个角色应该有这样的功能:包括一个委托字段,通过调用委托来发出广播。而订阅者应该有这样的功能:可以通过调用 += 和 -= 来决定何时开始或停止订阅。

事件就是描述这种场景模式的一个词。事件是委托的一个子集,为了满足“广播/订阅”模式的需求而生。

事件的基本使用

声明一个事件很简单,只需在声明一个委托对象时加上event关键字就行。如下:

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 + " 元,快来抢!");
}
}

运行结果:

委托和事件的知识比较多,所以我单独写成一篇。由于委托和事件将来会经常用到(尤其是委托),所以建议大家一定要撑握,用到的时候能够自己写得出来。

有些人可能会比较急,希望我直接写ASP.NET MVC的示例。如果是这样,你永远成为不了大牛,将来开发的时候你只会Google找答案找例子。

http://www.cnblogs.com/willick/p/4172174.html

ASP.NET MVC4+EF5(Lambda/Linq)读取数据的更多相关文章

  1. [MVC4]ASP.NET MVC4+EF5(Lambda/Linq)读取数据

    继续上一节初始ASP.NET MVC4,继续深入学习,感受了一下微软的MVC4+EF5(EntityFramework5)框架的强大,能够高效的开发出网站应用开发系统,下面就看一下如何用MVC4+EF ...

  2. [转][MVC4]ASP.NET MVC4+EF5(Lambda/Linq)读取数据

    本文转自:https://blog.csdn.net/dingxiaowei2013/article/details/29405687 继续上一节初始ASP.NET MVC4,继续深入学习,感受了一下 ...

  3. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(33)-数据验证共享

    原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(33)-数据验证共享 注:本节阅读需要有MVC 自定义验证的基础,否则比较吃力 一直以来表单的验证都是不可 ...

  4. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(17)-LinQ动态排序

    原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(17)-LinQ动态排序 首先修复程序中的一个BUG这个BUG在GridPager类中,把sord修改为s ...

  5. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(18)-权限管理系统-表数据

    原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(18)-权限管理系统-表数据 这一节,我们插入数据来看看数据流,让各位同学,知道这个权限表交互是怎么一个流 ...

  6. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(48)-工作流设计-起草新申请

    原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(48)-工作流设计-起草新申请 系列目录 创建新表单之后,我们就可以起草申请了,申请按照严格的表单步骤和分 ...

  7. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(29)-T4模版

    原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(29)-T4模版 这讲适合所有的MVC程序 很荣幸,我们的系统有了体验的地址了.演示地址 之前我们发布了一 ...

  8. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(26)-权限管理系统-分配角色给用户

    原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(26)-权限管理系统-分配角色给用户 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x ...

  9. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(21)-权限管理系统-跑通整个系统

    原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(21)-权限管理系统-跑通整个系统 这一节我们来跑通整个系统,验证的流程,通过AOP切入方式,在访问方法之 ...

随机推荐

  1. c# 定时执行任务

    在Global.asax文件中加上 void Application_Start(object sender, EventArgs e) { // Code that runs on applicat ...

  2. iBATIS事务处理

    一:问题 最近发现了我们自己的项目的事务的处理根本就是行不通的,也因此我自己又去看了下有关事务的处理,算是有了个大致的了解吧,先说说我们最初的配置吧. 二:内容 (1):使用iBatis的事务管理 S ...

  3. [USACO06NOV] Roadblocks

    https://www.luogu.org/problem/show?pid=2865 题目描述 Bessie has moved to a small farm and sometimes enjo ...

  4. Struts2.X深入浅出 学习笔记

    第一节.MVC以及Struts2简介 第二节.Action生命周期以及接收表单数据 第三节.Struts2配置以及Struts.xml详解 Struts2 主要配置文件 Web.xml 设置过滤器以及 ...

  5. luaj luaoc 回调函数传递的一些小总结

    问题场景:我们的游戏在支付时,由于第三方支付比较费时,可能在支付的过程中,我们lua写的cocos2dx项目会断网,我们的游戏有自动重连的机制.我就想,如果断线好了以后,支付完成了,那在断网之前传入的 ...

  6. Linux系统网络基础知识及配置

    一:DNS(domain name system)简介 DNS(Domain Name System,域名系统),因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而 ...

  7. Spring Data JPA 的使用(山东数漫江湖)

    pring data jpa介绍 什么是JPA JPA(Java Persistence API)是Sun官方提出的Java持久化规范.它为Java开发人员提供了一种对象/关联映射工具来管理Java应 ...

  8. .NET Core Data Access

    .NET Core was released a few months ago, and data access libraries for most databases, both relation ...

  9. [bzoj3993][SDOI2015]星际战争-二分+最大流

    Brief Description 3333年,在银河系的某星球上,X军团和Y军团正在激烈地作战.在战斗的某一阶段,Y军团一共派遣了N个巨型机器人进攻X军团的阵地,其中第i个巨型机器人的装甲值为Ai. ...

  10. js 数组&字符串 去重

    Array.prototype.unique1 = function() { var n = []; //一个新的临时数组 for(var i = 0; i < this.length; i++ ...