连载,LINQ兵法十四章,4(2of2)

hi20140509 2014-05-13 10:21:35
加精
上一篇

(5)匿名委托和Lambda表达式

我们已经知道,委托可以允许我们将一个函数或者一段代码表示为一个变量,它可以作为参数传给一个函数,实现函数内某个步骤的自定义。

比如我们写一个遍历C盘根目录文件的函数:
static void FindFiles(Action<string> onfind)
{
foreach (var item in System.IO.Directory.GetFiles("c:\\", "*.*"))
{
onfind(item);
}
}


函数实现了遍历这个步骤,但是把找到文件后怎么处理它作为委托留给调用者自己去处理。

我们怎么使用它呢?

很显然,我们需要定义一个方法,实现具体如何处理找到的文件(在这里,我们打算直接输出文件名到控制台),然后调用FindFiles,将这个方法作为参数传进去。

首先定义方法:
static void foo(string s)
{
Console.WriteLine(s);
}


然后传进去:
FindFiles(new Action<string>(foo));


我们发现,我们得多定义一个方法,这个方法定义在别处,可读性就打了折扣。我们有时候为了看foo是什么,或者修改它,不得不找foo在哪里定义的。还有,这个foo方法是作为给FindFiles调用时候的参数,很可能只用一次。能不能直接把函数的定义“镶嵌”在对FindFiles的调用内呢?C# 2.0的匿名委托可以做到:

FindFiles(new Action<string>(delegate (string s) 
{ Console.WriteLine(s); }
));


我们不再需要定义foo了。

关于Lambda,其实第二章已经说过了,这里再提下,看你是否记得:

以上调用,我们还可以用Lambda进一步简化:

FindFiles(s => Console.WriteLine(s));


使用匿名委托还有什么好处呢?

我们可以直接访问主程序定义的变量,看如下代码:

string format = "filename: {0}";
FindFiles(s => Console.WriteLine(string.Format(format, s)));


我们吃惊地发现,format这个变量并不属于匿名函数,但是它的确可以被匿名函数“看到”。如果我们将这个委托的方法单独定义,恐怕除了将format定义成全局变量,没有什么办法让它在另一个方法中访问了。

但是反过来,我们不能在主程序中访问匿名委托中的变量,比如

string format = "filename: {0}";
FindFiles(s => Console.WriteLine(string.Format(format, s)));
Console.WriteLine(s);


第三行会报错。其实这个不难理解。

这种单向可见的关系,我们有个术语来称呼它,叫闭包(Closure)。

等等,使用匿名委托还有个好处,那就是我们甚至不用指定参数(如果我们没有在方法体中使用参数的话),比如:

void foo(Action<string> action)
{
action("hello world");
}


如果我们想这样调用:
foo(delegate(string s) { Console.WriteLine("call me"); });


我们看到,委托中我们没有使用参数s,那么,我们甚至可以将它省略:
foo(delegate { Console.WriteLine("call me"); });


注意,Lambda表达式就必须指定参数。

(6)静态类和扩展方法

如果一个类中定义的方法或字段,全部是静态的,我们可以干脆加上static修饰,让它成为静态类。加上static后,可以避免我们不小心在其中定义方法或者字段的时候忘记加上static,定义成成员变量或者方法,编译器会检查并确保这一点。静态类也不允许实例化或者再被继承。因此它很适合作为全局变量和全局方法的收容所。其实说到底,静态类不过是C#编译器的魔术,它其实是抽象类(Abstract)、密封类(Sealed)以及封闭了无参公有构造函数的类。

我们知道,调用一个静态方法,我们使用 类型.关键字 的形式。比如:

static class A
{
public static int SpaceCount(string s)
{
int r = 0;
foreach (char c in s)
if (c == ' ') r++;
return r;
}
}
class Program
{
static void Main(string[] args)
{
string s = "Hello World";
Console.WriteLine(A.SpaceCount(s));
}
}


我们可以将SpaceCount定义成扩展方法,使它看起来像string类的成员函数:
static class A
{
public static int SpaceCount(this string s)
{
int r = 0;
foreach (char c in s)
if (c == ' ') r++;
return r;
}
}
class Program
{
static void Main(string[] args)
{
string s = "Hello World";
Console.WriteLine(s.SpaceCount());
}
}


我们在参数s前加上了this修饰。而在Main中,我们不再需要关心SpaceCount出自哪个类了。扩展方法不但可以作用于类类型上,也可以作用于接口类型上:

static class A
{
public static int foo(this IConvertible val)
{
return Convert.ToInt32(val);
}
}
class Program
{
static void Main(string[] args)
{
string s = "1";
int i = s.foo();
float f = 2.0f;
int j = f.foo();
}
}

这样一来,我们怎么知道一个方法是对象的成员方法还是扩展方法呢?VS的自动完成列表会有提示,扩展方法会有一个向下的箭头。另外,扩展方法的优先级比成员方法低。比如说有人恶作剧,想给string加上一个ToUpper方法,对原来的那个来个偷梁换柱,呵呵,这是办不到滴。

(7)yield关键字、集合初始化器
这部分放在第五章、第六章来讲。

另外C#还有一些别的语法,比如重载运算符、分部类、分部方法、动态、异步、可选参数、命名参数,以及C#1.x就有的事件、索引器等等,这些可以在msdn中查找或者阅读相关书籍。至于linq,是本文的重点,后面会详细讲。

下一篇
...全文
961 11 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
不吃猫的老鼠 2014-05-15
  • 打赏
  • 举报
回复
学习下,增长知识
xiaoyaoju 2014-05-15
  • 打赏
  • 举报
回复
很多都不会,面试会很凄惨么?只会用笨的方法干活
lele_nancy 2014-05-14
  • 打赏
  • 举报
回复
Thank you !
朝露昙花 2014-05-14
  • 打赏
  • 举报
回复
介绍很详细.................
欢乐的小猪 2014-05-13
  • 打赏
  • 举报
回复
yqb_last 2014-05-13
  • 打赏
  • 举报
回复
继续看看那
tangyi321 2014-05-13
  • 打赏
  • 举报
回复
谢谢楼主的精彩讲解 收藏了 慢慢消化吧

111,088

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • AIGC Browser
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