C#使用IEnumerable, ICollection, IList, IReadOnlyList, IQueryable和ReadOnlyCollection<T>

在C#中,IEnumerable​,ICollection​,IList​,IReadOnlyList​,IQueryable​和ReadOnlyCollection​在集合处理和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​接口的集合,而IList​继承自ICollection​,进而继承自IEnumerable​,因此可以直接使用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 接口 (System.Collections.Generic) | Microsoft Learn
  • ReadOnlyCollection 类 (System.Collections.ObjectModel) | Microsoft Learn

你可能感兴趣的:(.NET,c#,asp.net,.net,.netcore,后端,开发语言,微软)