65.9K
CodeProject 正在变化。 阅读更多。
Home

如何使用谓词委托和匿名方法创建类似 LINQ 的功能

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.69/5 (5投票s)

2008 年 1 月 6 日

CDDL

4分钟阅读

viewsIcon

44372

了解如何使用谓词委托、匿名方法、泛型、Action 委托来实现类似 LINQ 的 WHERE 和 ORDERBY

引言

今天,我将作为您的向导,帮助您为自定义集合(例如 Employee、Order、Account 等)赋予类似 LINQ 的 WhereOrderBy 功能(您可以随时将其与传统 SQL 查询语法进行匹配)。并通过了解一些较新的 C# 功能——谓词委托、Action 委托、匿名方法和泛型——来帮助您理解我们能实现什么。

在着手创建此类功能之前,让我们先理解几个基本概念

  1. 谓词委托 (Predicate Delegate): 我们使用谓词委托来代替一个特殊的委托(函数指针),它只接受一个参数并返回一个布尔值。这种类型的委托由 .NET Framework 的 List 类特别使用。为了更好地理解这一点,请阅读我之前关于谓词委托的帖子
  2. 匿名方法 (Anonymous Method): 这些可以被视为作为参数传递给函数调用的代码块。一种内联函数。为了更好地理解这一点,请阅读我之前关于匿名方法的帖子

背景

我们将从理解我们想要实现的目标开始。我们想要一个泛型列表,它支持以下功能:

  1. 按集合中对象的任何属性进行排序,并且无需编写任何自定义比较器。一旦您读完 David Hayden 的博客,其中他谈到了如何创建和使用自定义比较器,您就能理解为了支持按自定义属性排序,我们需要编写多少代码。例如,我们可能有一个 Person 集合,并希望按“Lastname”、“Firstname”和“Age”排序。

    这个需求意味着为我们的自定义泛型列表提供一个通用的 "OrderBy" 子句/方法。

    所以,我版本的 David 的代码可能会如下所示:
    //Create Person collection
    LinqList<Person> people = new LinqList<Person>();
    people.Add(new Person("John","Doe", 76));
    people.Add(new Person("Abby","Normal", 25));
    people.Add(new Person("Jane","Doe", 84));
    
    //Sort by Lastname
    DisplayPeople(people.OrderBy<string>("Lastname"));
    
    //Sort by Age
    DisplayPeople(people.OrderBy<int>("Age"));
    
    //Sort by Firstname
    DisplayPeople(people.OrderBy<string>("Firstname"));
  2. 按集合中对象的任何属性进行搜索/查找。信不信由你,这同样需要我们编写自定义比较器;就像我们在David Hayden 的博客上看到的那样。例如,我们可能有一个 Person 集合,并希望搜索 Lastname="Doe" 或 Age=25。

    这个需求意味着为我们的自定义泛型列表提供一个通用的 "Where" 子句/方法。

    我的代码可能会如下所示:

    //Create Person collection
    LinqList<Person> people = new LinqList<Person>();
    people.Add(new Person("John","Doe", 76));
    people.Add(new Person("Abby","Normal", 25));
    people.Add(new Person("Jane","Doe", 84));
    
    //Search where Lastname = 'Doe'
    DisplayPeople(people.Where<string>("Lastname", "Doe"));
    
    //Search where Age = 25
    DisplayPeople(people.Where<int>("Age", 25));

我将把这个泛型列表称为 "LinqList",因为我正尽我所能地模仿LINQ 的功能,它将具备使用 "Where" 进行搜索和使用 "OrderBy" 进行排序的能力。

那么,我们如何设计类似 Linq 的功能呢?

既然我要负责设计,那么我就有责任帮助您将这些功能映射到 C# 提供的功能上。"Where" 对应 GenericList.FindAll(),"OrderBy" 对应 GenericList.Sort()。您可能在想,既然这些已经存在了,为什么还要重复造轮子呢?

我发誓我没有欺骗您…… C# 总是需要自定义的具体比较器来执行这两种操作——Find()/FindAll() 和 Sort()。为了提供一个通用的内部比较器,我将利用我们从过去的故事中学到的知识。

  1. 谓词委托
  2. 匿名方法
  3. 泛型方法和泛型集合

基本设计包含两个组件:

1) Finder - 用于支持 GenericFind() 和 GenericSort()

Finder

2) LinqList - 用于消费 Finder 并将普通泛型列表变成类似 LINQ 的列表。

LinqList

理解 Finder 的设计

让我向您展示 Finder.GenericFind() 的代码。

