C#基础之匿名方法实例教程

本文以实例形式讲解了C#的匿名方法的用法,分享给大家供大家参考之用。具体如下:

匿名方法是C# 2.0的语言新特性。首先看个最简单的例子:

class Program
{
 static void Main(string[] args)
 {
  List<string> names = new List<string>();
  names.Add("Sunny Chen");
  names.Add("Kitty Wang");
  names.Add("Sunny Crystal"); 

  List<string> found = names.FindAll( new Predicate<string>(NameMatches)); 

  if (found != null)
  {
   foreach (string str in found)
    Console.WriteLine(str);
  }
 } 

 static bool NameMatches(string name)
 {
  return name.StartsWith("sunny", StringComparison.OrdinalIgnoreCase);
 }
}

这段代码在开始的时候初始化了一个字符串列表(string list),然后通过列表的FindAll方法来查找以“sunny”起始的字符串,最后将所查找到的所有结果输出。

我们需要着重介绍List<T>的FindAll方法。这个方法是一个参数为Predicate<T>类型、返回值为List<T>类型的函数。注意,Predicate<T>是一个泛型委托,它指代这样一些函数:这些函数仅有一个T类型的参数,并且返回值是布尔类型。通过reflector等工具,我们可以看到Predicate<T>的定义如下:

public delegate bool Predicate<T>(T obj);

至此我们也多少能够猜到FindAll方法的具体实现。即针对List<T>中的每个元素,调用Predicate<T>所指代的函数,如果函数返回为true,则将其加入新建的列表中。遍历完所有的元素后,将新建的列表返回给调用者。如下:

public List<T> FindAll<T>(Predicate<T> match)
{
 List<T> ret = new List<T>();
 foreach (T elem in items)
 {
  if (match(elem))
   ret.Add(elem);
 }
 return ret;
} 

因此,针对上面的例子,要调用FindAll方法,我们必须先定义一个参数为string类型,返回值为布尔类型的函数,在这个函数中,对参数进行条件判断,如果符合条件(也就是以“sunny”作为起始字符串),那么就返回true,否则返回false。最后再将这个函数作为参数传递给FindAll。于是也就得到了最上面的代码。

在上面的例子中,为了调用FindAll方法,我们不得不新定义一个函数,其实这个函数除了FindAll方法要用外,别的地方都几乎很少使用到它,你还不得不给它起个名字。如果程序中有多处需要调用FindAll方法,或者类似的情况,那么整个程序也就会出现一大批“只有一个地方使用”的函数,使得代码难于阅读和维护。

由于存在这样的问题,C# 2.0引入了匿名方法。开发人员在实现方法的时候,只需要给出方法的参数列表(甚至也可以不给)以及方法具体实现,而不需要关心方法的返回值,更不必给方法起名字。最关键的是,只在需要的地方定义匿名方法,保证了代码的简洁。

匿名方法只在需要的地方定义,定义的时候,使用delegate关键字,后接参数列表,然后跟上用一对花括号包括起来的函数体即可。上面的代码可以重构成下面的形式:

class Program
{
 static void Main(string[] args)
 {
  List<string> names = new List<string>();
  names.Add("Sunny Chen");
  names.Add("Kitty Wang");
  names.Add("Sunny Crystal"); 

  List<string> found = names.FindAll(
   delegate(string name)
   {
    return name.StartsWith("sunny", StringComparison.OrdinalIgnoreCase);
   }); 

  if (found != null)
  {
   foreach (string str in found)
    Console.WriteLine(str);
  }
 } 

 //static bool NameMatches(string name)
 //{
 // return name.StartsWith("sunny",
 //  StringComparison.OrdinalIgnoreCase);
 //}
} 

此时,我们完全不需要NameMatches方法了,直接将匿名方法作为参数传递给FindAll方法。其实匿名方法本身还是有名字的,只是我们并不关心它究竟该取什么名字,因而.NET帮我们随便取了个名字罢了。

匿名方法在C#中应用十分广泛,因为委托作为函数参数是件非常平常的事情。在定义简单的事件处理过程时,我们同样可以使用匿名方法。比如:

