C#基础之IEnumerable

本文深入探讨了IEnumerable接口在Linq查询中的作用,通过自定义FruitShop集合类,展示了如何实现IEnumerable接口进行数据遍历。同时,对比分析了Cast<T>()与OfType<T>()方法在类型转换和筛选上的差异。

1.IEnumerable的作用

在使用Linq查询数据时经常以IEnumerable<T>来作为数据查询返回对象,在使用foreach进行遍历时需要该对象实现IEnumerable接口,这2个功能让我对IEnumerable充满了无穷的好奇。然而在VS中查看IEnumerable的定义时发现它只定义了一个GetEnumerator()方法,关于IEnumerator我知道它依靠MoveNext和Current来达到Foreach的遍历,但是具体是如何将数据进行迭代的,完整的流程是怎样的?这些疑虑我觉得只有亲自实现一次自定义集合foreach才能够解决。为此我需要定义一个集合FruitShop类,它是一个关于Fruit对象的集合,代码如下所示。整个流程是第一次遇到foreach里的fruitShop对象时就会去执行FruitShop中的GetEnumerator方法,接着每次执行in关键字就会去执行MoveNext方法,每次取数据则是调用Current属性。

 

写完代码之后突然就觉得其实没什么了,不过在查找资料当中发现IEnumerable接口并不是我们看到的只有一个方法,它还有4个扩展方法。其中Cast<T>()和OfType<T>()这2个方法非常实用,代码如下所示。有时候对于非泛型集合比如ArrayList,它只实现了IEnumerable接口而没有实现IEnumerable<T>接口,因此无法使用标准查询运算。但是标准查询运算如此的方便,.NET肯定不会允许这样的不完美发生,于是为IEnumerable提供了2个扩展方法。只要实现了IEnumerable接口的集合就可以使用这2个方法来进行强制转换。而OfType<T>比Cast<T>更加强大,它除了进行强制转换外还可以实现类型的过滤。从结果中可以看到第一个foreach遇到int类型后由于无法转换而报出InvalidCastException异常,而使用OfType<T>进行转换时则会自动进行类型筛选,遇到int类型的数据将不会转换,所以进行转换时OfType<T>是首选。

public class Fruit
    {
        public string fruitName;
        public string fruitPrice;
        public Fruit(string fruitName, string fruitPrice)
        {
            this.fruitName = fruitName;
            this.fruitPrice = fruitPrice;
        }
    }


 class FruitShop:IEnumerable
    {
        Fruit[] fruits=new Fruit[10];
        int current = 0;
        public void Add(Fruit fruit)
        {
            fruits[current] = fruit;
            current++;
        }

        public IEnumerator GetEnumerator()
        {
            return new FruitEnumerator(fruits);
        }
    }

    public class FruitEnumerator:IEnumerator
    {
        Fruit[] fruits;
        int current = -1;
        public FruitEnumerator(Fruit[] fruits)
        {
            this.fruits = fruits;
        }

        //这里需要做一个判断,因为有可能此时current<0或超出数组长度
        public object Current
        {
            get { return CurrentFruit(); }
        }
        object CurrentFruit()
        {
            if (current < 0 || current > fruits.Length)
                return null;
            else
                return fruits[current];
        }

        public bool MoveNext()
        {
            current++;
            if(current<fruits.Length&&fruits[current]!=null)
                return true;
            return false;
        }

        public void Reset()
        {
            current=0;
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            FruitShop fruitShop = new FruitShop();
            Fruit fruitApple = new Fruit("Apple", "10");
            Fruit fruitPear = new Fruit("Pear", "12");
            Fruit fruitGrape = new Fruit("Grape", "15");
            fruitShop.Add(fruitApple);
            fruitShop.Add(fruitPear);
            fruitShop.Add(fruitGrape);

            foreach (Fruit f in fruitShop)
            {
                Console.WriteLine(f.fruitName+": "+f.fruitPrice);
            }
        }
    }
static void CastAndOfType()
        {
            ArrayList fruits = new ArrayList(4);
            fruits.Add("Apple");
            fruits.Add("Pear");
            fruits.Add(2);
            fruits.Add(5);

            //下面这句会报错,因为ArrayList没有实现IEnumerable<T>接口,故无法使用标准查询运算
            //IEnumerable<string> s=fruitShop.Select(str => str);
            //但是使用Cast<T>和OfType<T>来进行转换

            IEnumerable<string> s1 = fruits.Cast<string>();
            IEnumerable<string> s2 = fruits.OfType<string>();
            //虽然Cast<T>和OfType<T>都可以用来进行转换,但是OfType<T>比Cast<T>更加强大,
            //它可以对结果进行筛选,而Cast<T>遇到无法强制转换的则会报错

            try
            {
                foreach (string fruit in s1)
                {
                    Console.WriteLine(fruit);
                }
            }
            catch(InvalidCastException invalid)
            {
                Console.WriteLine(invalid.Message);
            }

            foreach (string fruit in s2)
            {
                Console.WriteLine(fruit);
            }
        }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

