说说委托那些事儿

委托基础

委托是个啥?

很多人第一反映可能是"函数指针",个人觉得"函数指针"是委托实例

委托的定义类似interface,是一种方法的"规范"或者说"模版",用来规范方法的"行为",以便将方法作为参数传递

public delegate void MyDelegate();

这样便定义了一个无参无返回值的委托,要求此委托的实例必须是无参无返回值的方法

public class MyClass
{
  public static void MyMethod1() { }  public static void MyMethod2() { }
}MyDelegate myDelegate = new MyDelegate(MyClass.MyMethod1);//定义了委托实例,并添加了相应的操作方法
//MyDelegate myDelegate = MyClass.MyMethod;//<--简写就是这样
myDelegate += MyClass.MyMethod2;//多播委托

上面的代码展示了委托的基本用法,多播委托也可以用Delegate.Combin()方法来实现

多播委托可以美化成下面的代码

MyDelegate myDelegate = null;
myDelegate += MyClass.MyMethod1;
myDelegate += MyClass.MyMethod2;

是不是漂亮多了!

在C#3以后常用委托都可以用Action跟Func来替代了(C#3还是2忘记了- -)

委托存在的意义:方法传递

真实案例:

在controller的自定义基类中有一个protected void CreateCookie(string name, string value) 方法

在获取到微信openid后,进行一些数据库处理,同时保存此openid的登录信息到cookies


public static void SetOpenId(string openId, Action<string, string> setCookie)
WeixinTool.SetOpenId(openid, CreateCookie);

这样便将CreateCookie传递给了SetOpenId方法

匿名委托

不需要定义方法名,直接书写方法体赋值给委托

在lambda表达式出来后用的不多了, 实际上lambda表达式就是匿名委托

MyDelegate anonymous1 = delegate() { Console.WriteLine("this is a test 1"); };//匿名委托
MyDelegate anonymous2 = () => { Console.WriteLine("this is a test 2"); };//lambda表达式

anonymous1();
anonymous2();

上面的代码编译后使用IlSpy查看直接就是俩匿名委托

使用ildasm查看il也是一致的

说了委托,是不是该说事件了

大家应该都写过winform啦,点击按钮触发click事件,相关事件处理程序影响该事件

很同学都知道有事件,但并不能准确描述事件是什么 (前文的多播委托的优化版是不是看着像事件)

public event MyDelegate ChangeSomething;

首先事件是"属性",是类的一个"属性",所以只能定义在一个类里面(或者结构体里面)

但是event关键字让你不能直接对这个属性赋值,所以只能用"+="或者"-="来操作这个"属性"

事件存在的目的是为了实现"发布/订阅模式",也就是大家常说的pub/sub

为啥不能让你直接给这个属性赋值呢,因为"订阅者"并不知道有多少人订阅了这个事件,如果大家都用"="来操作,后面的"订阅者"就会覆盖前面的"订阅者",容易造成bug,故而event关键字封装了委托,关闭了直接赋值通道

委托的逆变与协变

用过泛型的很多同学都知道,泛型有逆变跟协变,其实委托也有逆变跟协变(接口,数组也有此特性)

那么啥是逆变与协变呢

简单来说

逆变:

基类变子类 -> 逆了天了,这都可以,所以叫逆变

逆变实际是编译器根据执行上下文推断类型是可以转换,才编译通过的

看似逆天实际也属于"is-a"关系正常转换

协变:

子类变基类->CLR协助变形,所以叫协变

大家在编程中常用到,"is-a"关系,所以可以正常转换

对于委托,逆变与协变可以是返回值变化,也可以是参数变化,亦可以是二者同时变化

来来来,我们来看一些具体的栗子:

定义类型与继承

class Person {}

class Employee : Person {}

定义委托

delegate Person EmployeeInPersonOut(Employee employee);

定义一些适合委托的方法

class Methods
{
    public static Person EmployeeInPersonOut(Employee employee)
    {
        return new Person();
    }

    public static Employee EmployeeInEmployeeOut(Employee employee)
    {
        return new Employee();
    }

    public static Person PersonInPersonOout(Person person)
    {
        return new Person();
    }

    public static Employee PersonInEmployeeOut(Person person)
    {
        return new Employee();
    }
}

常规使用

//常规使用
EmployeeInPersonOut employeeInPersonOut = Methods.EmployeeInPersonOut;
Person person = employeeInPersonOut(new Employee());

协变

//协变使用
/*
 * 返回值Employee跟Person属于"is-a"关系,所以是常规转换
 */
EmployeeInPersonOut employeeInPersonOut = Methods.EmployeeInEmployeeOut;
Person person = employeeInPersonOut(new Employee());

逆变

//逆变使用
/*
* 对于委托声明:委托方法的参数Person竟然可以变成Employee!
* 实际是编译器根据上下文推断,对象可以成功转换
* 在执行的时候, 委托声明EmployeeInPersonOut只能输入Employee
* Employee对于Methods.PersonInPersonOout的参数peron是"is-a关系",所以可以正常转换成方法参数
*/
EmployeeInPersonOut employeeInPersonOut = Methods.PersonInPersonOout;
Person person = employeeInPersonOut(new Employee());

