C# 3.0 给我们带来了什么?从C#1.1到LINQ的查询语句变化

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) {

        if (predicate(element)) yield return element;

    }

}

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页