/// <summary>
/// Perform find() operation
/// </summary>
/// <param name="collectionToBeSearched">Give the list to be searched</param>
/// <param name="criteria">Criteria to be compared</param>
/// <param name="fieldName">Name of the property to be compared</param>
/// <param name="finderOption">Types of Search Options to be used with Finder</param>
public List<listType> GenericFind(List<listType> collectionToBeSearched, fieldType criteria, string propertyName, SearchOptions finderOption)
{
    List<listType> findResults = collectionToBeSearched.FindAll(
        delegate(listType input)
        {
            bool result = false;

            fieldType currentValue = (fieldType)input.GetType().GetProperty(propertyName).GetValue(input, null);
            result = Compare<fieldType>(currentValue, criteria, finderOption);

            return result;
        });

    return findResults;
}
/// <summary>
/// Compare object of type IComparable using CompareTo()
/// T defines the type of objects to be compared
/// </summary>
private bool Compare<T>(T value, T criteria, SearchOptions finderOption) where T : IComparable<T>
{
    bool result = false;

    switch (finderOption)
    {
        case SearchOptions.GreaterThan:
            result = (value.CompareTo(criteria) > 0);
            break;
        case SearchOptions.GreaterThanOrEqualTo:
            result = (value.CompareTo(criteria) >= 0);
            break;
        case SearchOptions.LessThan:
            result = (value.CompareTo(criteria) < 0);
            break;
        case SearchOptions.LessThanEqualTo:
            result = (value.CompareTo(criteria) <= 0);
            break;
        case SearchOptions.EqualTo:
            result = (value.CompareTo(criteria) == 0);
            break;
        case SearchOptions.NotEqualTo:
            result = (value.CompareTo(criteria) != 0);
            break;
    }

    return result;
}

在 GenericFind() 方法中,我通过谓词委托匿名方法检查条件,然后使用具体反射动态读取指定属性的值,然后使用基于泛型的 IComparable<T> 来评估“读取的属性值”与条件。

还有另一个方法,Finder.GenericSort();是时候看看代码了 :)

/// <summary>
/// Perform sort() operation
/// </summary>
/// <param name="collectionToBeSearched">Give the list to be searched</param>
/// <param name="criteria">Criteria to be compared</param>
/// <param name="fieldName">Name of the property to be compared</param>
/// <param name="sortOption">Types of Sort Options to be used with Finder</param>
public List<listType> GenericSort(List<listType> collectionToBeSearched, string propertyName, SortOptions sortOption)
{
    collectionToBeSearched.Sort(
        delegate(listType c1, listType c2)
        {
            int result;

            fieldType valueToCompareWith = (fieldType)c1.GetType().GetProperty(propertyName).GetValue(c1, null);
            fieldType valueToBeCompared = (fieldType)c2.GetType().GetProperty(propertyName).GetValue(c2, null);

            if (sortOption == SortOptions.Ascending)
                result = Comparer<fieldType>.Default.Compare(valueToCompareWith, valueToBeCompared);
            else
                result = Comparer<fieldType>.Default.Compare(valueToBeCompared, valueToCompareWith);

            return result;
        });

    return collectionToBeSearched;
}

在 GenericSort() 中,我使用匿名方法来消费 List<T>:Sort 方法 (IComparer<T>),然后使用具体反射动态读取指定属性的值,然后使用基于泛型的 IComparer<T> 来比较“指定属性的值”。

为了补充 GenericFind() 和 GenericSort(),我使用了枚举——SearchOptions 和 SortOptions。我将向您展示其自解释的代码。

/// <summary>
/// Types of Search Options to be used with Finder
/// </summary>
public enum SearchOptions
{
    GreaterThan,
    GreaterThanOrEqualTo,
    LessThan,
    LessThanEqualTo,
    EqualTo,
    NotEqualTo
}

/// <summary>
/// Types of Sort Options to be used with Finder
/// </summary>
public enum SortOptions
{
    Ascending,
    Descending
}

理解 LinqList 的设计

LinqList 的代码库相当简单。我只是从 Generic List<T> 继承它,并添加了两个基于泛型的方法——Where 和 OrderBy;它们分别在内部调用 Finder.GenericFind() 和 Finder.GenericSort()。

/// <summary>
/// Use this class enrich your collection
/// with LINQ type functionality
/// </summary>
/// <typeparam name="T"></typeparam>
public class LinqList<T> : List<T>
{
    /// <summary>
    /// Search within your collection
    /// </summary>
    public LinqList<T> Where<R>(string propertyName, R criteria, SearchOptions findOption) where R : IComparable<R>
    {
        return new FinderUsingLinq<T, R>().GenericFind(this, criteria, propertyName, findOption);
    }