协变与逆变一起使用

//这段就不解释了,仔细看前两段就能明白其中原理
EmployeeInPersonOut employeeInPersonOut = Methods.PersonInEmployeeOut;
Person person = employeeInPersonOut(new Employee());

协变在winform中的应用

class Program
{
    static void Main(string[] args)
    {
        var button =  new Button(){Text = "click me!"};
        button.Click += HandleEvent;
        button.KeyPress += HandleEvent;

        var form = new Form();
        form.Controls.Add(button);

        Application.Run(form);
    }

    static void HandleEvent(object sender, EventArgs args)
    {
        MessageBox.Show(args.GetType().FullName);
    }
}

用匿名无参委托忽略事件参数也是可以的

button.Click += delegate {/*do something.*/};

委托与闭包

什么是闭包

class Program
{
    static void Main(string[] args)
    {
        var action = ClosureMethod();

        action();
        action();
        action();

        Console.ReadKey();

    }

    static Action ClosureMethod()
    {
        int localCounter = 0;

        Action x = delegate
        {
            localCounter++;
            Console.WriteLine(localCounter);
        };

        return x;
    }
}

这段代码依次输出1,2,3

这就是闭包

可以参考javascript中的闭包,猜测一下:匿名方法使用了局部变量"localCounter",使得在方法执行完后无法释放变量,从而形成了一个"范围内的全局变量"

下面我们来验证一下这个猜测

祭出神器:IL DASM

为了看着简单点,我把代码稍微做了点修改

static Action ClosureMethod()
{
    string local = "零";

    Action x = delegate
    {
        local += "壹";
        Console.WriteLine(local);
    };

    return x;
}

汉字在il中更容易找到位置

从il中可以看出

C#闭包并不是与js一样是由于垃圾回收机制的原因

由于匿名方法捕获了一个"外部方法"的局部变量"local"

使得编译器生成了一个"内部类"(<>c_DisplayClass1)

而"外部方法"直接使用了这个"内部类"的实例中的变量(il中的<>c_DisplayClass1::local)

委托"Aciton x"也使用了该实例

这样变完成了"闭包", 所以C#中的闭包完全是编译器的功劳

闭包的作用

1.局部变量实例化,使得外部可以使用该变量

static  IList<string> StringFilter(List<string> list, int length)
        {
            return list.FindAll(delegate(string str)
            {
                return str.Length > length;
            });

        }

当然也可以使用lambda表达式

static IList<string> StringFilter(List<string> list, int length)
        {
            return list.FindAll(str => str.Length > length);

        }

前面说过lambda表达式实际就是匿名委托

上面的代码都捕获了外部变量length

2.延长变量生命周期,委托不死,变量不亡(var action = ClosureMethod();这有在action释放后,"ClosureMethod"的变量"local"才会被释放)

就像闭包部分第一段代码的计数器,在"ClosureMethod"方法执行完毕后,变量"localCounter"的生命周期延长了

说一说闭包中的坑

在for中使用闭包

坑1:

static void Main(string[] args)
{
    var actions = LoopClosure();

    actions[0]();
    actions[0]();
    actions[0]();

    actions[1]();
    actions[2]();

    Console.ReadKey();

}

static IList<Action> LoopClosure()
{
    var list = new List<Action>();

    for (int i = 0; i < 3; i++)
    {
        int val = i*10;

        list.Add(delegate
        {
            val++;
            Console.WriteLine(val);
        });   
    }

    return list;
}

输出结果是1,2,3,11,21

此循环虽然只有生成了一个"内部类",但是每次循环都产生了一个"内部类"的实例,所以会有上述结果

坑2:

var actions = new List<Action>();
for (int i = 0; i < 3; i++)
    actions.Add(() => Console.WriteLine(i));//access to modified closure ‘i‘
foreach (var action in actions)
    action();

输出结果是3,3,3

因为使用了变化/修改过的闭包变量

但是在foreach中是没有这个坑的

var actions = Enumerable.Range(0, 3).Select(i => (Action)(() => Console.WriteLine(i))).ToList();

这样的在foreach中的闭包就能正常输出0,1,2

趣味编程:

能不能在C#中像javascript一样写一个自执行方法 ^_^

参考资料:

https://msdn.microsoft.com/zh-cn/library/ee207183.aspx
https://msdn.microsoft.com/zh-cn/library/dd233060.aspx
https://msdn.microsoft.com/zh-cn/library/dd465122.aspx
http://csharpindepth.com/articles/chapter5/closures.aspx