<think>嗯,用户想知道如何在C#中声明IEnumerable对象。首先,我得回忆一下C#中关于IEnumerable基础知识。根据引用[2]和引用[3],IEnumerable接口在System.Collections命名空间里,它有一个GetEnumerator方法,返回IEnumerator。所以,声明IEnumerable对象的话,可能有几种方法,比如直接实现接口,或者使用集合类,或者用迭代器方法。 首先,用户可能需要知道最直接的方式,比如用集合类型。比如List、数组这些本身都实现了IEnumerable,所以可以直接赋值给IEnumerable变量。比如IEnumerable<string> data = new List<string>(); 这样的话,用户可以直接用已有的集合类型来声明,这是比较常见和简单的方法。 然后,用户可能也会需要自己实现IEnumerable接口的情况。比如创建一个自定义的集合类,这时候需要实现GetEnumerator方法。引用[2]提到,实现IEnumerable的类是可枚举的,可以进行迭代操作。这时候可能需要同时实现IEnumerator,处理Current、MoveNext等方法,不过这可能比较复杂,特别是对于新手来说。不过现在C#有yield关键字,可以简化这个过程,比如在方法中使用yield return,编译器会自动生成状态机,这样就不需要手动实现IEnumerator了。比如引用[4]中的例子,用yield return来生成元素,这样方法返回的就是IEnumerable类型。 另外,用户可能还会问到如何通过迭代器方法创建IEnumerable对象。比如在方法中返回IEnumerable,并在方法体内使用yield return语句。比如引用[4]中的TakeWhilePositive方法,通过foreach遍历,并在满足条件时yield return,否则yield break。这种方法比较简洁,适合动态生成序列的情况。 还有,引用[5]中的例子,使用StreamReaderEnumerable来逐行读取文件,并返回IEnumerable<string>,这可能涉及到自定义实现,但用户可能更倾向于使用内置的方法或者LINQ来生成IEnumerable对象。比如使用LINQ的Select、Where等方法,这些操作返回的也是IEnumerable类型。 总结一下,用户可能需要几种不同的方法来声明IEnumerable对象:使用现有集合类型、通过迭代器方法使用yield、自定义实现IEnumerable接口,或者使用LINQ查询。我应该分步骤说明这些方法,并给出示例代码,确保用户能清楚每种方法的适用场景和实现方式。同时,要注意代码的正确性,比如在自定义类中正确实现GetEnumerator,或者在使用yield时避免常见错误。 另外,用户提供的引用中有提到IEnumerable和IEnumerator的关系,所以在解释的时候需要明确说明两者之间的协作,比如GetEnumerator返回IEnumerator,而foreach循环实际上使用了这个模式。不过可能用户更关注的是如何声明对象,而不是底层原理,所以重点放在实际应用上。 最后,要确保回答结构清晰,分点说明每种方法,并给出对应的示例,这样用户能够逐步理解和应用。同时,生成的相关问题需要围绕可能的后续疑问,比如性能优化、具体应用场景或者更高级的用法。</think>在C#中声明$IEnumerable$对象有以下几种常见方法,按实现方式分类说明如下: --- ### 一、直接使用集合类型 所有实现$IEnumerable$接口的集合类型都可以直接赋值: ```csharp // 使用List集合 IEnumerable<string> list = new List<string> { "A", "B", "C" }; // 使用数组 IEnumerable<int> array = new int[] { 1, 2, 3 }; // 使用LINQ查询结果 IEnumerable<double> query = from n in array select n * 0.5; ``` --- ### 二、通过迭代器方法生成 使用`yield return`语句创建迭代器方法(编译器自动生成状态机)[^4]: ```csharp IEnumerable<int> GenerateNumbers(int max) { for (int i = 0; i < max; i++) { yield return i; } } // 调用方法 IEnumerable<int> numbers = GenerateNumbers(5); ``` --- ### 三、自定义实现IEnumerable接口 需要同时实现$IEnumerable$和$IEnumerator$接口(适用于特殊集合类型)[^2][^3]: ```csharp public class CustomCollection : IEnumerable<string> { private string[] _items = { "X", "Y", "Z" }; public IEnumerator<string> GetEnumerator() { return new CustomEnumerator(_items); } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); private class CustomEnumerator : IEnumerator<string> { private string[] _values; private int _index = -1; public CustomEnumerator(string[] values) => _values = values; public string Current => _values[_index]; object IEnumerator.Current => Current; public bool MoveNext() => ++_index < _values.Length; public void Reset() => _index = -1; public void Dispose() { } } } // 使用自定义集合 IEnumerable<string> customEnum = new CustomCollection(); ``` --- ### 四、通过延迟执行特性 利用LINQ的延迟加载特性生成序列: ```csharp IEnumerable<FileInfo> largeFiles = Directory.EnumerateFiles("C:\\") .Select(f => new FileInfo(f)) .Where(f => f.Length > 1024 * 1024); ``` --- ### 关键选择建议 - **简单序列**:优先使用集合类型或`yield`迭代器 - **文件/网络流处理**:参考引用[^5]的流式处理模式 - **复杂数据结构**:自定义实现接口 - **数据库查询**:考虑使用$IQueryable$(派生自$IEnumerable$)[^3]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值