    /// <summary>
    /// Search within your collection
    /// using default EqualTo SearchOption
    /// </summary>
    public LinqList<T> Where<R>(string propertyName, R criteria) where R : IComparable<R>
    {
        return Where<R>(propertyName, criteria, SearchOptions.EqualTo);
    }

    /// <summary>
    /// Sort your collection
    /// </summary>
    public LinqList<T> OrderBy<R>(string propertyName, SortOptions sortOption) where R : IComparable<R>
    {
        //Perform the generic sort
        new FinderUsingLinq<T, R>().GenericSort(this, propertyName, sortOption);

        return this;
    }

    /// <summary>
    /// Sort your collection
    /// using default Ascending SortOption
    /// </summary>
    /// <typeparam name="R"></typeparam>
    /// <param name="propertyName"></param>
    /// <returns></returns>
    public LinqList<T> OrderBy<R>(string propertyName) where R : IComparable<R>
    {
        return OrderBy<R>(propertyName, SortOptions.Ascending);
    }
}

让我们使用新创建的 LinqList 来进行搜索和排序。

我将使用 David 的 person 类。

public class Person
    {
        #region Private Members

        private string _firstname;
        private string _lastname;
        private int _age;

        #endregion

        #region Properties

        public string Firstname
        {
            get { return _firstname; }
            set { _firstname = value; }
        }

        public string Lastname
        {
            get { return _lastname; }
            set { _lastname = value; }
        }

        public int Age
        {
            get { return _age; }
            set { _age = value; }
        }

        #endregion

        #region Contructors

        public Person(string firstname, string lastname, int age)
        {
            _firstname = firstname;
            _lastname = lastname;
            _age = age;
        }

        #endregion
    }

对搜索和排序功能的代码感兴趣吗?

    //Create Person collection
    LinqList<Person> people = new LinqList<Person>();
    people.Add(new Person("John","Doe", 76));
    people.Add(new Person("Abby","Normal", 25));
    people.Add(new Person("Jane","Doe", 84));
    
    //Sort by Lastname
    DisplayPeople(people.OrderBy<string>("Lastname"));
    //Sort by Age
    DisplayPeople(people.OrderBy<int>("Age"));
    //Sort by Firstname
    DisplayPeople(people.OrderBy<string>("Firstname"));
    //Search where Lastname = 'Doe'
    DisplayPeople(people.Where<string>("Lastname", "Doe"));
    //Search where Age = 25
    DisplayPeople(people.Where<int>("Age", 25));
    
    //Search where Lastname = 'Doe' and sort the resultset by Age
    DisplayPeople(people.Where<string>("Lastname", "Doe").OrderBy<int>("Age"));
    

想知道 DisplayPeople(); 的作用吗?在这里查看。

//Iterate through the collection and print person details
private static void DisplayPeople(LinqList<Person> people)
{
    Console.WriteLine("******** start ***********");

    //Print people list
    people.ForEach(delegate(Person person)
    {
        Console.WriteLine("{0} {1}, Age = {2}", person.Lastname, person.Firstname, person.Age);
    }
    );

    Console.WriteLine("******** end *************");
    Console.WriteLine();
}

最后但同样重要:)

在这篇文章中,我解释了如何使用 C# 2.0 框架的一些非常酷的功能来实现诸如“搜索”和“排序”之类的简单且广泛使用的功能。如果您阅读了 Where 和 OrderBy 的语法,您就能轻松感受到基于流程的编程的力量。示例:

//搜索 Lastname = 'Doe',然后按 Age 对结果集进行排序DisplayPeople(people.Where<string>("Lastname", "Doe").OrderBy<int>("Age"));

提示:我不确定有多少读者注意到了 DisplayPeople()。此方法使用了 C# 的另一个特性——List<T>.ForEach(),它使用 Action(T) 委托。要了解更多关于 Action(T) 委托的信息,请阅读我的另一篇文章——灯光 .. 摄像 .. 行动 (Action 委托)

有关更多详细信息和更新的文章,请参阅我在 http://www.domaindrivendesign.info/2008/01/using-predicate-delegate-anonymous.html 的帖子。

如果您是一名有抱负的架构师,并希望了解更多关于我们如何设计更好的解决方案,您可以访问我的博客 http://www.domaindrivendesign.info

© . All rights reserved.