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

在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

参考

  • 40
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值