总目录
前言
在 C# 中,System.Linq.Enumerable 类是 LINQ(Language Integrated Query)的核心组成部分,它提供了一系列静态方法,用于操作实现了 IEnumerable 接口的集合。通过这些方法,我们可以轻松地对集合进行查询、转换、排序和聚合等操作。
本文属于 C# Enumerable类 使用详解 中的一个章节,着重介绍 C# Enumerable 类中数据筛选这部分的内容。
一、概览
方法 | 描述 | 示例 |
---|---|---|
Where | 条件过滤 | nums.Where(n => n > 10) |
OfType | 类型过滤 | list.OfType<string>() |
Distinct | 去重 | names.Distinct() |
二、Where
:条件过滤
1. 什么是Where
Where 是 LINQ 提供的一个扩展方法,用于根据指定的谓词(predicate)筛选集合中的元素。它适用于实现了 IEnumerable 接口的任何类型,包括数组、列表和其他可枚举集合。
2. where 方法 基本信息
1) Where
Where
用于从集合中筛选出满足特定条件的元素。
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate);
- 参数:
source
:要筛选的源序列。predicate
:一个返回布尔值的函数,用于确定是否应包含某个元素。- 第一个重载仅传递元素本身给谓词函数。
- 第二个重载除了传递元素本身外,还传递该元素在序列中的索引位置。
- 返回值:一个包含满足谓词条件的元素的序列。
2)工作原理
Where
方法会遍历集合中的每个元素,并根据提供的谓词函数判断是否应包含该元素。如果谓词函数返回 true
,则该元素会被包含在结果集中;如果返回 false
,则忽略该元素。
3)使用场景
- 数据过滤:当需要从集合中筛选出满足某些条件的数据时,可以使用
Where
方法。 - 数据清洗:在处理用户输入或其他外部数据源时,筛选出有效或符合条件的数据。
- 性能优化:通过提前过滤掉不符合条件的数据,减少后续处理的数据量。
3. 使用示例
示例 1:基本用法
假设我们有一个包含数字的列表,我们可以使用 Where
方法筛选出偶数。
static void Main()
{
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
var evenNumbers = numbers.Where(n => n % 2 == 0);
Console.WriteLine(string.Join(",",evenNumbers));
//输出:2,4,6
}
在这个例子中,我们创建了一个包含数字的列表 numbers
,然后使用 Where
方法筛选出了所有偶数。
示例 2:复杂条件
我们可以在谓词函数中使用更复杂的逻辑来筛选数据。例如,筛选出所有偶数且小于10的数字。
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var filteredNumbers = numbers.Where(n => n % 2 == 0 && n < 10);
foreach (var num in filteredNumbers)
{
Console.WriteLine(num); // 输出: 2 4 6 8
}
}
}
在这个例子中,我们使用了复合条件来筛选出所有偶数且小于10的数字。
示例 3:结合其他 LINQ 方法
Where
可以与其他 LINQ 方法结合使用,进一步增强其功能。例如,我们可以先使用 Where
方法筛选出符合条件的元素,然后再使用 Select
方法进行转换。
using System;
using System.Collections.Generic;
using System.Linq;
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
class Program
{
static void Main()
{
List<Person> people = new List<Person>
{
new Person { Name = "Alice", Age = 30 },
new Person { Name = "Bob", Age = 25 },
new Person { Name = "Charlie", Age = 35 },
new Person { Name = "David", Age = 20 }
};
var filteredPeople = people
.Where(p => p.Age > 25) // 筛选出年龄大于25的人
.Select(p => p.Name); // 只选择名字
foreach (var name in filteredPeople)
{
Console.WriteLine(name); // 输出: Alice Charlie
}
}
}
在这个例子中,我们首先使用 Where
方法筛选出了年龄大于25的人,然后使用 Select
方法只选择了这些人的名字。
示例 4:带索引的筛选
有时我们需要根据元素在序列中的索引来筛选数据。这时可以使用第二个重载的 Where
方法,它不仅传递元素本身,还传递该元素在序列中的索引位置。
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
List<string> words = new List<string> { "apple", "banana", "cherry", "date", "elderberry" };
var filteredWords = words.Where((word, index) => word.Length > 5 || index % 2 == 0);
foreach (var word in filteredWords)
{
Console.WriteLine(word); // 输出: apple cherry date elderberry
}
}
}
在这个例子中,我们使用了带索引的 Where
方法,筛选出了长度大于5的单词或者索引为偶数的单词。
示例 5:带索引的筛选解析
int[] numbers = { 0, 30, 20, 15, 90, 85, 40, 75 };
IEnumerable<int> query =
numbers.Where((number, index) => number <= index * 10);
foreach (int number in query)
{
Console.WriteLine(number);
}
/*
This code produces the following output:
0
20
15
40
*/
- 第一个参数
number
表示要测试的元素。 第二个参数表示 中index
元素的从零开始的索引。 - 过滤过程,根据条件计算:
- 索引0:0 <= 0×10 → 0(保留)
- 索引1:30 <= 1×10 → 30>10(舍弃)
- 索引2:20 <= 2×10 → 20<=20(保留)
- 索引3:15 <=3×10 → 15<=30(保留)
- 索引4:90 <=4×10 → 90>40(舍弃)
- 索引5:85 <=5×10 → 85>50(舍弃)
- 索引6:40 <=6×10 → 40<=60(保留)
- 索引7:75 <=7×10 → 75>70(舍弃)
- 最终输出:0 20 15 40。
示例 6:自定义对象
当我们处理自定义对象时,可以根据对象的属性进行筛选。例如,筛选出所有年龄大于30岁的人。
using System;
using System.Collections.Generic;
using System.Linq;
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
class Program
{
static void Main()
{
List<Person> people = new List<Person>
{
new Person { Name = "Alice", Age = 30 },
new Person { Name = "Bob", Age = 25 },
new Person { Name = "Charlie", Age = 35 },
new Person { Name = "David", Age = 20 }
};
var filteredPeople = people.Where(p => p.Age > 30);
foreach (var person in filteredPeople)
{
Console.WriteLine($"{person.Name} ({person.Age})"); // 输出: Charlie (35)
}
}
}
在这个例子中,我们根据 Person
对象的 Age
属性筛选出了所有年龄大于30岁的人。
4. 注意事项
尽管 Where
方法非常有用,但在实际应用中也有一些需要注意的地方:
- 延迟执行:
Where
方法是延迟执行的,这意味着它不会立即对集合进行筛选操作,而是返回一个查询表达式。只有在实际遍历结果集时,才会执行筛选操作。这种设计可以提高性能,特别是在处理大型集合时。
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
var query = numbers.Where(n => n > 3);
Console.WriteLine("Before modifying the list:");
foreach (var num in query)
{
Console.WriteLine(num); // 输出: 4 5
}
numbers.Add(6);
Console.WriteLine("After modifying the list:");
foreach (var num in query)
{
Console.WriteLine(num); // 输出: 4 5 6
}
}
}
在这个例子中,我们看到即使在第一次遍历之后修改了原始集合,第二次遍历时仍然包含了新添加的元素。
-
谓词函数的性能:谓词函数的复杂度直接影响到
Where
方法的性能。尽量保持谓词函数简单高效,避免在谓词函数中执行耗时的操作。 -
不可变性:
Where
方法不会修改原始集合,而是返回一个新的集合。这意味着原始集合保持不变,新的集合只包含满足条件的元素。 -
空集合处理: 如果源集合为空,
Where
方法将返回一个空的结果集。因此,在使用Where
方法之前,通常不需要检查集合是否为空。
三、 OfType
:类型过滤
1. 什么是 OfType
OfType<T>
方法是 LINQ 提供的一个扩展方法,用于从集合中筛选出可以转换为指定类型的元素。它可以帮助我们在多态集合中提取特定类型的对象,过滤掉无法转换的元素。
2. OfType方法 基本信息
1)OfType
public static IEnumerable<TResult> OfType<TResult>(this IEnumerable source);
- 参数:
source
表示要筛选的源集合。 - 返回值:一个包含可以转换为指定类型的元素的
IEnumerable<TResult>
。
2)工作原理:
OfType<T>
方法会遍历集合中的每个元素,并尝试将其转换为目标类型 TResult
。如果转换成功,则该元素会被包含在结果集中;如果转换失败(例如元素不是目标类型的实例),则该元素会被忽略。
2)使用场景
- 多态集合:当集合中包含多种类型的对象时,可以使用
OfType<T>
提取特定类型的对象。 - 接口和基类:当集合中包含实现某个接口或继承自某个基类的对象时,可以使用
OfType<T>
过滤出特定类型或接口的实例。 - 类型安全操作:在对集合进行操作之前,确保所有元素都是期望的类型,避免运行时类型转换异常。
3. 使用示例
示例 1:基本用法
假设我们有一个包含不同类型的对象的集合,我们可以使用 OfType<T>
提取出特定类型的对象。
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
List<object> mixedList = new List<object>
{
1,
"Hello",
2.5,
true,
"World"
};
var strings = mixedList.OfType<string>();
foreach (var str in strings)
{
Console.WriteLine(str); // 输出: Hello World
}
}
}
在这个例子中,我们创建了一个包含不同类型对象的列表 mixedList
,然后使用 OfType<string>()
提取了所有字符串类型的元素。
示例 2:多态集合
假设我们有一个包含多个派生类对象的集合,可以使用 OfType<T>
提取特定派生类的实例。
using System;
using System.Collections.Generic;
using System.Linq;
class Animal { }
class Dog : Animal { }
class Cat : Animal { }
class Program
{
static void Main()
{
List<Animal> animals = new List<Animal>
{
new Dog(),
new Cat(),
new Dog(),
new Cat()
};
var dogs = animals.OfType<Dog>();
foreach (var dog in dogs)
{
Console.WriteLine("Dog instance found"); // 输出: Dog instance found Dog instance found
}
}
}
在这个例子中,我们创建了一个包含 Dog
和 Cat
对象的集合 animals
,然后使用 OfType<Dog>()
提取了所有 Dog
类型的实例。
示例 3:接口集合
假设我们有一个包含实现了某个接口的对象的集合,可以使用 OfType<T>
提取实现该接口的对象。
using System;
using System.Collections.Generic;
using System.Linq;
interface IAnimal
{
void Speak();
}
class Dog : IAnimal
{
public void Speak() => Console.WriteLine("Woof!");
}
class Cat : IAnimal
{
public void Speak() => Console.WriteLine("Meow!");
}
class Program
{
static void Main()
{
List<object> mixedList = new List<object>
{
new Dog(),
new Cat(),
"Hello",
42
};
var animals = mixedList.OfType<IAnimal>();
foreach (var animal in animals)
{
animal.Speak(); // 输出: Woof! Meow!
}
}
}
在这个例子中,我们创建了一个包含不同类型的对象的列表 mixedList
,然后使用 OfType<IAnimal>()
提取了所有实现了 IAnimal
接口的对象,并调用了它们的 Speak
方法。
示例 4:结合其他 LINQ 方法
OfType<T>
可以与其他 LINQ 方法结合使用,进一步增强其功能。例如,我们可以先使用 OfType<T>
提取特定类型的对象,然后再使用 Select
或 Where
等方法进行进一步处理。
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
List<object> mixedList = new List<object>
{
1,
"Hello",
2.5,
true,
"World"
};
var upperStrings = mixedList
.OfType<string>()
.Select(s => s.ToUpper());
foreach (var str in upperStrings)
{
Console.WriteLine(str); // 输出: HELLO WORLD
}
}
}
在这个例子中,我们首先使用 OfType<string>()
提取所有字符串类型的元素,然后使用 Select
方法将这些字符串转换为大写形式。
4. 注意事项
-
性能考虑:OfType 会遍历整个集合并尝试对每个元素进行类型转换。如果集合非常大,这可能会导致性能问题。因此,在处理大型集合时,应谨慎使用,并根据具体需求选择合适的方法。
-
类型安全性:虽然 OfType 可以帮助我们过滤出特定类型的元素,但它并不能防止所有类型的错误。例如,如果集合中包含不兼容类型的对象,这些对象会被忽略,但不会引发异常。因此,在使用 OfType 时,仍然需要确保集合中的数据符合预期。
-
与
Cast<T>
的区别Cast<T>
:尝试将集合中的每个元素强制转换为指定类型。如果转换失败,会抛出InvalidCastException
。OfType<T>
:仅保留可以成功转换为指定类型的元素,忽略无法转换的元素。- 因此,
OfType<T>
更加灵活和安全,特别是在处理可能包含多种类型对象的集合时。
四、Distinct:去重
1. 什么是 Distinct
Distinct 是 LINQ 提供的一个扩展方法,用于从集合中筛选出唯一的元素,去除重复项。它适用于实现了 IEnumerable 接口的任何类型,包括数组、列表和其他可枚举集合。
2. Distinct
方法基本信息
1)Distinct
Distinct
用于从集合中去除重复的元素。
public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source);
public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer);
- 参数:
source
:要筛选的源序列。comparer
(可选):用于比较元素的IEqualityComparer<T>
实现。
- 返回值:一个包含唯一元素的序列。
2) 工作原理
Distinct
方法会遍历集合中的每个元素,并使用默认的或自定义的比较器来判断元素是否已经存在于结果集中。如果元素是唯一的,则将其添加到结果集中;如果元素已经存在,则忽略该元素。
3)使用场景
- 去除重复数据:当集合中包含重复的数据时,可以使用
Distinct
方法去除这些重复项。 - 数据清洗:在处理用户输入或其他外部数据源时,去除重复项有助于提高数据质量。
- 性能优化:在某些高性能应用场景中,去除重复项可以减少不必要的计算和内存占用。
3. 使用示例
示例 1:基本用法
假设我们有一个包含重复数字的列表,我们可以使用 Distinct
方法去除这些重复项。
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
List<int> numbers = new List<int> { 1, 2, 2, 3, 4, 4, 5 };
var distinctNumbers = numbers.Distinct();
foreach (var num in distinctNumbers)
{
Console.WriteLine(num); // 输出: 1 2 3 4 5
}
}
}
在这个例子中,我们创建了一个包含重复数字的列表 numbers
,然后使用 Distinct
方法去除了重复项。
示例 2:自定义对象去重
当我们处理自定义对象时,默认情况下,Distinct
方法会使用对象的引用进行比较。为了实现基于对象属性的比较,我们需要提供一个自定义的 IEqualityComparer<T>
实现。
定义自定义类
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
自定义比较器
class PersonComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
if (x == null && y == null) return true;
if (x == null || y == null) return false;
return x.Name == y.Name && x.Age == y.Age;
}
public int GetHashCode(Person obj)
{
if (obj == null) return 0;
int hashName = obj.Name == null ? 0 : obj.Name.GetHashCode();
int hashAge = obj.Age.GetHashCode();
return hashName ^ hashAge;
}
}
使用自定义比较器
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
List<Person> people = new List<Person>
{
new Person { Name = "Alice", Age = 30 },
new Person { Name = "Bob", Age = 25 },
new Person { Name = "Alice", Age = 30 }, // 重复项
new Person { Name = "Charlie", Age = 35 }
};
var distinctPeople = people.Distinct(new PersonComparer());
foreach (var person in distinctPeople)
{
Console.WriteLine($"{person.Name} ({person.Age})"); // 输出: Alice (30), Bob (25), Charlie (35)
}
//简化输出
Console.WriteLine(string.Join(",",distinctPeople.Select(x=>$"{x.Name} ({x.Age})")));
}
}
在这个例子中,我们定义了一个自定义类 Person
和一个自定义比较器 PersonComparer
,然后使用 Distinct
方法去除了重复的 Person
对象。
相反,如果不定义 自定义比较器 PersonComparer
,是无法对 自定义 Person
对象进行去重的。
示例 3:结合其他 LINQ 方法
Distinct
可以与其他 LINQ 方法结合使用,进一步增强其功能。例如,我们可以先使用 Select
方法提取特定属性,然后再使用 Distinct
方法去除重复项。
using System;
using System.Collections.Generic;
using System.Linq;
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
class Program
{
static void Main()
{
List<Person> people = new List<Person>
{
new Person { Name = "Alice", Age = 30 },
new Person { Name = "Bob", Age = 25 },
new Person { Name = "Alice", Age = 30 }, // 重复项
new Person { Name = "Charlie", Age = 35 }
};
var uniqueNames = people.Select(p => p.Name).Distinct();
foreach (var name in uniqueNames)
{
Console.WriteLine(name); // 输出: Alice, Bob, Charlie
}
}
}
在这个例子中,我们首先使用 Select
方法提取了所有人的名字,然后使用 Distinct
方法去除了重复的名字。
示例 4:字符串集合
假设我们有一个包含重复字符串的集合,我们可以使用 Distinct
方法去除这些重复项。
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
List<string> words = new List<string> { "apple", "banana", "apple", "orange", "banana" };
var distinctWords = words.Distinct();
foreach (var word in distinctWords)
{
Console.WriteLine(word); // 输出: apple, banana, orange
}
}
}
在这个例子中,我们创建了一个包含重复字符串的列表 words
,然后使用 Distinct
方法去除了重复项。
4. 注意事项
尽管 Distinct
方法非常有用,但在实际应用中也有一些需要注意的地方:
- 性能考虑:
Distinct
方法会遍历整个集合并存储已经遇到的元素。对于大型集合,这可能会导致性能问题。因此,在处理大型集合时,应谨慎使用,并根据具体需求选择合适的方法。 - 默认比较器: 默认情况下,
Distinct
方法使用对象的Equals
和GetHashCode
方法进行比较。对于自定义对象,如果没有重写这些方法,Distinct
可能无法正确识别重复项。 - 自定义比较器: 如果需要基于对象的某个属性进行比较,必须提供一个自定义的
IEqualityComparer<T>
实现。否则,Distinct
方法将无法正确识别重复项。 - 不可变性:
Distinct
方法不会修改原始集合,而是返回一个新的集合。这意味着原始集合保持不变,新的集合只包含唯一的元素。
结语
回到目录页:C#/.NET 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。
参考资料:
微软官方文档 Enumerable