在C#中,IEnumerable
,ICollection
,IList
,IReadOnlyList
,IQueryable
和ReadOnlyCollection<T>
在集合处理和LINQ查询中扮演着重要角色
接口或类 | 说明 |
---|---|
IEnumerable | 适用于只读访问集合元素 不能修改集合(添加或删除元素) 支持 foreach 迭代 使用 yield return 实现延迟执行 |
ICollection | 提供集合的大小信息 支持添加和删除元素 可以检查集合是否包含某元素 |
IList | 支持按索引访问和修改元素 可以插入和移除指定位置的元素 |
IReadOnlyList | 只读的泛型集合,提供对元素的随机访问功能,但不允许修改集合(即不能添加、删除或修改元素) |
IQueryable | 支持 LINQ 查询的延迟执行 可以表示查询的表达式树 通常与 ORM 框架(如 Entity Framework)一起使用 |
ReadOnlyCollection | 是一个包装类,用于将现有的集合封装为只读集合 |
IEnumerable
定义如下
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
延迟执行
IEnumerable支持延迟执行,这意味着查询不会立即执行,而是在实际迭代时才执行。这在LINQ查询中非常常见
IEnumerable<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
var query = numbers.Where(n => n > 3);
// 查询尚未执行
foreach (var number in query)
{
Console.WriteLine(number); // 查询在此处执行
}
使用yield return
namespace Demo
{
internal class Program
{
public static IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
static void Main(string[] args)
{
IEnumerable<int> numbers = GetNumbers();
foreach (int number in numbers)
{
Console.WriteLine(number);
}
}
}
}
输出结果:
1
2
3
foreach迭代
List<string> cities = new List<string>() { "New York", "London", "Tokyo", "Lisbon", "Hyderabad", "Chicago" };
IEnumerable<string> query = cities.Where(x => x.StartsWith("L"));
foreach (var city in query)
{
Console.WriteLine(city);
}
重复迭代
每次调用GetEnumerator都会重新开始迭代,因此如果IEnumerable的实现涉及昂贵的计算或数据库访问,重复迭代可能会导致性能问题
var expensiveQuery = GetExpensiveQuery();
foreach (var item in expensiveQuery)
{
// 第一次迭代
}
foreach (var item in expensiveQuery)
{
// 第二次迭代,可能会再次触发昂贵的操作
}
转换为List或Array
如果需要多次迭代或避免延迟执行,可以将IEnumerable转换为List或数组
namespace Demo
{
internal class Program
{
public static IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
static void Main(string[] args)
{
IEnumerable<int> numbers = GetNumbers();
List<int> numberList = numbers.ToList();
// 现在可以多次迭代
foreach (int number in numberList)
{
Console.WriteLine(number);
}
foreach (int number in numberList)
{
Console.WriteLine(number * 2);
}
}
}
}
输出结果:
1
2
3
2
4
6
Linq查询
namespace Demo
{
internal class Program
{
public static IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
static void Main(string[] args)
{
IEnumerable<int> numbers = GetNumbers();
var evenNumbers = numbers.Where(n => n % 2 == 0);
foreach (int number in evenNumbers)
{
Console.WriteLine(number);
}
}
}
}
输出结果:
2
使用IEnumerator
namespace Demo
{
internal class Program
{
public static IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
static void Main(string[] args)
{
IEnumerable<int> numbers = GetNumbers();
using (IEnumerator<int> enumerator = numbers.GetEnumerator())
{
while (enumerator.MoveNext())
{
int number = enumerator.Current;
Console.WriteLine(number);
}
}
}
}
}
输出结果:
1
2
3
可变集合
如果IEnumerable基于可变集合,如 List,在迭代过程中修改集合可能会导致InvalidOperationException
IEnumerable<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
foreach (var number in numbers)
{
if (number == 3)
{
((List<int>)numbers).Add(6); // 会抛出 System.InvalidOperationException:“Collection was modified; enumeration operation may not execute.”
}
}
ICollection
先看定义
public interface ICollection<T> : IEnumerable<T>, IEnumerable
{
int Count { get; }
bool IsReadOnly { get; }
void Add(T item);
void Clear();
bool Contains(T item);
void CopyTo(T[] array, int arrayIndex);
bool Remove(T item);
}
与IEnumerable接口不同,ICollection接口允许您在集合中添加或删除元素
ICollection<string> countries = new Collection<string>();
countries.Add("USA");
countries.Add("India");
countries.Add("England");
countries.Add("Japan");
foreach (string country in countries)
{
Console.WriteLine(country);
}
可以检查集合是否包含某元素
ICollection<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
numbers.Add(6);
Console.WriteLine(numbers.Count); // 输出 6
numbers.Remove(3);
Console.WriteLine(numbers.Contains(3)); // 输出 False
ICollection继承自IEnumerable,因此可以直接使用LINQ查询
ICollection<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
var squares = numbers.Select(n => n * n);
foreach (var square in squares)
{
Console.WriteLine(square); // 输出 1, 4, 9, 16, 25
}
IList
先看定义
public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable
{
T this[int index] { get; set; }
int IndexOf(T item);
void Insert(int index, T item);
void RemoveAt(int index);
}
支持按索引访问和修改元素,可以插入和移除指定位置的元素
IList<string> customers = new List<string>();
customers.Add("Joydip");
customers.Add("Steve");
customers.Add("Peter");
customers.Insert(2, "Michael");
customers.RemoveAt(0);
for (int i = 0; i < customers.Count; i++)
{
Console.WriteLine(customers[i]);
}
LINQ可以应用于任何实现了IEnumerable<T>
接口的集合,而IList<T>
继承自ICollection<T>
,进而继承自IEnumerable<T>
,因此可以直接使用LINQ查询
IList<int> list1 = new List<int> { 1, 2, 3 };
IList<int> list2 = new List<int> { 3, 4, 5 };
var concatenated = list1.Concat(list2); // 连接两个列表
var union = list1.Union(list2); // 合并并去重
var intersect = list1.Intersect(list2); // 交集
var except = list1.Except(list2); // 差集
Console.WriteLine("Concatenated: " + string.Join(", ", concatenated)); // 输出 1, 2, 3, 3, 4, 5
Console.WriteLine("Union: " + string.Join(", ", union)); // 输出 1, 2, 3, 4, 5
Console.WriteLine("Intersect: " + string.Join(", ", intersect)); // 输出 3
Console.WriteLine("Except: " + string.Join(", ", except)); // 输出 1, 2
IReadOnlyList
先看定义
public interface IReadOnlyList<out T> : IEnumerable<T>, IEnumerable, IReadOnlyCollection<T>
{
T this[int index] { get; }
}
只读。不允许添加、删除或修改元素。而IList可变,可以添加、删除和修改元素
IReadOnlyList<int> readOnlyList = new List<int> { 1, 2, 3 };
// readOnlyList.Add(4); // 编译错误
// readOnlyList[0] = 10; // 编译错误
// readOnlyList.RemoveAt(1); // 编译错误
IList转换为IReadOnlyList
可以通过List的AsReadOnly方法将IList转换为IReadOnlyList
List<int> list = new List<int> { 1, 2, 3 };
IReadOnlyList<int> readOnlyList = list.AsReadOnly();
foreach (var num in readOnlyList)
{
Console.WriteLine(num); // 1,2,3
}
// Linq
var evenNumbers = readOnlyList.Where(n => n % 2 == 0).ToList();
foreach (var num in evenNumbers)
{
Console.WriteLine(num); // 2
}
数组
数组(T[])也实现了IReadOnlyList
int[] array = { 1, 2, 3 };
IReadOnlyList<int> readOnlyList = array;
foreach (var num in readOnlyList)
{
Console.WriteLine(num); // 1,2,3
}
IQueryable
先看定义
public interface IQueryable<out T> : IEnumerable<T>, IEnumerable, IQueryable
{
}
IEnumerable接口可用于处理内存中的数据集合,而IQueryable接口可用于外部数据源(例如Web服务或数据库)
using System.Collections.ObjectModel;
namespace Demo
{
public class Author
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool IsActive { get; set; }
}
internal class Program
{
static void Main(string[] args)
{
List<Author> authors = new List<Author>()
{
new Author(){Id = 1, FirstName = "Joydip", LastName = "Kanjilal", IsActive = true},
new Author(){Id = 2, FirstName = "Michael", LastName = "Smith", IsActive = false},
new Author(){Id = 3, FirstName = "Steve", LastName = "Jones", IsActive = true}
};
IQueryable<Author> query = authors.AsQueryable()
.Where(a => a.IsActive == true);
foreach (var author in query)
{
// 输出:Id : 1 FirstName : Joydip LastName : Kanjilal, Id : 3 FirstName : Steve LastName : Jones
Console.WriteLine($"Id : {author.Id} FirstName : {author.FirstName} LastName : {author.LastName}");
}
}
}
}
IQueryable接口适合处理大型数据集,特别是当您需要实现分页以仅检索所需的数据时
IQueryable是一个强大的接口,主要用于构建和执行查询,特别是在使用LINQ to SQL、Entity Framework 等 ORM(对象关系映射)工具时。与IEnumerable不同,IQueryable支持延迟查询执行,并且可以将查询表达式翻译成数据库查询(如 SQL)
如果要从数据库查询数据,请使用IQueryable。如果要从内存中查询数据,请使用IEnumerable、ICollection或IList,具体取决于要对集合的元素执行的操作
使用AsQueryable
如果你有一个IEnumerable集合,并希望将其转换为IQueryable,可以使用AsQueryable。但请注意,转换后的查询仍然在内存中执行,而不是在数据库中
using System.Collections.ObjectModel;
namespace Demo
{
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool IsActive { get; set; }
}
internal class Program
{
public static IEnumerable<User> GetNumbers()
{
yield return new User { Id = 1, FirstName = "Ma", LastName = "Jack", IsActive = true };
yield return new User { Id = 2, FirstName = "Hua", LastName = "Li", IsActive = true };
yield return new User { Id = 3, FirstName = "Ma", LastName = "Tony", IsActive = false };
}
static void Main(string[] args)
{
IEnumerable<User> users = GetNumbers();
IQueryable<User> query = users.AsQueryable().Where(u => u.IsActive);
// 这将在内存中执行,而不是在数据库中
foreach (var author in query)
{
// 输出:Id : 1 FirstName : Ma LastName : Jack, Id : 2 FirstName : Hua LastName : Li
Console.WriteLine($"Id : {author.Id} FirstName : {author.FirstName} LastName : {author.LastName}");
}
}
}
}
不要在查询中混合本地集合和数据库集合
避免在IQueryable查询中使用本地集合,因为这可能导致查询在内存中执行,而不是在数据库中执行
List<int> ids = new List<int> { 1, 2, 3 };
IQueryable<User> query = dbContext.Users.Where(u => ids.Contains(u.Id));
// 这可能导致整个 Users 表加载到内存中,然后进行过滤
ReadOnlyCollection
先看定义
public class ReadOnlyCollection<T> : ICollection<T>, IEnumerable<T>, IEnumerable, IList<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection, IList
{
public ReadOnlyCollection(IList<T> list);
public T this[int index] { get; }
public static ReadOnlyCollection<T> Empty { get; }
public int Count { get; }
protected IList<T> Items { get; }
public bool Contains(T value);
public void CopyTo(T[] array, int index);
public IEnumerator<T> GetEnumerator();
public int IndexOf(T value);
}
通过将现有集合传递给ReadOnlyCollection的构造函数来创建只读集合
List<int> list = new List<int> { 1, 2, 3 };
ReadOnlyCollection<int> readOnlyList = new ReadOnlyCollection<int>(list);
foreach (var num in readOnlyList)
{
Console.WriteLine(num); // 1,2,3
}
数据封装
虽然ReadOnlyCollection本身是只读的,但如果底层集合(如 List)被修改,ReadOnlyCollection的内容也会随之改变
List<int> list = new List<int> { 1, 2, 3 };
ReadOnlyCollection<int> readOnlyList = new ReadOnlyCollection<int>(list);
foreach (var num in readOnlyList)
{
Console.WriteLine(num); // 1,2,3
}
list.Add(4);
Console.WriteLine(readOnlyList.Count); // 输出 4
参考
- How to use IEnumerable, ICollection, IList, and IQueryable in C# | InfoWorld
- 了解下 IEnumerable、ICollection、IList 和 IQueryable 接口_ienumerable icollection ilist-CSDN博客
- IEnumerable<T> 接口 (System.Collections.Generic) | Microsoft Learn
- ReadOnlyCollection<T> 类 (System.Collections.ObjectModel) | Microsoft Learn