美橙西安网站备案拍照,有限公司和股份公司区别,网络营销策划有哪些,广州网站维护制作阅读本文大概需要 13 分钟。大家好#xff0c;这是 [C#.NET 拾遗补漏] 系列的第 08 篇文章#xff0c;今天讲 C# 强大的 LINQ 查询。LINQ 是我最喜欢的 C# 语言特性之一。LINQ 是 Language INtegrated Query 单词的首字母缩写#xff0c;翻译过来是语言集成查询。它为查询跨… 阅读本文大概需要 13 分钟。大家好这是 [C#.NET 拾遗补漏] 系列的第 08 篇文章今天讲 C# 强大的 LINQ 查询。LINQ 是我最喜欢的 C# 语言特性之一。LINQ 是 Language INtegrated Query 单词的首字母缩写翻译过来是语言集成查询。它为查询跨各种数据源和格式的数据提供了一致的模型所以叫集成查询。由于这种查询并没有制造新的语言而只是在现有的语言基础上来实现所以叫语言集成查询。一些基础在 C# 中从功能上 LINQ 可分为两类LINQ to Object 和 LINQ to Provider如XML从语法上 LINQ 可以分为 LINQ to Object 和 LINQ 扩展方法。大多数 LINQ to Object 都可以用 LINQ 扩展方法实现等同的效果而且平时开发中用的最多的是 LINQ 扩展方法。LINQ to Object 多用于映射数据库的查询LINQ to XML 用于查询 XML 元素数据。使用 LINQ 查询的前提是对象必须是一个 IEnumerable 集合注意为了描述方便本文说的集合都是指 IEnumerable 对象包含字面上的 ICollection 对象。另外LINQ 查询大多是都是链式查询即操作的数据源是 IEnumerableT1 类型返回的是 IEnumerableT2 类型。形如下面这样的查询就是 LINQ to Objectvar list from user in userswhere user.Name.Contains(Wang)select user.Id;等同于使用下面的 LINQ 扩展方法var list users.Where(u user.Name.Contains(Wang)).Select(u u.id);LINQ 查询支持在语句中间根据需要定义变量比如取出数组中平方值大于平均值的数字int[] numbers { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var result from number in numberslet average numbers.Average()let squared Math.Pow(number, 2)where squared averageselect number;
// 平均值为 4.5, result 为 { 3, 4, 5, 6, 7, 8, 9 }其中的 Select 方法接收的参数用的最多的是 FuncTSource, TResult它还可以接收 FuncTSource, int, TResult 参数示例:var collectionWithRowNumber collection..Select((item, index) new { Item item, RowNumber index }).ToList();再来看一下 LINQ to XML 的示例。假如我们有如下 XML 文件?xml version1.0 encodingutf-8 ?
EmployeesEmployeeEmpId1/EmpIdNameLiam/NameSex男/Sex/EmployeeEmployeeEmpId2/EmpId.../Employee
/Employees使用 LINQ to XML 查询所有含有指定节点值的元素XElement xelement XElement.Load(Employees.xml);
var els from el in xelement.Elements(Employee)where (string)el.Element(Sex) Maleselect el;等同于使用 LINQ 扩展方法var els xelement.Elements(Employee).Where(el (string)el.Element(Sex) Male);LINQ to XML 操作 XML 非常方便和灵活大家可以在具体使用的时候去探索这里就不展开讲了。LINQ 查询有很多方法由于篇幅原因就不一一列举演示了这里只选取一些强大的查询方法这些方法若使用非 LINQ 来实现可能会比较麻烦。LINQ 之所以强大是因为它可以轻松实现复杂的查询下面我们来总结一下 C# LINQ 的强大之处。First、Last 和 Single 等First、FirstOrDefault、Last、LastOrDefault、Single 和 SingleOrDefault 是快速查询集合中的第一个或最后一个元素的方法。如果集合是空的Fist、Last 和 Single 都会报错如果使其不报错而在空集合时使用默认值可以使用 FirstOrDefault、LastOrDefault 和 SingleOrDefault。Single/SingleOrDefault 和其它方法的区别是它限定查询结果只有一个元素如果查询结果集合中包含多个元素时会报错。具体看下面几个示例new[] { a, b }.First(x x.Equals(b)); // 返回 ”b“
new[] { a, b }.First(x x.Equals(c)); // 抛出 InvalidOperationException 异常
new[] { a, b }.FirstOrDefault(x x.Equals(c)); // 返回 nullnew[] { a, b }.Single(x x.Equals(b)); // 返回 ”b“
new[] { a, b }.Single(x x.Equals(c)); // 抛出 InvalidOperationException 异常
new[] { a, b }.SingleOrDefault(x x.Equals(c)); // 返回 null
new[] { a, a }.Single(); // 抛出 InvalidOperationException 异常在实际应用中如果要确保查询结果的唯一性比如通过手机号查询用户使用 Single/SingleOrDefaut其它情况应尽量使用 First/FirstOrDefault。虽然 FirstOrDefault 也可以根据条件判断元素是否存在但使用 Any 更高效。Except 取差集LINQ 的 Except 方法用来取差集即取出集合中与另一个集合所有元素不同的元素。示例int[] first { 1, 2, 3, 4 };
int[] second { 0, 2, 3, 5 };
IEnumerableint result first.Except(second);
// result { 1, 4 }注意 Except 方法会去除重复元素int[] second { 0, 2, 3, 5 };
int[] third { 1, 1, 1, 2, 3, 4 };
IEnumerableint result third.Except(second);
// result { 1, 4 }对于简单类型int、float、string 等使用 Except 很简单但对于自定义类型或者叫复合类型下同的 Object 如何使用 Except 呢此时需要将自定义类型实现IEquatableT接口示例class User : IEquatableUser
{public string Name { get; set; }public bool Equals(User other){return Name other.Name;}public override int GetHashCode(){return Name?.GetHashCode() ?? 0;}
}class Program
{static void Main(string[] args){var list1 new ListUser{new User{ Name User1},new User{ Name User2},};var list2 new ListUser{new User{ Name User2},new User{ Name User3},};var result list1.Except(list2);result.ForEach(u Console.WriteLine(u.Name));// 输出User1}
}SelectMany 集合降维SelectMany 可以把多维集合降维比如把二维的集合平铺成一个一维的集合。举例var collection new int[][]
{new int[] {1, 2, 3},new int[] {4, 5, 6},
};
var result collection.SelectMany(x x);
// result [1, 2, 3, 4, 5, 6]再来举个更贴合实际应用的例子。例如有如下实体类一个部门有多个员工class Department
{public Employee[] Employees { get; set; }
}class Employee
{public string Name { get; set; }
}此时我们拥有一个这样的数据集合var departments new[]
{new Department(){Employees new []{new Employee { Name Bob },new Employee { Name Jack }}},new Department(){Employees new []{new Employee { Name Jim },new Employee { Name John }}}
};现在我们可以使用 SelectMany 把各部门的员工查询到一个结果集中var allEmployees departments.SelectMany(x x.Employees);
foreach(var emp in allEmployees)
{Console.WriteLine(emp.Name);
}
// 依次输出Bob Jack Jim JohnSelectMany 迪卡尔积运算SelectMany 不光适用于单个包含多维集合对象的降维也适用于多个集合之前的两两相互操作比如进行迪卡尔积运算。比如我们有这样两个集合var list1 new Liststring { a1, a2 };
var list2 new Liststring { b1, b2, b3 };现在我们需要把它进行两两组合使用普通的方法我们需要用嵌套循环语句来实现var result newListstring();
foreach (var s1 in list1)foreach (var s2 in list2)result.Add(${s1}{s2});
// result [a1b1, a1b2, a1b3, a2b1, a2b2, a2b3]改用 SelectMany 实现var result list1.SelectMany(x list2.Select(y ${x}{y}));
// result [a1b1, a1b2, a1b3, a2b1, a2b2, a2b3]具有挑战性的问题来了如何对 N 个集合进行迪卡尔积运算呢比如有这样的集合数据var arrList new Liststring[]
{new string[] { a1, a2 },new string[] { b1, b2, b3 },new string[] { c1 },// ...
};如何对上面的 arrList 中的各个集合进行两两组合呢在电商业务尤其是零售业务中的产品组合促销中这种需求很常见。下面是一个使用 SelectMany 的实现需要用到递归class Program
{static void Main(string[] args){var arrList new Liststring[]{new string[] { a1, a2 },new string[] { b1, b2, b3 },new string[] { c1 },// ...};var result Recursion(arrList, 0, new Liststring());result.ForEach(x Console.WriteLine(x));}static Liststring Recursion(Liststring[] list, int start, Liststring result){if (start list.Count)return result;if (result.Count 0)result list[start].ToList();elseresult result.SelectMany(x list[start].Select(y x y)).ToList();result Recursion(list, start 1, result);return result;}
}输出a1b1c1
a1b2c1
a1b3c1
a2b1c1
a2b2c1
a2b3c1类似这种集合的迪卡尔积运算操作也可以用 LINQ to Object 来代替 SelectMany 实现result result.SelectMany(x list[start].Select(y x y)).ToList();
// 等同使用扩展方法
result (from a in result from b in list[start] select a b).ToList();LINQ to Object 比扩展方法看上去易读性更好但写起来扩展方法更方便。Aggregate 聚合Aggregate 扩展方法可以对一个集合依次执行类似累加器的操作就像滚雪球一样把数据逐步聚集在一起。比如实现从 1 加到 10用 Aggregate 扩展方法就很方便int[] numbers { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int sum numbers.Aggregate((prevSum, current) prevSum current);
// sum 55我们来解析一下它的执行步骤第一步prevSum 取第一个元素的值即 prevSum 1第二步把第一步得到的 prevSum 的值加上第二个元素即 prevSum prevSum 2依此类推第 i 步把第 i-1 得到的 prevSum 加上第 i 个元素再来看一个字符串的例子加深理解string[] stringList { Hello, World, ! };
string joinedString stringList.Aggregate((prev, current) prev current);
// joinedString Hello World !Aggregate 还有一个重载方法可以指定累加器的初始值。我们来看一个比较综合的复杂例子。假如我们有如下 1-12 的一个数字集合var items new Listint { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };现在我们想做如下计算计算集合元素的总数个数计算值为偶数的元素个数收集每第 4 个元素当然通过普通的循环遍历也可以实现这三个计算但使用 Aggregate 会更简洁下面是 Aggregate 的实现var result items.Aggregate(new { Total 0, Even 0, FourthItems new Listint() },(accum, item) new{Total accum.Total 1,Even accum.Even (item % 2 0 ? 1 : 0),FourthItems (accum.Total 1) % 4 0 ? new Listint(accum.FourthItems) { item } : accum.FourthItems}
);// result:
// Total 12
// Even 6
// FourthItems [4, 8, 12]这里为了简单起见使用匿名类型作为累加器的初始值由于匿名类型的属性是只读的所以在累加的过程都 new 了一个新对象。如果初始值使用的是自定义类型那累加时就不需 new 新对象了。Join 关联查询和 SQL 查询一样LINQ 同样支持 Inner Join、Left Join、Right Join、Cross Join 和 Full Outer Join有时候你可能看到不同的写法其实是同一个意思比如 Left Outer Join 就是 Left JoinJoin 是 Inner Join 省略了 Inner 等。假设我们有下面两个集合分别表示左边的数据和右边的数据。var first new Liststring() { a,b,c }; // 左边
var second new Liststring() { a, c, d }; // 右边下面以此数据为例来演示各种关联查询。Inner Joinvar result from f in firstjoin s in second on f equals sselect new { f, s };
// 等同使用扩展方法
var result first.Join(second,f f,s s,(f, s) new { f, s });// result: {a,a}
// {c,c}Left Joinvar result from f in firstjoin s in second on f equals s into tempfrom t in temp.DefaultIfEmpty()select new { First f, Second t };
// 或者
var result from f in firstfrom s in second.Where(x x f).DefaultIfEmpty()select new { First f, Second s };// 等同使用扩展方法
var result first.GroupJoin(second,f f,s s,(f, s) new { First f, Second s }).SelectMany(temp temp.Second.DefaultIfEmpty(),(f, s) new { First f.First, Second s });// result: {a,a}
// {b, null}
// {c,c}Right Joinvar result from s in secondjoin f in first on s equals f into tempfrom t in temp.DefaultIfEmpty()select new { First t, Second s };
// 其它和 Left Join 类似// result: {a,a}
// {c,c}
// {null,d}Cross Joinvar result from f in firstfrom s in secondselect new { f, s };// result: {a,a}
// {a,c}
// {a,d}
// {b,a}
// {b,c}
// {b,d}
// {c,a}
// {c,c}
// {c,d}Full Outer Joinvar leftJoin from f in firstjoin s in second on f equals s into tempfrom t in temp.DefaultIfEmpty()select new { First f, Second t };
var rightJoin from s in secondjoin f in first on s equals f into tempfrom t in temp.DefaultIfEmpty()select new { First t, Second s };
var fullOuterJoin leftJoin.Union(rightJoin);根据多个键关联在 SQL 中表与表进行关联查询时 on 条件可以指定多个键的逻辑判断用 and 或 or 连接。但 C# 的 LINQ 不支持 and 关键字若要根据多键关联需要把要关联的键值分别以相同的属性名放到匿名对象中然后使用 equals 比较两个匿名对象是否相等。示例var stringProps typeof(string).GetProperties();
var builderProps typeof(StringBuilder).GetProperties();
var query from s in stringPropsjoin b in builderPropson new { s.Name, s.PropertyType } equals new { b.Name, b.PropertyType }select new{s.Name,s.PropertyType};以上均使用两个集合做为示例LINQ 关联查询也支持多个集合关联就像 SQL 的多表关联只需往后继续追加 join 操作即可不再累述。LINQ 关联查与 SQL 相似但使用上有很大区别。LINQ 关联查询的用法有很多也很灵活不用刻意去记住它们只要熟悉简单常用的其它的在实际用到的时候再查询相关文档。Skip Take 分页Skip 扩展方法用来跳过从起始位置开始的指定数量的元素读取集合Take 扩展方法用来从集合中只读取指定数量的元素。var values new[] { 5, 4, 3, 2, 1 };
var skipTwo values.Skip(2); // { 3, 2, 1 }
var takeThree values.Take(3); // { 5, 4, 3 }
var skipOneTakeTwo values.Skip(1).Take(2); // { 4, 3 }Skip 与 Take 两个方法结合即可实现我们常见的分页查询public IEnumerableT GetPageT(this IEnumerableT collection, int pageNumber, int pageSize)
{int startIndex (pageNumber - 1) * pageSize;return collection.Skip(startIndex).Take(pageSize);
}使用过 EF (Core) 的同学一定很熟悉。另外还有 SkipWhile 和 TakeWhile 扩展方法它与 Skip 和 Take 不同的是它们的参数是具体的条件。SkipWhile 从起始位置开始忽略元素直到匹配到符合条件的元素停止忽略往后就是要查询的结果TakeWhile 从起始位置开始读取符合条件的元素一旦遇到不符合条件的就停止读取即使后面还有符合条件的也不再读取。示例SkipWhileint[] list { 42, 42, 6, 6, 6, 42 };
var result list.SkipWhile(i i 42);
// result: 6, 6, 6, 42TakeWhileint[] list { 1, 10, 40, 50, 44, 70, 4 };
var result list.TakeWhile(item item 50).ToList();
// result { 1, 10, 40 }Zip 拉链Zip 扩展方法操作的对象是两个集合它就像拉链一样根据位置将两个系列中的每个元素依次配对在一起。其接收的参数是一个 Func 实例该 Func 实例允许我们成对在处理两个集合中的元素。如果两个集合中的元素个数不相等那么多出来的将会被忽略。示例int[] numbers { 3, 5, 7 };
string[] words { three, five, seven, ignored };
IEnumerablestring zip numbers.Zip(words, (n, w) n w);foreach (string s in zip)
{Console.WriteLine(s);
}输出3three
5five
7sevenOfType 和 Cast 类型过滤与转换OfType 用于筛选集合中指定类型的元素Cast 可以把集合转换为指定类型但要求源类型必须可以隐式转换为目标类型。假如有如下数据interface IFoo { }
class Foo : IFoo { }
class Bar : IFoo { }var item0 new Foo();
var item1 new Foo();
var item2 new Bar();
var item3 new Bar();
var collection new IFoo[] { item0, item1, item2, item3 };OfType 示例var foos collection.OfTypeFoo(); // result: item0, item1
var bars collection.OfTypeBar(); // result: item2, item3
var foosAndBars collection.OfTypeIFoo(); // result: item0, item1, item2, item3// 等同于使用 Where
var foos collection.Where(item item is Foo); // result: item0, item1
var bars collection.Where(item item is Bar); // result: item2, item3Cast 示例var bars collection.CastBar(); // InvalidCastException 异常
var foos collection.CastFoo(); // InvalidCastException 异常
var foosAndBars collection.CastIFoo(); // OKToLookup 索引式查找ToLookup 扩展方法返回的是可索引查找的数据结构它是一个 ILookup 实例所有元素根据指定的键进行分组并可以按键进行索引。这样说有点抽象来看具体示例string[] array { one, two, three };
// 根据元素字符串长度创建一个查找对象
var lookup array.ToLookup(item item.Length);// 查找字符串长度为 3 的元素
var result lookup[3];
// result: one,two再来一个示例int[] array { 1,2,3,4,5,6,7,8 };
// 创建一个奇偶查找键为 0 和 1
var lookup array.ToLookup(item item % 2);// 查找偶数
var even lookup[0];
// even: 2,4,6,8// 查找奇数
var odd lookup[1];
// odd: 1,3,5,7Distinct 去重Distinct 方法用来去除重复项这个容易理解。示例int[] array { 1, 2, 3, 4, 2, 5, 3, 1, 2 };
var distinct array.Distinct();
// distinct { 1, 2, 3, 4, 5 }简单类型的集合调用 Distinct 方法使用的是默认的比较器Distinct 方法用此比较器来判断元素是否与其它元素重复但对于自定义类型要实现去重则需要自定义比较器。示例public class IdEqualityComparer : IEqualityComparerPerson
{public bool Equals(Person x, Person y) x.Id y.Id;public int GetHashCode(Person p) p.Id;
}public class Person
{public int Id { get; set; }public string Name { get; set; }
}class Program
{static void Main(string[] args){var people new ListPerson();var distinct people.Distinct(new IdEqualityComparer());}
}ToDictionary 字典转换ToDictionary 扩展方法可以把集合 IEnumerableTElement 转换为 DictionaryTKey, TValue 结构的字典它接收一个 FuncTSource, TKey 参数用来返回每个元素指定的键与值。示例IEnumerableUser users GetUsers();
Dictionaryint, User usersById users.ToDictionary(x x.Id);如果不用 ToDictionary你需要这样写IEnumerableUser users GetUsers();
Dictionaryint, User usersById new Dictionaryint, User();
foreach (User u in users)
{usersById.Add(u.Id, u);
}上面 ToDictionary 返回的字典数据中的值是整个元素你也可以通过它的第二个参数来自定义字典的值。示例Dictionaryint, string userNamesById users.ToDictionary(x x.Id, x x.Name);你也可以为转换的字典指定其键是否区分大小写即自定义字典的 IComparer示例Dictionarystring, User usersByCaseInsenstiveName users.ToDictionary(x x.Name,StringComparer.InvariantCultureIgnoreCase);var user1 usersByCaseInsenstiveName[liam];
var user2 usersByCaseInsenstiveName[LIAM];
user1 user2; // true注意字典类型要求所有键不能重复所以在使用 ToDictionary 方法时要确保作为字典的键的元素属性不能有重复值否则会抛出异常。其它常见扩展方法LINQ 还有很多其它常见的扩展方法大家在平时应该用的比较多比如 Where、Any、All 等这里也选几个简单举例介绍一下。Range 和 RepeatRange 和 Repeat 用于生成简单的数字或字符串系列。示例// 生成 1-100 的数字即结果为 [1, 2, ..., 99, 100]
var range Enumerable.Range(1, 100);// 生成三个重复的字符串“a”即结果为 [a, a, a]
var repeatedValues Enumerable.Repeat(a, 3);Any 和 AllAny 用来判断集合中是否存在任一一个元素符合条件All 用来判断集合中是否所有元素符合条件。示例var numbers new int[] {1, 2, 3, 4, 5 };
bool result numbers.Any(); // true
bool result numbers.Any(x x 6); // false
bool result numbers.All(x x 0); // true
bool result numbers.All(x x 1); // falseConcat 和 UnionConcat 用来拼接两个集合不会去除重复元素示例Listint foo newListint { 1, 2, 3 };
Listint bar newListint { 3, 4, 5 };
// 通过 Enumerable 类的静态方法
var result Enumerable.Concat(foo, bar).ToList(); // 1,2,3,3,4,5
// 通过扩展方法
var result foo.Concat(bar).ToList(); // 1,2,3,3,4,5Union 也是用来拼接两个集合与 Concat 不同的是它会去除重复项示例var result foo.Union(bar); // 1,2,3,4,5GroupBy 分组GroupBy 扩展方法用来对集合进行分组下面是一个根据奇偶进行分组的示例var list new Listint() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var grouped list.GroupBy(x x % 2 0);
// grouped: [1, 3, 5, 7, 9] 和 [2, 4, 6, 8]还可以根据指定属性进行分组public class Person
{public int Age { get; set; }public string Name { get; set; }
}var people new ListPerson();
var query people.GroupBy(x x.Age).Select(g { Age g.Key, Count g.Count() });DefaultIfEmpty 空替换在上面的关联查询中我们使用了 DefaultIfEmpty 扩展方法它表示在没有查询到指定条件的元素时使用元素的默认值代替。其实 DefaultIfEmpty 还可以指定其它的默认值示例var chars new Liststring() { a, b, c, d };
chars.Where(s s.Length 1).DefaultIfEmpty().First(); // 返回 null
chars.DefaultIfEmpty(N/A).FirstOrDefault(); // 返回 a
chars.Where(s s.Length 1).DefaultIfEmpty(N/A).FirstOrDefault(); // 返回 N/ASequenceEqual 集合相等SequenceEqual 扩展方法用于比较集合系列各个相同位置的元素是否相等。示例int[] a new int[] {1, 2, 3};
int[] b new int[] {1, 2, 3};
int[] c new int[] {1, 3, 2};bool result1 a.SequenceEqual(b); // true
bool result2 a.SequenceEqual(c); // false最后还有一些常用和简单的扩展方法就不举例了比如 OrderBy排序、Sum求和、Count计数、Reverse反转等同时欢迎大家补充本文遗漏的强大或好用的 LINQ 语法糖。