程序员简书面面观

C#沉淀-委托

2018-09-21  本文已影响151人  东南有大树

什么是委托

可以认为委托是持有一个或多个方法的对象。委托可以被执行,执行委托时委托会执行它所“持有”的方法

代码示例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CodeForDelegate
{
    //使用关键字delegate声明委托类型
    //委托是一种类型,所以它与类属于同一级别
    //注意:这里是委托类型,而不是委托对象
    delegate void MyDel(int value);

    class Program
    {

        void PrintLow(int value)
        {
            Console.WriteLine("{0} - Low Value", value);
        }

        void PrintHigh(int value)
        {
            Console.WriteLine("{0} - High Value", value);
        }

        static void Main(string[] args)
        {
            //实例化Program类,以访问PrintLow和PrintHigh方法
            Program program = new Program();

            //声明一个MyDel类型的委托
            MyDel del;

            //创建随机数
            Random rand = new Random();
            int randomvalue = rand.Next(99);

            //使用三目运算符根据当前随机数的值来创建委托对象
            del = randomvalue < 50
                ? new MyDel(program.PrintLow)
                : new MyDel(program.PrintHigh);

            //执行委托
            del(randomvalue);

            Console.ReadKey();
        }
    }
}

从上例可以看出,使用委托的首先得通过关键字delegate声明一个委托类型,这个委托类型包括返回值、名称、签名;当类型声明好以后,需要通过new来创建委托对象,创建对象时的参数是一个方法,这个方法的签名和返回类型必须与该委托类型定义的签名一致;调用委托时,直接通过实例化的委托对象名,并提供参数即可,然后委托会执行在其所持有的方法

委托与类

委托和类一样,是一种用户自定义的类型;不同的是类表示的是数据和方法的集合,而委托持有一个或多个方法,以及一系列预定义操作

委托的使用步骤

delegate的原则

delegate相当于一个包含有序方法列表的对象,这些方法都具有相同的签名和返回类型

声明委托类型

如下,delegate关键字开关,然后是返回类型,再定义名称与签名

delegate void MyDel(int vallue);

返回类型与签名指定了委托接受的方法形式

注意:委托类型是没有方法主体的

创建委托对象

使用new运算符创建对象

MyDel del = new MyDel(object.Func); //object.Func是个实例方法
Mydel _del = new MyDel(Object.Func); //Object.Func是个静态方法

使用快捷语法创建对象

MyDel del = object.Func; //object.Func是个实例方法
Mydel _del = Object.Func; //Object.Func是个静态方法

这种语法是能够工作是因为在方法名称和其相应的委托类型之间存在隐式的转换

创建委托对象后会将指定的方法加入到委托的调用列表中

由于委托是引用类型,可以通过赋值来改变包含在委托变量中的引用,如下:

MyDel del;
del = new MyDel(object.FuncA); //创建第一个对象
del = new MyDel(object.FuncB); //创建第二个对象

由于第二个对象也赋值给了变量del,因此del所引用的第一个对象将被垃圾回收器回收

组合委托

//创建两个委托
MyDel del_A = new MyDel(object.FuncA);
Mydel del_B = new MyDel(object.FuncA);

//组合委托
MyDel del_C = del_A + del_B;

当将del_A与del_B通过+进行组合后,会返回一个新的委托对象,该对象将del_A与del_B中的方法调用列表组合到新的对象里,该新对象赋值给变量del_C,所以执行del_C的时候,会执行del_A与del_B中所保存的方法object.FuncA和object.FuncA

委托添加多个方法

MyDel del = object.FuncA; //创建并初始化委托对象
del += object.FuncB; //增加方法
del += object.FuncC; //增加方法

通过+=符号为委托对象添加更多方法,上例中,del对象不保存了三个方法,在执行del时,这三个方法会被依次调用

注意,在使用+=为委托对象添加新的方法时,实际上是创建了一个新的委托对象(原对象的副本)

移除委托方法

del -= object.FuncB; //移除方法
del -= object.FuncC; //移除方法

通过-=来将委托调用列表中已保存的方法,移除动作是从调用列表的最后一个方法开始匹配,一次只会移除一条匹配的方法,如果调用列表中不存在该方法,则没有任何效果;如果试图调用一个空的委托则会发生异常

注意,在使用-=为委托对象移除方法时,实际上是创建一个新的委托对象(原对象的副本)

调用委托

调用委托就像调用方法一样

示例:MyDel类型参考上面的定义

MyDel del = object.FuncA; //创建并初始化委托对象
del += object.FuncB; //增加方法
del += object.FuncC; //增加方法

//调用委托 
del(55);

参数55会在调用委托对象时依次传递给保存的方法

一个完整的委托示例代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CodeForDelegate
{
    class Program
    {
        //定义委托类型
        delegate void PrintFunction(string txt);

        //测试类中定义三个方法
        class Test
        {
            public void PrintA(string txt)
            {
                Console.WriteLine("printA:{0}", txt);
            }

            public void PrintB(string txt)
            {
                Console.WriteLine("printB:{0}", txt);
            }

            public static void PrintC(string txt)
            {
                Console.WriteLine("printC:{0}", txt);
            }
        }
        static void Main(string[] args)
        {
            Test test = new Test();
            PrintFunction pf;

            //实例化并创建委托对象
            pf = test.PrintA;

            //为委托对象增加方法
            pf += test.PrintB;
            pf += Test.PrintC;
            pf += test.PrintA; //添加一个重复的方法

            //通过与null比较,确认委托对象中保存了方法
            if (pf != null)
                pf("Hello");
            else
                Console.WriteLine("pf是个空委托!");

            Console.ReadKey();
        }
    }
}

调用带有返回值的委托

如何委托有返回值,并且调用列表中有一个以上的方法,那么将使用最后一个方法的返回值,之前方法的返回值被忽略

示例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CodeForDelegate
{
    class Program
    {
        //定义委托类型
        delegate int DelFunction();

        //测试类中定义三个方法
        class Test
        {
            int IntValue = 0;
            
            public int FuncA()
            {
                return IntValue += 1;
            }

            public int FuncB()
            {
                return IntValue += 10;
            }
        }
        static void Main(string[] args)
        {
            Test test = new Test();
            DelFunction df;

            df = test.FuncA;
            df += test.FuncB;

            //最终返回值的是11
            if (df != null)
                Console.WriteLine("返回值:"+df());
            else
                Console.WriteLine("pf是个空委托!");

            Console.ReadKey();
        }
    }
}

具有引用参数的委托

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CodeForDelegate
{
    //定义委托类型
    delegate void MyDel(ref int x);
    class Program
    {

        static void Add1(ref int x) { x += 1; }
        static void Add2(ref int x) { x += 2; }

        static void Main(string[] args)
        {
            Program program = new Program();

            MyDel del = Add1;
            del += Add2;

            //ref会将x当作引用值传递给委托方法
            int x = 5;
            del(ref x);

            Console.ReadKey();
        }
    }
}

在调用Add1方法时,x = 5+1,再调用Add2方法时,不是x = 5+2而是x = 6 +2

参考:ref按引用传递参数

在方法的参数列表中使用 ref 关键字时,它指示参数按引用传递,而非按值传递。 按引用传递的效果是,对所调用方法中参数进行的任何更改都反映在调用方法中。 例如,如果调用方传递本地变量表达式或数组元素访问表达式,所调用方法会替换 ref 参数引用的对象,然后,当该方法返回时,调用方的本地变量或数组元素将开始引用新对象

匿名方法

匿名方法是在初始化委托时内联声明的方法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CodeForDelegate
{
    //定义委托类型
    delegate void MyDel(ref int x);
    class Program
    {

        static void Add1(ref int x) { x += 1; }
        static void Add2(ref int x) { x += 2; }

        static void Main(string[] args)
        {
            Program program = new Program();
            
            //采用匿名方法形式代替具名方法
            MyDel del = delegate(ref int y) { y += 3; };
            del += Add1;
            del += Add2;

            //ref会将x当作引用值传递给委托方法
            int x = 5;
            del(ref x);

            Console.ReadKey();
        }
    }
}

在声明委托变量时作为初始化表达式,或在为委托增加事件时使用

语法解析

以关键字delegate开头;后跟小括号提供参数;再后跟{}作为语句块

delegate (Parameters) {ImplementationCode}

示例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CodeForDelegate
{
    //定义委托类型
    delegate void MyDel(ref int x);
    class Program
    {

        static void Add1(ref int x) { x += 1; }
        static void Add2(ref int x) { x += 2; }

        static void Main(string[] args)
        {
            Program program = new Program();
            
            //采用匿名方法形式代替具名方法
            MyDel del = delegate(ref int y) { y += 3; };
            del += Add1;
            del += Add2;
            
            //匿名方法未使用任何参数,简化形式
            del += delegate{int z = 10;};

            //ref会将x当作引用值传递给委托方法
            int x = 5;
            del(ref x);

            Console.ReadKey();
        }
    }
}

如果定义一个带有params形式的参数,在使用匿名方法的时候可以省略params关键字以简化代码

示例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CodeForDelegate
{
    //定义一个带有params形式参数的委托类型
    delegate void DelFunction(int x, params int[] z);
    class Program
    {
        static void Main(string[] args)
        {
            Program program = new Program();
            
            // 关键字params被忽略(省略关键字以简化)
            DelFunction df = delegate(int x, int[] y) { ... };

            Console.ReadKey();
        }
    }
}

Lambda表达式

Lambda可以简化匿名方法,语法形式如下:

(参数) => {语句块} // => 读作 gose to

示例:

MyDel del = delegate(int y) { return y += 3; }; //匿名方法
MyDel del1 = (int y) => {return y += 3;} // Lambda表达式
MyDel del2 = (y) => {return y += 3;} // 省略参数类型
MyDel del3 = y => y += 3; // 省略圆括号和花括号,虽然没有return,但仍会返回y的值

上一篇下一篇

猜你喜欢

热点阅读