欢迎以任何形式的转载本文,转载请注明出处,尊重他人劳动成果
转载请注明:文章转载自:博客园[http://www.cnblogs.com]
本文标题:说说委托那些事儿
本文地址:http://www.cnblogs.com/eyu/p/all_those_delegate_things.html

时间: 2024-11-10 04:36:54

说说委托那些事儿的相关文章

学习过程中找到的一些好文章和我的一些想法记录

C# 中 点击button按钮时 那个焦点虚线框怎么去掉? 12楼亮了. 拖动无边框Form窗体 网站模板(Site Template)sitetemplate 参数代码 C#各种结束进程的方法详细介绍 [测试建议]测试tooltip的时候要多在内容中加一些标点单引号双引号之类的特殊符号,后面接上文字. GDI+发生generic错误,原因在于对图片存储的路径权限不够. 利用SmtpClient发送邮件 [翻译]表达式树基础 C# 参考:反射 -- 学习笔记整理,概念与应用 创建Windows服

.NET中那些所谓的新语法之三:系统预定义委托与Lambda表达式

开篇:在上一篇中,我们了解了匿名类.匿名方法与扩展方法等所谓的新语法,这一篇我们继续征程,看看系统预定义委托(Action/Func/Predicate)和超爱的Lambda表达式.为了方便码农们,.Net基类库针对实际开发中最常用的情形提供了几个预定义好的委托,这些委托可以直接使用,无需再重头定义一个自己的委托类型.预定义委托在.Net基类库中使用的比较广泛,比如在Lambda表达式和并行计算中都大量地使用,需要我们予以关注起来! /* 新语法索引 */ 1.自动属性 Auto-Impleme

关于BT下载的一点事儿

之前一直对BT下载非常的好奇,今天迅雷出现了一些问题,于是上网了解了一下BT下载的原理,果然还是有所收获的. 1.为什么BT下载用户越多下载,速度越快? 答:BT全名为BitTorrent. 在传统下载方式中,一般是把文件由服务器端传送到客户端,例如FTP,HTTP,PUB等等.由于是从一台服务器下载,服务器所提供的带宽是一定的,因而随着用户的增多,对带宽的要求也随之增多,用户过多就会造成瓶颈,而且搞不好还会让服务器陷入瘫痪,所以很多的服务器对用户人数和下载速度进行限制. BT下载原理与传统下载

从异步更新进度想起的事儿——IProgress

今天,在群里向大家请教了这样一个问题:"两个对象(类.窗体或什么)之间,要完成比较频繁的报告进度更新都有哪些好的方式",Somebody 跳出来给出了个"IProgress",没了解过,后面围绕着它讨论学习了下. 简单来说,IProgress 是类库给出的一种解决问题的方式而非具体实现,IProgress 包括一个需要实现的 Report 方法.使用时由调用"任务"的"创建者"创建对接口的实现,也就是具体实现 Report 的

对张子阳先生对委托和事件的两篇文章的读后思考(说得很透,内附故事一篇)

第一篇 C#中的委托和事件 第二篇 C#中的委托和事件(续) 首先,张子阳先生的这是两篇关于委托和事件间关系的文章,是目前为止我读过的介绍委托和事件以及异步调用最简明清晰文章,作者通过非常有节奏的"标题"->"问题"->"思路"->"实现"->"讲解"的结构,分步骤一步一步地将委托和事件的实现.应用与原理阐述得非常清楚,并且在行文期间将自己有趣的思考过程通过生动的语言表达了出来,使人

对张子扬显示的两篇委托和事件说得很透文章读后的思考

第一篇 C#中的委托和事件 http://www.tracefact.net/CSharp-Programming/Delegates-and-Events-in-CSharp.aspx 第二篇 C#中的委托和事件(续) http://www.tracefact.net/CSharp-Programming/Delegates-and-Events-Advanced.aspx 这是两篇目前为止,我读过的介绍委托和事件以及异步调用最简明清晰文章,作者通过非常有节奏的"标题"->&q

C#委托(Delegate)学习日记

在.NET平台下,委托类型用来定义和响应应用程序中的回调.事实上,.NET委托类型是一个类型安全的对象,指向可以以后调用的其他方法.和传统的C++函数指针不同,.NET委托是内置支持多路广播和异步方法调用的对象. 委托类型包含3个重要信息: 它所调用的方法的名称 该方法的参数 该方法的返回值 1.定义一个委托类型 // 这个委托可以指向任何传入两个整数,返回整数的方法 public delegate int BinaryOp(int x,int y); 创建一个委托类型时,需要使用delegat

[转] iOS文字排版(CoreText)那些事儿

文章转载自 http://www.cocoachina.com/applenews/devnews/2014/0521/8504.html iOS文字排版(CoreText)那些事儿 转自阿毛的蛋疼地 第一次比较深入接触iOS文字排版相关内容是在12年底,实现某IM项目聊天内容的图文混排,照着nimbus的AttributedLabel和Raywenderlish上的这篇文章<Core Text Tutorial for iOS: Making a Magazine App>改出了一个比较适用

关于类加载器那点破事儿

Java的类加载器就是负责把.class文件加载到内存中的工具.据说,只有.class被加载如内存后才能被称为字节码. Java的类加载器呈父子级联关系: BootStrap --> ExtClassLoader --> AppClassLoader -- > 自定义类加载器 -- BootStrap 跟加载器,负责加载rt.jar这类的由Java自身提供的类,他是由C++编写的,所以当我们在程序里想要get他时,返回的是null -- ExtClassLoader 扩展类加载器,负责加