LINQ(语言集成查询)LINQ (Language Integrated Query)

本文内容

什么是 LINQ?What is it?

LINQ 在 C# 和 VB 中提供语言级查询功能和高阶函数 API,以便能够编写具有很高表达力度的声明性代码。

语言级查询语法:

  1. var linqExperts = from p in programmers
  2. where p.IsNewToLINQ
  3. select new LINQExpert(p);

同一个示例使用 IEnumerable<T> API 的情况:

  1. var linqExperts = programmers.Where(p => p.IsNewToLINQ)
  2. .Select(p => new LINQExpert(p));

LINQ 具有很高的表达力度LINQ is Expressive

假设你有一份宠物列表,但想要将它转换为字典,以便可以使用宠物的 RFID 值直接访问宠物信息。

传统的命令性代码:

  1. var petLookup = new Dictionary<int, Pet>();
  2. foreach (var pet in pets)
  3. {
  4. petLookup.Add(pet.RFID, pet);
  5. }

代码的意图不是创建新的 Dictionary<int, Pet> 并通过循环在其中添加条目,而是将现有列表转换为字典!LINQ 维持这种意图,而命令性代码则不会。

等效的 LINQ 表达式:

  1. var petLookup = pets.ToDictionary(pet => pet.RFID);

使用 LINQ 的代码非常有效,因为在程序员的推理过程中,LINQ 能够在意图与代码之间找到合理的平衡。另一个好处就是精简代码。想像一下,如果能够像上面一样将大部分的基本代码减掉 1/3,情况会怎样?很爽,对吧?

LINQ 提供程序简化数据访问LINQ Providers Simplify Data Access

对于生产环境中的软件,其重要功能块的任务不外乎就是来自某些源(数据库、JSON、XML 等)的数据。通常,这就需要用户学习每个数据源的新 API,而这是一个枯燥的过程。LINQ 可将用于数据访问的常用元素抽象化成查询语法,不过你选择哪种数据源,这种语法看上去都是相同的,因而简化了此任务。

考虑以下操作:查找具有特定属性值的所有 XML 元素。

  1. public static IEnumerable<XElement> FindAllElementsWithAttribute(XElement documentRoot, string elementName,
  2. string attributeName, string value)
  3. {
  4. return from el in documentRoot.Elements(elementName)
  5. where (string)el.Element(attributeName) == value
  6. select el;
  7. }

为了执行此任务而编写代码来手动遍历 XML 文档会带来重重困难。

LINQ 提供程序的作用不仅仅是与 XML 交互。Linq to SQL 是适用于 MSSQL Server 数据库的极其简练的对象关系映射器 (ORM)。使用 JSON.NET 库可以通过 LINQ 有效遍历 JSON 文档。此外,如果没有哪个库可以解决你的需要,你还可以编写自己的 LINQ 提供程序)!

为何使用查询语法?Why Use the Query Syntax?

这是用户经常提出的一个问题。毕竟,下面的代码

  1. var filteredItems = myItems.Where(item => item.Foo);

要比下面的代码简洁得多:

  1. var filteredItems = from item in myItems
  2. where item.Foo
  3. select item;

难道 API 语法不比查询语法更简洁吗?

不是。查询语法允许使用 let 子句,这样,便可以在表达式的作用域内引入和绑定变量,然后在表达式的后续片段中使用该变量。只使用 API 语法重现相同的代码也是可行的,不过,这很可能会导致代码难以阅读。

那么,问题来了,只使用查询语法可以吗?

在以下情况下,此问题的答案是可以

  • 现有的基本代码已使用查询语法
  • 由于复杂性的问题,需要在查询中限定变量的作用域
  • 你偏好使用查询语法,并且它不会使基本代码变得混乱
    在以下情况下,此问题的答案是不可以

  • 现有的基本代码已使用 API 语法

  • 不需要在查询中限定变量的作用域
  • 你偏好使用 API 语法,并且它不会使基本代码变得混乱

重要片段示例Essential Samples

有关 LINQ 示例的完整列表,请访问 101 个 LINQ 示例