ServiceHost host = new ServiceHost(typeof(FileTransferImpl));
host.Opened += delegate(object sender, EventArgs e)
{
 Console.WriteLine("Service Opened.");
};

匿名方法可以很方便地使用本地变量,这与单独定义的命名方法相比,能够简化编程。比如上文的例子中,假如Main函数里面定义了一个整型本地变量(局部变量)number,那么可以在delegate (string name)这一匿名方法定义中使用number变量。

上文提到,在定义匿名方法的时候,连参数列表都可以省略。因为编译器可以根据委托的签名来确定函数的签名,然后只要再给函数起个名字就可以了。下面的代码演示了这种使用方式:

delegate void IntDelegate(int x); 

// 带参数的定义方式
IntDelegate d2 = delegate(int p) { Console.WriteLine(p); };
// 不带参数的定义方式(当然也没带返回值)
IntDelegate d3 = delegate { Console.WriteLine("Hello."); };

在使用不带参数和返回值的匿名方法定义时,需要注意以下两点:

1.如果在你的匿名方法中需要对参数进行处理,那么你不能使用不定义参数列表的声明方式。也就是在定义匿名方法的时候,需要给出参数列表。
2.不带参数和返回值的匿名方法,可以被具有任何形式签名的委托所指代。

上述第一点显而易见,因为你没有定义参数列表,也就没有办法使用参数;要说明第二点,我们可以看下面的代码:

class Program
{
 delegate void IntDelegate(int x);
 delegate void StringDelegate(string y); 

 static void Output(IntDelegate id)
 {
 } 

 static void Output(StringDelegate sd)
 {
 } 

 static void Main(string[] args)
 {
  /*
   * ERROR: The call is ambiguous between
   *  Output(IntDelegate)
   *    and
   *  Output(StringDelegate)
   */
  Output(delegate { });
 }
}

上面的代码没法编译通过,因为编译器不知道应该将delegate { }这一匿名方法还原为由IntDelegate指代的函数,还是还原为由StringDelegate指代的函数。此时只能显式给定参数列表,以便让编译器知道,我们究竟是想调用哪个Output函数。

希望本文所述对大家的C#程序设计有所帮助

(0)

