在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
C#语言的未来版本已经在最近的PDC上被揭露, 对象、XML、关系数据将被语言深度集成。这不是一个全新的发展方向,而是沿着C#一贯发展道路迈出的又一步。让我们从一个简单的例子——筛选一组符合条件的对象——中看看C#的发展是如何使我们的编程范式更简单和自然。 C#1.1时代的查询语句 我们有这样一个Employee类: class Employee { public string Name; public int Years; public string Department; } 若我们想为工龄超过4年的员工发放一只金表作为奖励,我们可能会这样写:
static Employee[] GoldWatch(Employee[] employees) { int resultCount = 0; for (int i = 0; i < employees.Length; ++i) { if (employees[i].Years > 3) { ++resultCount; } } Employee[] results = new Employee[resultCount]; for (int i = 0, j = 0; i < employees.Length; ++i) { if (employees[i].Years > 3) { results[j] = employees[i]; ++j; } } return results; }
这个方法首先计算符合条件的员工总数,据此创建一个合适大小的数组,然后用填充这个数组。这个方式是正确的,但你可能已经发现了一些问题:原数组被遍历了两次,规则(Years>3)被编码了两次,总的来说,对于这么一个简单的问题,这样的解决方法可能比我们想象的复杂了许多。下面的代码进行了一点简化:
static Employee[] GoldWatch(Employee[] employees) { ArrayList results = new ArrayList(); for (int i = 0; i < employees.Length; ++i) { if (employees[i].Years > 3) { results.Add(employees[i]); } } return (Employee[])results.ToArray(typeof(Employee)); }
现在,原数组只被遍历了一次。但却是以类型安全作为单价的,因为ArrayList包含的是object类型。代码末尾的复杂的cast就是这样做的后果之一。 上面的两个方法的伸缩性都存在问题:员工的集合一定要以数组形式给出,当然我们可以很容易写出支持别的集合类型的代码,但是为每一种类型都编写依次代码无疑是很浪费的。
我们再换一个方法:
static Employee[] GoldWatch(IEnumerable employees) { ArrayList results = new ArrayList(); foreach(Employee employee in employees) { if (employee.Years > 3) { results.Add(employee); } } return (Employee[])results.ToArray(typeof(Employee)); }
这段代码可以工作在数组,ArryaList等多种集合类型上,但是因为编译器不能确定employees是不是仅包括Employee对象,这样的后果是我们必须牺牲更多类型安全性。
这就是我们在C#1.1时代所能做到的。
C#2.0时代的查询语句 C#2.0为我们改善查询语句提供了一些新特性,我们首先来解决类型安全的问题:
static Employee[] GoldWatch(IEnumerable<Employee> employees) { List<Employee> results = new List<Employee>(); foreach (Employee employee in employees) { if (employee.Years > 3) { results.Add(employee); } } return results.ToArray(); }
Generic让我们可以应用这样简单的实现,同时保证完全的类型安全。
但我们还有一个潜在的性能问题,我们在返回前构造了整个满足要求的员工组成的列表。万一这样的员工有很多,万一调用着只需要处理少数几个员工的数据呢?
如果有这样的情况并且我们也能接受返回一个IEnumerable<Employee>,那么这样一个应用了迭代器的方法应该是更好的方法: static IEnumerable<Employee> GoldWatch(IEnumerable<Employee> employees) { foreach (Employee employee in employees) { if (employee.Years > 3) { yield return employee; } } } 现在没有了Employee的临时拷贝并且调用者也能决定他需要的返回数量,这段代码决不会干比要求得更多的事。
这是一段简单的可维护的代码。
看来我们已经解决了我们的问题了,太好了!
但是如果我们需要返回别的条件的员工时会怎么样呢?假设我们要返回销售部的所有员工。我们很容易可以写出新的SalesForce()函数,和Goldwatch()只有要满足的条件不同的新函数。这样的情况下,让我们来试试把这段重构得更好。
我们可能会用委托来实现一个通用的Filter方法: delegate bool Choose(Employee employee); static IEnumerable<Employee> Filter(IEnumerable<Employee> employees, Choose choose) { foreach (Employee employee in employees) { if (choose(employee)) { yield return employee; } } } static bool GoldWatchChoose(Employee employee) { return employee.Years>3; } static bool SalesForceChoose(Employee employee) { return employee.Department=="Sales"; } static IEnumerable<Employee> GoldWatch(IEnumerable<Employee> s) { return Filter(employees, new Choose(GoldWatchChoose)); } static IEnumerable<Employee> SalesForce(IEnumerable<Employee> s) { return Filter(employees, new Choose(SalesForceChoose)); }
这样的话,每次我们添加新的查询条件我们需要加入两个方法:一个查询代码和一个判断是狗满足条件的委托。方法这样的增长会造成可维护性上的问题。
我们可以应用匿名委托--C#2.0的新特性:
static IEnumerable<Employee> GoldWatch(IEnumerable<Employee> s) { return Filter(employees, delegate(Employee employee) { return employee.Years>3; } ); } static IEnumerable<Employee> SalesForce(IEnumerable<Employee> s) { return Filter(employees, delegate(Employee employee) { return employee.Department=="Sales"; } ); }
现在我们只需要为新的查询添加一个新函数就可以了。而且我们也能直接调用Filter(),这样的话一个新函数都不需要添加了。但是另一方面,看着一个小方法的实现作为参数被传到一个方法让人乍舌,而且也会减弱代码的可读性。
用C#2.0,我们能做的就这么多了。
C#3.0时代的查询语句
在C#3.0中我们又有了改善代码的新工具。
匿名委托很不错,但是我们希望有更简单的,更容易维护的代码。C#3.0提供了lamda表达式的概念(译者注:Hmm....Lisp)。你可以把lamda表达式是我们应用匿名委托的捷径,下面是用Lamda表达式重写的查询:
static IEnumerable<Employee> GoldWatch(IEnumerable<Employee> employees) { return Filter(employees, employee => employee.Years>3 ); } static IEnumerable<Employee> SalesForce(IEnumerable<Employee> employees) { return Filter(employees, employee => employee.Department=="Sales" ); }
这段代码相当简单而且也很容易维护,但还存在一些问题。
GoldWatch(employees); SalesForce(employees);
当你看到这样的调用的时候就会意识到这个问题,从OO的视角来看,我们已经熟悉了noun.verb()这样的调用形式,理想情况下,我们希望用这样的语法能查询一个集合: employees.GoldWatch(); employees.SalesForce();
有人可能会定义一个新的Employee类,它实现了IEnumerable<Employee>。但是问题是,我们的用户可能会希望是用别的IEnumerable<Employee>实现,比如Employee[]和List<Employee>。 C#3.0用扩展方法(Extension method)解决这个方法: static IEnumerable<Employee> Filter(this IEnumerable<Employee> employees, Choose choose) { foreach (Employee employee in employees) { if (choose(employee)) { yield return employee; } } } static IEnumerable<Employee> GoldWatch(this IEnumerable<Employee> employees) { return employees.Filter(employee => employee.Years>3); } static IEnumerable<Employee> SalesForce(this IEnumerable<Employee> employees) { return employees.Filter( employee => employee.Department=="Sales"); }
注意this关键字的使用,现在我们可以用成员方法的样式调用这些函数:
employees.GoldWatch(); employees.SalesForce(); employees.Filter(employee => employee.Department=="Sales");
用这样字的方式实现,我们的用户还能应用如下的链式调用来完成复杂的查询:
employees .GoldWatch() .SalesForce();
我们就能知道在销售部里,谁应该获得金表了。
这看起来很好了,但如果我们想象Employee一样查询Customer呢?或者说,查询我们的存货呢? 不用为每一个类单独写一个Filter方法,我们可以将Filter写成一个通用函数:
delegate bool Choose<T>(T t); static IEnumerable<T> Filter<T>(this IEnumerable<T> items, Choose<T> choose) { foreach (T item in items) { if (choose(item)) { yield return item; } } }
现在我们可以筛选我们希望的任何类型了!
int [] a = new int [] {1,2,3,4,5}; a.Filter(i => i==1 || i==3);
事实上,这个筛选方法是如此有用且通用,C#里已经内置了一个称为Where的实现,下面是在PDC上展示的实际的Where实现:
public delegate T Func<A0, T>(A0 arg0); public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate) { foreach (T element in source) {
2023-10-27 2022-08-15 2022-08-17 2022-09-23 2022-08-13 |
请发表评论