当前位置:新注册送38元体验金 > 新注册送38元体验金编程 > 说说委托那些事儿,说说委托事儿

说说委托那些事儿,说说委托事儿

文章作者:新注册送38元体验金编程 上传时间:2019-08-22

说说委托那些事儿,说说委托事儿

挖一挖委托那些事儿,匿名方法,委托的逆变与协变,委托与闭包,C#自执行函数

委托基础

委托是个啥?

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

委托的定义类似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查看直接就是俩匿名委托

新注册送38元体验金 1

使用ildasm查看il也是一致的

新注册送38元体验金 2

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

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

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

public event MyDelegate ChangeSomething;

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

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

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

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

 

委托的逆变与协变

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

那么啥是逆变与协变呢

简单来说

逆变:

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

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

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

 

协变:

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

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

 

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

 

新注册送38元体验金,来来来,我们来看一些具体的栗子:

定义类型与继承

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中更容易找到位置

新注册送38元体验金 3

 

从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

新注册送38元体验金 4

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

坑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一样写一个自执行方法,答案显然是可以的 ^_^

 //无参自执行
((Action)(delegate
{
    Console.WriteLine("I'm a IIFE method.");
}))();

//有参自执行
((Action<int>)(delegate(int i) {
    Console.WriteLine("I'm a IIFE method with parameter:{0}", i);
}))(2);

 

参考资料:




 

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

 

挖一挖委托那些事儿,匿名方法,委托的逆变与协变,委托与闭包,C#自执行函数 委托基础 委托是个啥? 很多...

本文由新注册送38元体验金发布于新注册送38元体验金编程,转载请注明出处:说说委托那些事儿,说说委托事儿

关键词: