C#中IEnumerable、ICollection、IList、IQueryable 、IQueryable 、List之間的區別


一:一個簡單的例子

int[] myArray = { 1, 32, 43, 343 };
            IEnumerator myie = myArray.GetEnumerator();
            myie.Reset();
            while (myie.MoveNext())
            {
                int i = (int)myie.Current;
                Console.WriteLine("Value: {0}", i);
            }

 通常我們這樣會這樣做

 foreach (int item in myArray)
  Console.WriteLine(item.ToString());

 使用for和foreach來遍歷數組,而對於上面的語法卻用的很少,但是對foreach的具體來歷還很模糊!】

二:理解Foreach

要實現foreach的必須要實現IEnumerable和IEnumerator的接口,只有實現了它們,才能實現遍歷,所以要講foreach的來歷,必須要把那兩個接口給搞清楚點!

如果對這兩個接口有了一定的了解后,只要實現那個GetEnumerator方法即可,而不需要實現於IEnumerable接口

   1.IEnumerable

 

具體的作用:就是使實現這個接口的對象成為可枚舉類型。

IEnumerable接口包含一個GetEnumerator方法,返回值為IEnumerator的類型!代碼如下:

    public class MyColors : IEnumerable
    {
        string[] colors = { "Red", "Yellow", "Biue" };
        public IEnumerator GetEnumerator()
        {
            return colors.GetEnumerator();
        }
    }

那么我們在客戶端進行調用的時候就可以這樣做了!

            MyColors colors = new MyColors();
            foreach (string item in colors)
                Console.WriteLine(item);

 

 數組本身就實現了IEnumerator接口,那么兩個接口都實現了,不就好實現foreach遍歷了,其實在實現遍歷枚舉數的時候編譯器會自動去調用數組中實現枚舉數的類中的方法。

 

  2.IEnumerator:

接口的作用:實現可枚舉數,首先看一下接口的定義:

包含一個屬性兩個方法

MoveNext → 把當前的項移動到下一項(類似於索引值),返回一個bool值,這個bool值用來檢查當前項是否超出了枚舉數的范圍!

Current → 獲取當前項的值,返回一個object的類型

Reset → 顧名思義也就是把一些值恢復為默認值,比如把當前項恢復到默認狀態值!

    public class MyIEnumerator : IEnumerator
    {
        public MyIEnumerator(string[] colors)
        {
            this.colors = new string[colors.Length];
            for (int i = 0; i < this.colors.Length; i++)
                this.colors[i] = colors[i];
        }

       string[] colors;        //定義一個數組,用來存儲數據

        int position = -1;      //定義當前項的默認值,也就是索引值,一開始認識數組的索引從“0”開始,
      

        public object Current       //根據當前項獲取相應的值
        {
            get
            {
                return colors[position];          //返回當前項的值,但是會做一個裝箱的操作!
            }
       }

       public bool MoveNext()                  //移動到下一項
        {
          if (position < colors.Length - 1)     //這就是設置默認值為-1的根據
            {
                position++;
                return true;
          }
          else
          {
                return false;
          }
        }

        //重置當前項的值,恢復為默認值
        public void Reset()
        {
            this.position = -1;
        }
    }

上面講到的IEnumerable接口中GetEnumerator方法是獲取要遍歷的枚舉數,在我們沒有創建自己的遍歷枚舉數的類時,我們使用的是Array的遍歷枚舉數的方法,
但這個有的時候不一定適合我們,我們需要為自己定制一個更合適的,所以我們要創建自己的枚舉數類(也就是上面的代碼),把第三點和第四點的代碼合並起來(改動一點代碼),如下: public class MyColors //: IEnumerable { string[] colors = { "Red", "Yellow", "Biue" }; public IEnumerator GetEnumerator() { return new MyIEnumerator(colors); } }

 3、關於可枚舉和枚舉數

 

①可枚舉類型 → 實現IEnumerable接口,可以不需要直接實現這個接口,但必須有個GetEnumerator方法,返回值類型必須為IEnumerator類型,也就是第四點最后一段代碼中接口注釋的那種寫法!

 

②枚舉數 → 實現IEnumerator接口,實現全部方法,首先是調用GetEnumerator返回一個類型為IEnumerator的枚舉數,然后編譯器會隱式的調用實現IEnumerator類中的方法和屬性!

總結:所以實現foreach遍歷,必須達到上面的兩種條件才能進行遍歷對象,他們可以寫在一起也可以分開,最好是分開,進行職責分離,一個類干一件事總歸是好事!也滿足面向對象的單一指責設計原則。

