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






3.69/5 (5投票s)
了解如何使用谓词委托、匿名方法、泛型、Action 委托来实现类似 LINQ 的 WHERE 和 ORDERBY
引言
今天,我将作为您的向导,帮助您为自定义集合(例如 Employee、Order、Account 等)赋予类似 LINQ 的 Where 和 OrderBy 功能(您可以随时将其与传统 SQL 查询语法进行匹配)。并通过了解一些较新的 C# 功能——谓词委托、Action 委托、匿名方法和泛型——来帮助您理解我们能实现什么。
在着手创建此类功能之前,让我们先理解几个基本概念
- 谓词委托 (Predicate Delegate): 我们使用谓词委托来代替一个特殊的委托(函数指针),它只接受一个参数并返回一个布尔值。这种类型的委托由 .NET Framework 的 List 类特别使用。为了更好地理解这一点,请阅读我之前关于谓词委托的帖子。
- 匿名方法 (Anonymous Method): 这些可以被视为作为参数传递给函数调用的代码块。一种内联函数。为了更好地理解这一点,请阅读我之前关于匿名方法的帖子。
背景
我们将从理解我们想要实现的目标开始。我们想要一个泛型列表,它支持以下功能:
- 按集合中对象的任何属性进行排序,并且无需编写任何自定义比较器。一旦您读完 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"));
- 按集合中对象的任何属性进行搜索/查找。信不信由你,这同样需要我们编写自定义比较器;就像我们在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) Finder - 用于支持 GenericFind() 和 GenericSort()
2) LinqList - 用于消费 Finder 并将普通泛型列表变成类似 LINQ 的列表。
理解 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。