下面简单演示了 LINQ 的一些重要片段。没有办法演示完整的代码,因为 LINQ 提供的功能比此处演示的要多得多。

  • 语句构成 - WhereSelectAggregate
  1. // Filtering a list
  2. var germanShepards = dogs.Where(dog => dog.Breed == DogBreed.GermanShepard);
  3. // Using the query syntax
  4. var queryGermanShepards = from dog in dogs
  5. where dog.Breed == DogBreed.GermanShepard
  6. select dog;
  7. // Mapping a list from type A to type B
  8. var cats = dogs.Select(dog => dog.TurnIntoACat());
  9. // Using the query syntax
  10. var queryCats = from dog in dogs
  11. select dog.TurnIntoACat();
  12. // Summing the lengths of a set of strings
  13. int seed = 0;
  14. int sumOfStrings = strings.Aggregate(seed, (s1, s2) => s1.Length + s2.Length);
  • 平展列表的列表:
  1. // Transforms the list of kennels into a list of all their dogs.
  2. var allDogsFromKennels = kennels.SelectMany(kennel => kennel.Dogs);
  • 两个集之间的联合(使用自定义比较运算符):
  1. public class DogHairLengthComparer : IEqualityComparer<Dog>
  2. {
  3. public bool Equals(Dog a, Dog b)
  4. {
  5. if (a == null && b == null)
  6. {
  7. return true;
  8. }
  9. else if ((a == null && b != null) ||
  10. (a != null && b == null))
  11. {
  12. return false;
  13. }
  14. else
  15. {
  16. return a.HairLengthType == b.HairLengthType;
  17. }
  18. }
  19. public int GetHashCode(Dog d)
  20. {
  21. // default hashcode is enough here, as these are simple objects.
  22. return d.GetHashCode();
  23. }
  24. }
  25. ...
  26. // Gets all the short-haired dogs between two different kennels
  27. var allShortHairedDogs = kennel1.Dogs.Union(kennel2.Dogs, new DogHairLengthComparer());
  • 两个集之间的交集:
  1. // Gets the volunteers who spend share time with two humane societies.
  2. var volunteers = humaneSociety1.Volunteers.Intersect(humaneSociety2.Volunteers,
  3. new VolunteerTimeComparer());
  • 排序:
  1. // Get driving directions, ordering by if it's toll-free before estimated driving time.
  2. var results = DirectionsProcessor.GetDirections(start, end)
  3. .OrderBy(direction => direction.HasNoTolls)
  4. .ThenBy(direction => direction.EstimatedTime);
  • 最后,我们演示一个更高级的示例:确定相同类型的两个实例的属性值是否相等(该示例摘自此 StackOverflow 文章,不过已做修改):
  1. public static bool PublicInstancePropertiesEqual<T>(this T self, T to, params string[] ignore) where T : class
  2. {
  3. if (self == null || to == null)
  4. {
  5. return self == to;
  6. }
  7. // Selects the properties which have unequal values into a sequence of those properties.
  8. var unequalProperties = from property in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
  9. where !ignore.Contains(property.Name)
  10. let selfValue = property.GetValue(self, null)
  11. let toValue = property.GetValue(to, null)
  12. where !Equals(selfValue, toValue)
  13. select property;
  14. return !unequalProperties.Any();
  15. }

PLINQPLINQ

PLINQ(又称并行 LINQ)是 LINQ 表达式的并行执行引擎。换言之,LINQ 正则表达式可能会没有意义地在任意数量的线程之间并行化。为此,可以调用表达式前面的 AsParallel()

考虑以下情况:

  1. public static string GetAllFacebookUserLikesMessage(IEnumerable<FacebookUser> facebookUsers)
  2. {
  3. var seed = default(UInt64);
  4. Func<UInt64, UInt64, UInt64> threadAccumulator = (t1, t2) => t1 + t2;
  5. Func<UInt64, UInt64, UInt64> threadResultAccumulator = (t1, t2) => t1 + t2;
  6. Func<Uint64, string> resultSelector = total => $"Facebook has {total} likes!";
  7. return facebookUsers.AsParallel()
  8. .Aggregate(seed, threadAccumulator, threadResultAccumulator, resultSelector);
  9. }

此代码将会根据需要在系统线程之间将 facebookUsers 分区,累加每个并行线程上的类似项总计,累加每个线程计算的结果,然后将该结果投影为一个合理的字符串。

图示:

PLINQ 图示

可通过 LINQ 能够轻松表达的可并行化 CPU 密集型作业(即,没有副作用的纯函数)非常适合使用 PLINQ 来处理。对于确实有副作用的作业,请考虑使用任务并行库

其他资源:Further Resources:

  • 101 LINQ 示例
  • Linqpad,适用于 C#/F#/VB 的演练环境和数据库查询引擎
  • EduLinq,帮助用户了解如何实现 LINQ 到对象的电子书