• 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;

        }

    }



    Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1654477

  • 相关阅读:
    Android笔记——UI开发
    P2P网络借贷系统-核心功能-用户投标-业务解说
    java回调简单实现
    Java面向对象编程(二)
    POJ-1190-生日蛋糕-DFS(深搜)-枚举-多重剪枝
    设计模式笔记——装饰模式
    bbed初体验
    高速理解环境变量
    最短路算法之 Dijkstra算法
    C++课程资源下载问题
  • 原文地址:https://www.cnblogs.com/SoulStore/p/830415.html
Copyright © 2020-2023  润新知