LINQ体验(3)——C# 3.0新语言特性和改进(下篇)
2008-01-12 00:53 李永京 阅读(28073) 评论(40) 编辑 收藏 举报上一篇我们介绍了C# 3.0新语言特性和改进上部分,这篇我们继续介绍剩下的部分。
C# 3.0新语言特性和改进包括:
- 自动属性(Auto-Implemented Properties)
- 隐含类型局部变量(Local Variable Type Inference)
- 匿名类型(Anonymous Types)
- 对象与集合初始化器(Object and Collection Initializers)
- 扩展方法(Extension Methods)
- Lambda表达式和Lambda表达式树 (Lambda Expression and Lambda Expression Trees)
扩展方法(Extension Methods)
往往我们需要对CLR类型进行一些操作,但苦于无法扩展CLR类型的方法,只能创建一些helper方法,或者继承类。我们来修改上面的User类:
public class User { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } public string Read() { return "Id:" + Id + "姓名:" + Name + "年龄:" + Age; } }
然后调用
var user = new { Id = 1, Name = "YJingLee", Age = 22 }; var str = user.Read();
现在有了扩展方法就方便多了。
扩展方法允许开发人员往一个现有的CLR类型的公开契约(contract)中添加新的方法,而不用生成子类或者重新编译原来的类型。扩展方法有助于把今天动态语言中流行的对duck typing的支持之灵活性,与强类型语言之性能和编译时验证融合起来。——引用Scott博文
扩展方法是可以通过使用实例方法语法调用的静态方法。效果上,使得附加的方法扩展已存在类型和构造类型成为可能。他可以对现有类功能进行扩充,从而使该类型的实例具有更多的方法(功能)。
扩展方法允许我们在不改变源代码的情况下扩展(即添加不能修改)现有类型中的实例方法。
扩展方法给我们一个怎样的思路呢?我们一步一步做一下!
首先声明扩展方法:通过指定关键字this修饰方法的第一个参数。注意扩展方法仅可声明在静态类中。扩展方法具备所有常规静态方法的所有能力,可以使用实例方法语法来调用。接着就可以调用扩展方法了。下面通过一个具体的实例分析一下:
例如我们要检查一个字符串变量是否是合法的电子邮件地址?在.Net2.0框架下像这样:
var email = "leeyongjing@gmail.com"; if (EmailValidator.IsValid(email)) { Response.Write("YJingLee提示:这是一个正确的邮件地址"); }
而使用扩展方法的话,我可以添加“IsValidEmailAddress()”方法到string类本身中去,该方法返回当前字符串实例是否是个合法的字符串。
if (email.IsValidEmailAddress()) { Response.Write("YJingLee提示:这是一个正确的邮件地址"); }
我们是怎么把这个IsValidEmailAddress()方法添加到现有的string类里去的呢?先定义一个静态类,再定义“IsValidEmailAddress”这个静态的法来实现的。
public static class Extensions//静态类 { public static bool IsValidEmailAddress(this string s) //静态方法和this { Regex regex = new Regex(@"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"); return regex.IsMatch(s); } }
注意,上面的静态方法在第一个类型是string的参数变量前有个“this”关键词,这告诉编译器,这个特定的扩展方法应该添加到类型为“string”的对象中去。然后在IsValidEmailAddress()方法实现里,我可以访问调用该方法的实际string实例的所有公开属性/方法/事件,取决于它是否是合法电子邮件地址来返回true/false。
扩展方法不仅能够应用到个别类型上,也能应用到.NET框架中任何基类或接口上。即可用于整个.NET框架丰富的可组合的框架层扩展。
扩展方法要点
- 扩展方法的本质为将实例方法调用在编译期改变为静态类中的静态方法调用。事实上,它确实拥有静态方法所具有的所有功能。
- 扩展方法的作用域是整个namespace可见的,并且可以通过using namespace来导入其它命名空间中的扩展方法。
- 扩展方法的优先级:现有实例方法优先级最高,其次为最近的namespace下的静态类的静态方法,最后为较远的namespace下的静态类的静态方法。
- 扩展方法是一种编译时技术,注意与反射等运行时技术进行区别,并慎重使用。
Lambda表达式和Lambda表达式树 (Lambda Expression and Lambda Expression Trees)
Lambda表达式
我们从“所有字符串查找包含YJingLee子字符串”说起。在C# 2.0中,匿名方法允许我们以内联的方式来实现委托实例,它提供强大的函数式编程语言,但是标记显得相当的冗长和带有强制性。我们使用C# 2.0 中的匿名方法查找,代码如下:
var inString = list.FindAll(delegate(string s) { return s.Indexof("YJingLee") >= 0; });
现在可以使用C# 3.0带来的Lambda表达式允许我们使用一种更接近人的思维、更自然的方式来实现类似于匿名方法同样的效果,看下面的代码多么简洁:
var inString = list.FindAll(s => s.Indexof("YJingLee") >= 0);
Lambda表达式格式:(参数列表)=>表达式或语句块
具体意义:定义Lambda接受参数列表,运行表达式或语句块返回表达式或语句块的值传给这个参数列表。
Lambda表达式参数类型可以是隐式类型或显式类型。在显式列表中,每个参数的类型是显式指定的,在隐式列表中,参数的类型由Lambda表达式出现的语境自动推断类型。
Lambda表达式的参数列表可以有一个或多个参数,或者无参数。在有单一的隐型参数的lambda表达式中,圆括号可以从参数列表中省略。
例如:
(x, y) => x * y;//多参数,隐式类型=>表达式 x => x * 10;//单参数,隐式类型=>表达式 x => { return x * 10; }; //单参数,隐式类型=>语句块 (int x) => x * 10;//单参数,显式类型=>表达式 (int x) => { return x * 10; };//单参数,显式类型=>语句块 () => Console.WriteLine(); //无参数
下面看这个例子:
在前面的帖子中,我们写了一个User类及增加了2个人,接下来,我们使用由LINQ提供的新的Where和Average方法来返回集合中的人的一个子集,以及计算这个集合中的人的平均年龄:
List<User> user = new List<User>{ new User{Id=1,Name="YJingLee",Age=22}, new User{Id=2,Name="XieQing",Age=25}, }; //获取特定人时所用的过滤条件,p参数属于User类型 var results = user.Where(p => p.Name == "YJingLee").ToList(); //用User对象的Age值计算平均年龄 var average = user.Average(p => p.Age);
效果图如下:
对这个Lambda表达式做个简要分析:
var resultsdelegate = user.Where(delegate(User p) { return p.Name == "YJingLee";// 返回一个布尔值 }); var averagedelegate = user.Average(delegate(User p) { return p.Age; });
Lambda表达式L可以被转换为委托类型D,需要满足以下条件:
L的参数类型要与D的参数个数相等,类型相同,返回类型相同,无论是表达式,还是语句块。注意隐式类型要参与类型辨析。
Lambda表达式树
Lambda表达式树允许我们像处理数据(比如读取,修改)一样来处理Lambda表达式。我以一个例子简单说明:
Expression<Func<int, bool>> filter = n => (n * 3) < 5; BinaryExpression lt = (BinaryExpression)filter.Body; BinaryExpression mult = (BinaryExpression)lt.Left; ParameterExpression en = (ParameterExpression)mult.Left; ConstantExpression three = (ConstantExpression)mult.Right; ConstantExpression five = (ConstantExpression)lt.Right; var One = filter.Compile(); Console.WriteLine("Result: {0},{1}", One(5), One(1)); Console.WriteLine("({0} ({1} {2} {3}) {4})", lt.NodeType, mult.NodeType, en.Name, three.Value, five.Value);
效果图如下:
Lambda表达式和Lambda表达式树要点
- Lambda表达式的参数类型可以忽略,因为可以根据使用的上下文进行推断。
- Lambda表达式的主体(body)可以是表达式,也可以是语句块。
- Lambda表达式传入的实参将参与类型推断,以及方法重载辨析。
- Lambda表达式和表达式体可以被转换为表达式树。
- 表达式树允许lambda表达式能够代表数据结构替代表示为执行代码。
好了,我在这里简单的把C# 3.0新语言特性和改进说了一下,接下来,正式进入这个系列的主题部分——LINQ。为 了让大家了解,我换一种手法来写,从一条一条LINQ to SQL语句分析来贯穿LINQ的知识点。一起体验LINQ带给我们的乐趣。
本系列链接:LINQ体验系列文章导航
LINQ推荐资源
LINQ专题:http://kb.cnblogs.com/zt/linq/
关于LINQ方方面面的入门、进阶、深入的文章。
LINQ小组:http://space.cnblogs.com/group/linq/
学习中遇到什么问题或者疑问提问的好地方。