相关推荐

  • c#装箱和拆箱知识整理

    1.装箱和拆箱是一个抽象的概念 2.装箱是将值类型转换为引用类型 : 拆箱是将引用类型转换为值类型 利用装箱和拆箱功能,可通过允许值类型的任何值与Object 类型的值相互转换,将值类型与引用类型链接起来 例如: 复制代码 代码如下: int val = 100; object obj = val; Console.WriteLine ("对象的值 = {0}", obj); 这是一个装箱的过程,是将值类型转换为引用类型的过程 复制代码 代码如下: int val = 100; obj

  • C#网络编程基础之进程和线程详解

    在C#的网络编程中,进程和线程是必备的基础知识,同时也是一个重点,所以我们要好好的掌握一下. 一:概念 首先我们要知道什么是"进程",什么是"线程",好,查一下baike. 进程:是一个具有一定独立功能的程序关于某个数据集合的一次活动.它是操作系统动态执行的基本单元, 在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元. 线程:是"进程"中某个单一顺序的控制流. 关于这两个概念,大家稍微有个印象就行了,防止以后被面试官问到. 二:进程

  • C#进阶系列 WebApi身份认证解决方案推荐:Basic基础认证

    前言:最近,讨论到数据库安全的问题,于是就引出了WebApi服务没有加任何验证的问题.也就是说,任何人只要知道了接口的url,都能够模拟http请求去访问我们的服务接口,从而去增删改查数据库,这后果想想都恐怖.经过一番折腾,总算是加上了接口的身份认证,在此记录下,也给需要做身份认证的园友们提供参考. 一.为什么需要身份认证 在前言里面,我们说了,如果没有启用身份认证,那么任何匿名用户只要知道了我们服务的url,就能随意访问我们的服务接口,从而访问或修改数据库. 1.我们不加身份认证,匿名用户可以

  • C# 表达式树Expression Trees的知识梳理

    目录 简介 Lambda 表达式创建表达式树 API 创建表达式树 解析表达式树 表达式树的永久性 编译表达式树 执行表达式树 修改表达式树 调试 简介 表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和 x < y 这样的二元运算等. 你可以对表达式树中的代码进行编辑和运算.这样能够动态修改可执行代码.在不同数据库中执行 LINQ 查询以及创建动态查询. 表达式树还能用于动态语言运行时 (DLR) 以提供动态语言和 .NET Framework 之间的互操作性. 一.

  • C#基于TCP协议的服务器端和客户端通信编程的基础教程

    运行在TCP之上常见的网络应用协议有比如HTTP.FTP.SMTP.POP3.IMAP. TCP是TCP/IP体系中最重要的传输协议,它提供全双工和可靠交付的服务,是大多数应用协议工作的基础. TCP是一种面向连接(连接导向)的,可靠的,基于字节流的传输层通信协议. TCP的工作过程 建立连接 传输数据 连接的终止 TCP的主要特点 1.TCP是面向连接的协议 2.是端到端的通信.每个TCP连接只能有两个端点,而且只能一对一通信,不能点对多的 的直接通信 3.高可靠性 4.全双工方式传输 5.数

  • c#多线程编程基础

    无论您是为具有单个处理器的计算机还是为具有多个处理器的计算机进行开发,您都希望应用程序为用户提供最好的响应性能,即使应用程序当前正在完成其他工作.要使应用程序能够快速响应用户操作,同时在用户事件之间或者甚至在用户事件期间利用处理器,最强大的方式之一是使用多线程技术. 多线程:线程是程序中一个单一的顺序控制流程.在单个程序中同时运行多个线程完成不同的工作,称为多线程.如果某个线程进行一次长延迟操作, 处理器就切换到另一个线程执行.这样,多个线程的并行(并发)执行隐藏了长延迟,提高了处理器资源利用率

  • c#基础之数组与接口使用示例(遍历数组 二维数组)

    一.初始化数组: 复制代码 代码如下: string[] s1 = {"aaa","bbb","ccc"}   //直接赋值string[] s2 = new string[5] {"aaa","bbb","ccc"}; //赋值加指定长度string[] s3 =  new string[]{"aaa","bbb","ccc"

  • C#学习笔记整理_变量等基础语法(必看篇)

    C#学习笔记1: 变量的作用域冲突时,调用实例变量:this.a,调用类变量:类名.a 常量总是静态的,必须初始化,一般用全大写格式,声明关键字为const,如const int NUNBE = 10; C#的基本预定义类型内置于.NET Framework结构中(System),object是基类: 整型:System.SByte.System.Int16.System.Int32.System.Int64 有符号的8位.16位.32位.64位分别表示为sbyte.short.int.long

  • C# 6.0 的知识梳理

    序 目前最新的版本是 C# 7.0,VS 的最新版本为 Visual Studio 2017 RC,两者都尚未进入正式阶段.C# 6.0 虽说出了一段时间,但是似乎有许多园友对这一块知识并不了解,如拼接字符串的 $ 符号,在此,小人献上拙作一篇<C# 6.0 的知识梳理>,祝大家在新的一年里:年年有今日,岁岁有今朝,月月涨工资,周周中彩票,天天好心情,日日好运道,白天遇财神,夜晚数钞票. 好了,废话不多说,我们先来回顾一下 C# 的版本史.后续我会对带 0 的版本号进行的简写:C# 6.0 -

  • C#知识整理

    这里简单介绍了一些常用的属性,以及一些术语的解释和举例说明,不太全面,希望读者多多补充. 1.重载:函数名相同,参数的个数或参数类型不同; public void MyDog(string s); public void MyDog(int i); public void MyDog(string s,int i); 2.继承:一个类继承另一个类中的成员,被继承的叫做基类,继承类叫做派生类; class A { 成员; } class B:A //继承的写法 派生类:基类 { 成员; } 3.多

随机推荐