从匿名方法到LINQ 学习笔记
匿名方法->Lambda表达式->LINQ,每种技术似乎都是有源头的哦。
在 2.0 之前的 C# 版本中,声明委托的唯一方法是使用命名方法。C# 2.0 引入了匿名方法,而在 C# 3.0 及更高版本中,Lambda 表达式取代了匿名方法,作为编写内联代码的首选方式。有一种情况下,匿名方法提供了 Lambda 表达式中所没有的功能。匿名方法使您能够省略参数列表,这意味着可以将匿名方法转换为带有各种签名的委托。这对于 Lambda 表达式来说是不可能的,Lambda表达式加上扩展方法就成了LINQ
匿名方法:
没有匿名方法的时候(C# 1.0):
addButton.Click += new EventHandler(AddClick); void AddClick(object sender, EventArgs e) { listBox.Items.Add(textBox.Text); }
有了匿名方法之后(C#2.0):
addButton.Click += delegate { listBox.Items.Add(textBox.Text); };
匿名方法允许我以一种“内联”的方式来编写方法代码,将代码直接与委托实例相关联,从而使得委托实例化的工作更加直观和方便。
匿名方法的参数
匿名方法可以在delegate关键字后跟一个参数列表(可以不指定),后面的代码则可以访问这些参数:
addButton.Click += delegate(object sender, EventArgs e) { MessageBox.Show(((Button)sender).Text); };
注意“不指定参数列表”与“参数列表为空”的区别
addButton.Click += delegate {…} // 正确! addButton.Click += delegate() {…} // 错误!
匿名方法的返回值
如果委托类型的返回值类型为void,匿名方法里便不能返回任何值;
如果委托类型的返回值类型不为void,匿名方法里的返回的值必须和委托类型的返回值兼容:
delegate void MyDelegate(); MyDelegate d = delegate{ …… return; };
delegate int MyDelegate(); MyDelegate d = delegate{ …… return 100; };
匿名方法的外部变量
一些局部变量和参数有可能被匿名方法所使用,它们被称为“匿名方法的外部变量”。
外部变量的生存期会由于“匿名方法的捕获效益”而延长——一直延长到委托实例不被引用为止。
static void Foo(double factor) { Function f=delegate(int x) { factor +=0. 2; // factor为外部变量 return x * factor; }; Invoke(f); // factor的生存期被延长 }
与局部变量不同,捕获变量的生命周期一直持续到引用该匿名方法的委托符合垃圾回收的条件为止。
委托类型的推断
C#2.0允许我们在进行委托实例化时,省略掉委托类型,而直接采用方法名,C#编译器会做合理的推断。
C#1.0中的做法:
addButton.Click += new EventHandler(AddClick); Apply(a, new Function(Math.Sin));
C#2.0中的做法:
addButton.Click += AddClick; Apply(a, Math.Sin);
匿名方法机制
C#2.0中的匿名方法仅仅是通过编译器的一层额外处理,来简化委托实例化的工作。它与C#1.0的代码不存在根本性的差别。
通过ILDasm.exe反汇编工具,我们可以获得对匿名方法的深入了解:
静态方法中的匿名方法
public delegate void D(); static void F() { D d = delegate { Console.WriteLine("test"); }; }
上面的代码被编译器转换为:
static void F() { D d = new D(__Method1); } static void __Method1() { Console.WriteLine("test"); }
实例方法中的匿名方法
class Test { int x; void F() { D d = delegate { Console.WriteLine(this.x); }; } }
上面的代码被编译器转换为:
void F() { D d = new D(__Method1); } void __Method1() { Console.WriteLine(this.x); }
匿名方法中的外部变量
void F() { int y = 123; D d = delegate { Console.WriteLine(y); }; }
上面的代码被编译器转换为:
class __Temp { public int y; public void __Method1() { Console.WriteLine(y); } } void F() { __Temp t = new __Temp(); t.y = 123; D d = new D(t.__Method1); }
扩展方法
扩展方法能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用。调用扩展方法与调用在类型中实际定义的方法之间没有明显的差异。
在代码中,可以使用实例方法语法调用该扩展方法。但是,编译器生成的中间语言 (IL) 会将代码转换为对静态方法的调用。因此,并未真正违反封装原则。实际上,扩展方法无法访问它们所扩展的类型中的私有变量。
public static class Extensions{ public static void Foo(this string s) { ….. } } String s=“Hello,World”; s.Foo();
扩展方法允许我们在不改变源代码的情况下扩展(即添加)现有类型中的实例方法。
扩展方法要点
扩展方法的本质是将实例方法调用在编译期改变为静态类中的静态方法调用。
注意扩展方法的优先级:现有实例方法优先级最高,其次为最近的namespace下的静态类的静态方法,最后为较远的namespace下的静态类的静态方法。
扩展方法是一种编译时技术,注意与反射等运行时技术进行区别,并慎重使用。
Lambda表达式
使用C#2.0中的匿名方法查找“内部包含abc字串的所有字符串”:
list.FindAll( delegate(string s) { return s.Indexof(“abc”) > 0; } );
使用C#3.0中的Lambda表达式查找“内部包含abc字串的所有字符串”:
list.FindAll(s=>s.Indexof(“abc”) > 0);
C#2.0的匿名方法允许我们以内联的方式来实现委托实例,而C#3.0的Lambda表达式允许我们使用一种更接近人的思维、更自热的方式来实现类似于匿名方法同样的效果。
Lambda表达式格式为:
(参数列表)=>表达式或者语句块
可以有多个参数,一个参数,或者无参数。参数类型
可以隐式或者显式。例如:
(x, y) => x * y //多参数,隐式类型=> 表达式
x => x * 10 //单参数, 隐式类型=>表达式
x => { return x * 10; } //单参数,隐式类型=>语句块
(int x) => x * 10 // 单参数,显式类型=>表达式
(int x) => { return x * 10; } // 单参数,显式类型=>语句块
() => Console.WriteLine() //无参数
Lambda表达式格式要点
• Lambda表达式的参数类型可以忽略,因为可以根据使用的上下文进行推断。
• Lambda表达式的主体(body)可以是表达式,也可以是语句块。
•Lambda表达式传入的实参将参与类型推断,以及方法重载辨析。
•Lambda表达式表达式和表达式体可以被转换为表达式树。
Lambda表达式与委托类型
Lambda表达式L可以被转换为委托类型D,需要满足以下条件:
• L和D拥有相同的参数个数。
• L的参数类型要与D的参数类型相同。注意隐式类型要参与类型辨析。
• D的返回类型与L相同,无论L是表达式,还是语句块。
LINQ简介
OO(面向对象)以外的疆域:信息的访问与整合。关系数据库与XML为其中的典型应用。
• .NET Language Integrated Query (LINQ):不采用特定於关系数据库或者XML的专有方案,而采用通用方案来解决各种信息源的访问与整合问题。
• 在LINQ中,查询成为编程语言的一个组成部分,这使得查询表达式可以得到很好的编译时语法检查,丰富的元数据,智能感知等强类型语言的好处。
LINQ表达式
class app { static void Main() { string[] names = { "Burke", "Connor", "Frank", "Everett", "Albert", "George", "Harris", "David" }; IEnumerable<string> query = from s in names where s.Length == 5 orderby s select s.ToUpper(); foreach (string item in query) Console.WriteLine(item); } }
查询表达式解析
IEnumerable<string> query = from s in names where s.Length == 5 orderby s select s.ToUpper();
在语义上等同于如下“方法风格(基于方法)的查询”:
IEnumerable<string> query = names .Where(s => s.Length == 5) .OrderBy(s => s) .Select(s => s.ToUpper());
注意其中的参数为lambda 表达式,类似于如下委托:
Func<string, bool> filter = delegate (string s) { return s.Length == 5;}; Func<string, string> extract = delegate (string s) { return s; }; Func<string, string> project = delegate (string s) { return s.ToUpper(); }; IEnumerable<string> query = names.Where(filter) .OrderBy(extract) .Select(project);
查询操作符是LINQ中的另外一项重要设施,LINQ使用扩展方法来定义查询操作符,例如where操作符:
namespace System.Linq { public static class Enumerable { public static IEnumerable<T> Where<T>( this IEnumerable<T> source, Func<T, bool> predicate) { foreach (T item in source) if (predicate(item)) yield return item; } } }
普通的方式来调用扩展方法:
IEnumerable<string> query = Enumerable.Where(names, s => s.Length < 6);
C#语言允许我们使用如下的方式来调用扩展方法:
IEnumerable<string> query = names.Where(s => s.Length < 6);