下面的代碼示例演示如何實現自定義集合的 IEnumerable 和 IEnumerator 接口

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    public class Person
    {
        public Person(string fName, string lName)
        {
            this.firstName = fName;
            this.lastName = lName;
        }

        public string firstName;
        public string lastName;
    }

    public class People : IEnumerable
    {
        private Person[] _people;
        public People(Person[] pArray)
        {
            _people = new Person[pArray.Length];

            for (int i = 0; i < pArray.Length; i++)
            {
                _people[i] = pArray[i];
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return (IEnumerator)GetEnumerator();
        }

        public PeopleEnum GetEnumerator()
        {
            return new PeopleEnum(_people);
        }
    }

    public class PeopleEnum : IEnumerator
    {
        public Person[] _people;

        // Enumerators are positioned before the first element
        // until the first MoveNext() call.
        int position = -1;

        public PeopleEnum(Person[] list)
        {
            _people = list;
        }

        public bool MoveNext()
        {
            position++;
            return (position < _people.Length);
        }

        public void Reset()
        {
            position = -1;
        }

        object IEnumerator.Current
        {
            get
            {
                return Current;
            }
        }

        public Person Current
        {
            get
            {
                try
                {
                    return _people[position];
                }
                catch (IndexOutOfRangeException)
                {
                    throw new InvalidOperationException();
                }
            }
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            Person[] peopleArray = new Person[3]
            {
                new Person("John", "Smith"),
                new Person("Jim", "Johnson"),
                new Person("Sue", "Rabon"),
            };

            People peopleList = new People(peopleArray);
            foreach (Person p in peopleList)
                Console.WriteLine(p.firstName + " " + p.lastName);
        }
    }
}

 1、IList 是 ICollection 接口的子代,並且是所有非泛型列表的基接口。IList 實現有三種類別:只讀、固定大小和可變大小。無法修改只讀 IList。固定大小的 IList 不允許添加或移除元素,但允許修改現有元素。可變大小的 IList 允許添加、移除和修改元素。

 

2、ICollection 接口是 System.Collections 命名空間中類的基接口。ICollection 接口擴展 IEnumerable;IDictionary 和 IList 則是擴展 ICollection 的更為專用的接口。 IDictionary 實現是鍵/值對的集合,如 Hashtable 類。 IList 實現是值的集合,其成員可通過索引訪問,如 ArrayList 類。  某些集合(如 Queue 類和 Stack 類)限制對其元素的訪問,它們直接實現 ICollection 接口。  如果 IDictionary 接口和 IList 接口都不能滿足所需集合的要求,則從 ICollection 接口派生新集合類以提高靈活性。定義所有非泛型集合的大小、枚舉器和同步方法。

 

 

3、IQueryable 提供對未指定數據 類型的特定數據源的查詢進行計算的功能,IQueryable 接口由查詢提供程序實現。 該接口只能由同時實現 IQueryable(Of T) 的提供程序實現。 如果該提供程序不實現 IQueryable(Of T),則無法對提供程序數據源使用標准查詢運算符。 IQueryable 接口繼承 IEnumerable 接口,以便在前者表示一個查詢時可以枚舉該查詢的結果。 枚舉強制執行與 IQueryable 對象關聯的表達式樹。 “執行表達式樹”的定義是查詢提供程序所特有的。 例如,它可能涉及將表達式樹轉換為適用於基礎數據源的查詢語言。 在調用 Execute 方法時將執行不返回可枚舉結果的查詢。

 

 

 

 

IQueryable和IEnumberable and IList與Lis t區別

基本概念:

IEnumerable:使用的是LINQ to Object方式,它會將AsEnumerable()時對應的所有記錄都先加載到內存,然后在此基礎上再執行后來的Query

IQeurable(IQuerable<T>):不在內存加載持久數據,因為這家伙只是在組裝SQL,(延遲執行) 到你要使用的時候,
例如 list.Tolist() or list.Count()的時候,數據才從數據庫進行加載 (AsQueryable())。

IList(IList<T>):泛型接口是 ICollection 泛型接口的子代,作為所有泛型列表的基接口,
在用途方面如果作為數據集合的載體這是莫有問題的,只是如果需要對集合做各種的操作,例如 排序 編輯 統計等等,它不行。

List <> :泛型類,它已經實現了IList <> 定義的那些方法,IList<T> list=new List<T>();
只是想創建一個基於接口IList<Class1>的對象的實例,這個接口是由List<T>實現的。只是希望使用到IList<T>接口規定的功能而已  


抽象場景:
其實在我們之前沒有使用 ORM 的的很久很久以前,我們 在ADO.net 里面使用的 DataReader 和 DataAdapter or DataSet 和這幾個貨的基本原理都接近的,
就是讀取數據的時候,一個必須獨占着數據庫的連接,而另一個就是先把數據庫的的局加載到了自己本地,然后再進行操作。


使用場景模擬:
復制代碼
//IList
IList users = res.ToList(); //此時已把users加載到內存,而每個user的關聯實體(UserInfos)未
                                       //被加載,所以下一行代碼無法順利通過
var ss = users.Where(p => p.UserInfos.ID != 3); //此處報錯,因為P的UserInfos實體無法被加載
 
// IQuerable的
IQueryable users = res.AsQueryable(); //users未被立即加載,關聯實體可通過“延遲加載”獲
                                   //得
var ss = users.Where(p => p.UserInfos.ID != 3);//此處順利獲得對應的ss

 

總結:

基於性能和數據一致性這兩點,使用IQueryable時必須謹慎,而在大多數情況下我們應使用IList。

1.當你打算馬上使用查詢后的結果(比如循環作邏輯處理或者填充到一個table/grid中),

並且你不介意該查詢即時被執行后的結果可以供調用者(Consummer)作后續查詢(比如這是一個"GetAll"的方法),或者你希望該查執行,使用ToList()
2.當你希望查詢后的結果可以供調用者(Consummer)作后續查詢(比如這是一個"GetAll"的方法),或者你希望該查詢延時執行,使用AsQueryable()
3.按照功能由低到高:List<T> IList<T> IQueryable<T> IEnumerable<T>
4.按照性能由低到高:IEnumerable<T> IQueryable<T> IList<T> List<T>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM