委托、事件和Lmbdas表达式3----委托相关知识

 

与委托相关的知识点有很多,本节仅仅就委托的协变和逆变、匿名方法、方法组转换与Lambda表达式做一些介绍。

8.5.1 委托的协变和逆变

在面向对象设计程序中,允许把派生类的对象赋值给基类的对象,如果从继承关系来看,允许从底层对象向高层对象的转换,并且这种转换是自动的。根据这一规律,对于委托来说可以得出两个有用的知识点。这个就是协变与逆变。

协变:从一个方法或者函数的返回类型来看,返回子类的函数或者方法都可以绑定到具有基类返回类型的委托上面。例如:如何将委托与具有返回类型的方法一起使用,这些返回类型派生自委托签名中的返回类型。由 SecondHandler 返回的数据类型是 Dogs 类型,它是由委托中定义的 Mammals 类型派生的。

class Mammals
{
}
 
class Dogs : Mammals
{
}
 
class Program
{
    //定义一个委托,该委托返回类型是Mammals
    public delegate Mammals HandlerMethod();
 
    public static Mammals FirstHandler()
    {
        return null;
    }
 
    public static Dogs SecondHandler()
    {
        return null;
    }
 
    static void Main()
    {
        HandlerMethod handler1 = FirstHandler;
 
        //协变允许委托这样绑定方法
        HandlerMethod handler2 = SecondHandler;
    }
}

逆变:如果从函数或者方法的参数来考虑,那么具有基类参数的函数或者方法都可以绑定到具有子类参数签名的委托上面。比如,现在可以创建一个接收 EventArgs 输入参数的事件处理程序MultiHandler,然后,可以将该处理程序与发送 MouseEventArgs 类型(作为参数)的 Button.MouseClick 事件一起使用,也可以将该处理程序与发送 KeyEventArgs 参数的 TextBox.KeyDown 事件一起使用。因为MouseEventArgsKeyEventArgs都是从EventArgs派生出来的子类。

System.DateTime lastActivity;
public Form1()
{
    InitializeComponent();
    lastActivity = new System.DateTime();
    this.textBox1.KeyDown += this.MultiHandler;    this.button1.MouseClick += this.MultiHandler; 
}
 
private void MultiHandler(object sender, System.EventArgs e)
{
    lastActivity = System.DateTime.Now;
}

8.5.2匿名方法

在程序中,经常有这样一些需求:

1)需要一个临时方法,这个方法只会使用一次,或者使用的很少。

2)这个方法的方法体很短,以至于比方法声明都短。

在以前,即使有上面的情况,我们还是不得不在类里面写出这两个方法。增加了类代码中小型方法的混乱。而且要想使委托工作,委托也要求方法必须已经存在。可是通过匿名方法,上面的情况都可以避免。

匿名方法可以将代码块传递为委托参数。用匿名方法定义委托的语法与前面的定义并没有什么区别。例如

delegate int Del(int x);

Del d = delegate(int k) { return k*k  };

可以看到使用格式:

delegate(参数类型  参数){ 对参数加工的语句块;}

能够让系统生成一个方法或者函数,只是这个委托没有具体的名字,这个方法或者函数可以直接绑定给具有相同方法签名的委托。可能你已经注意到,delegate(int k) { return k*k  };中并没有显式给出返回值的类型,这个工作系统会自动的完成,可以根据代码的返回值自动推断出类型。

匿名方法最明显的好处就是可以降低重新写一个方法的工作量,特别是这些方法的实现语句很小,或者被使用的机会很少。另外一个好处就是可以访问调用者的变量,降低传参数的复杂度,下面就通过一些使用例子来具体看看。

假设现在有一个城市名称的列表list,现在需要从list中找到符合一定条件的城市的名称,并打印出来。比如找到list中所有以字符xy开头的城市名称。这些要求可能很多。按照传统的写法,每一个要求都需要写成一个方法。下面就是例子AnonymityMethod代码:

namespace AnonymityMethod

{   

    class Program

    {        

        static void Main(string[] args)

        {

            List<string> list = new List<string>();

            list.Add("xian");

            list.Add("shanghai");

            list.Add("beijing");

            list.Add("nanjing");

            list.Add("xuchang");

            list.Add("nanchang");

            list.Add("yanan");

            list.Add("xiangfan");

            list.Add("yuncheng");

            //查找以x开始的城市名称结果放入ResultList

            List<string> ResultList = list.FindAll(FindCitysByFirstX);

            foreach (string item in ResultList)

            {

                Console.WriteLine(item);

            }

            //查找以y开始的城市名称,结果放入ResultList

            ResultList = list.FindAll(FindCitysByFirstY);

            foreach (string item in ResultList)

            {

                Console.WriteLine(item);

            }

            Console.ReadKey();

        }

        //找到以x开始的城市名

        public static bool FindCitysByFirstX(string city)

        {

            return city[0] == 'x';

 

        }

        //找到以y开始的城市名

        public static bool FindCitysByFirstY(string city)

        {

            return city[0] == 'y';

        }       

    }

}

例子中有两种规则,那么就要定义两个方法,如果使用匿名方法的话,代码就会很简单,把例子AnonymityMethod改写成例子AnonymityMethod2

namespace AnonymityMethod2

{

    class Program

    {

        static void Main(string[] args)

        {

            List<string> list = new List<string>();

            list.Add("xian");

            list.Add("shanghai");

            list.Add("beijing");

            list.Add("nanjing");

            list.Add("xuchang");

            list.Add("nanchang");

            list.Add("yanan");

            list.Add("xiangfan");

            list.Add("yuncheng");

            //查找以x开始的城市名称结果放入ResultList

            List<string> ResultList = list.FindAll(delegate(string city)

                                                     {

                                                         return city[0] == 'x';

                                                     }

                                                   );

            foreach (string item in ResultList)

            {

                Console.WriteLine(item);

            }

            //查找以y开始的城市名称,结果放入ResultList

            ResultList = list.FindAll(delegate(string city)

                                        {

                                            return city[0] == 'y';

                                        }

                                      );

            foreach (string item in ResultList)

            {

                Console.WriteLine(item);

            }

            Console.ReadKey();

        }

 

    }

}

使用工具Reflector查看生成的代码,可以看到如图8-9所示

1

 

8-9

在图8-9中可以看到,系统还是在背后偷偷的生成了两个静态的方法,名字分别为<Main>b__0<Main>b__1,所以生成方法的代码系统自动在背后实现了。

匿名方法的另一个优点是可以访问调用者的变量,降低传参数的复杂度。比如现在要做一个字符串中字符替换的程序,现在替换的规则有三个,将字符串中所有的字符‘A’替换为‘1’,一个规则是把字符串的所有的字符‘B’替换为字符‘4’,最后一个是把串的所有的字符‘C’替换为字符‘8’。按照传统的方法,每一个规则在类中都需要写成一个类的方法,比如把字符A替换为1的方法。

public string ConvertATo1(string strText)

        {

            return strText.Replace("A", "1");

    }

为了替换,不得不把整个字符串作为参数进行传递,可是如果使用匿名方法,因为调用匿名方法的主调函数中就用字符串,所以在匿名方法可以自己使用,像这样的变量,匿名方法称为外部变量或者捕获变量,匿名方法可以直接使用这种变量,而不需要另外传递进来。采用匿名方法的完整代码。

namespace AnonymityDirect

{

    class Program

    {

        delegate string Convert();

        private static void Test2(Convert convert)

        {

            Console.WriteLine(convert());

        }

        static void Main(string[] args)

        {

            string strText = "ABCDABCDABCDA";

 

            Test2(delegate()

            {

                return strText.Replace("A", "1");

            });

            Test2(delegate()

            {

                return strText.Replace("B", "4");

            });

            Test2(delegate()

            {

                return strText.Replace("C", "8");

            });

 

            Console.ReadLine();

        }

    }

}

8.5.3 Lambda 表达式

C#首席架构师Anders Hejlsberg曾经对Lambda 表达式表达过这样的观点:Lambda 表达式(拉姆达表达式)和匿名方法其实是一件事情。唯一的不同是:他们语法表现形式不同。Lambda 表达式是在语法方面的更进一步的进化。在本质上,他们是一件事情。他们的作用都是:产生方法。即:内联方法。

Lambda表达式的书写格式如下:

(参数列表) => 表达式或者语句块

其中:

参数个数:可以有多个参数,一个参数,或者无参数。

参数类型:可以隐式或者显式定义。当指定类型时,必须用括号将类型名称和变量名括起。

=>符号称作 lambda 运算符,可以解释为“到”。=> 运算符具有与赋值运算符 (=) 相同的优先级,并且是右结合运算符。

表达式或者语句块:这部分就是我们平常写函数的实现部分(函数体)。

先看一个具体的小例子,比如计算整数平方的函数pow2的传统写法是

int pow2(int x)

{  

   return x*x;

}

把它改写成Lambda表达式就可以写成(int x=>x*x,或者写成(x)=>x*x

为了作为对比,把例子AnonymityMethod2改写成LambdaSample,只需要将例子AnonymityMethod2中查找以字符‘x’开始的匿名方法相对的语句从

List<string> ResultList = list.FindAll(delegate(string city)

{

                                   return city[0] == 'x';

                                 }

                                );

改写成语句

List<string> ResultList = list.FindAll((string s)=>s[0]=='x');就能够达到想要的结果。使用Reflector查看生成的代码,可以看到如图8-10

2

 

8-10

可以看到图8-10与图8-9几乎一样,同时可以查看<Main>b__0或者<Main>b__1也可以看到与例子AnonymityMethod2中的代码也完全一样,说明Lambda表达式的内部实现方式与匿名方法完全一样。只是Lambda表达式更加直观,代码的可读性更好。

当然Lambda表达式也可以比较复杂,而不仅仅是一句话,比如(x, y) => { if (x < 5) { return (x + 8) * y; } else { return y; }这种写法也是允许的。

与匿名方法一样,Lambda表达式也可以直接读取主调函数中的局部变量。

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值