欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C#中的IEnumerable接口深入研究

 更新時(shí)間:2014年07月16日 08:43:43   投稿:junjie  
這篇文章主要介紹了.NET中的IEnumerable接口深入研究,分析出了它的實(shí)現(xiàn)原理和實(shí)現(xiàn)代碼,需要的朋友可以參考下

C#和VB.NET中的LINQ提供了一種與SQL查詢類似的“對(duì)象查詢”語言,對(duì)于熟悉SQL語言的人來說除了可以提供類似關(guān)聯(lián)、分組查詢的功能外,還能獲取編譯時(shí)檢查和Intellisense的支持,使用Entity Framework更是能夠自動(dòng)為對(duì)象實(shí)體的查詢生成SQL語句,所以很受大中型信息系統(tǒng)設(shè)計(jì)者的青睞。

IEnumerable這個(gè)接口可以說是為了這個(gè)特性“量身定制”,再加上微軟提供的擴(kuò)展(Extension)方法和Lambda表達(dá)式,給開發(fā)者帶來了無窮的便利。本人在最近的開發(fā)工作中使用了大量的這種特性,同時(shí)在調(diào)試過程中還遇到了一個(gè)小問題,那么正好趁此機(jī)會(huì)好好研究一下相關(guān)原理和實(shí)現(xiàn)。

先從一個(gè)現(xiàn)實(shí)的例子開始吧。假如我們要做一個(gè)商品檢索功能(這只是一個(gè)例子,我當(dāng)然不可能把公司的產(chǎn)品也業(yè)務(wù)在這里貼出來),其中有一個(gè)檢索條件是可以指定廠家的名稱并進(jìn)行模糊匹配。廠家的包括兩個(gè)名稱:注冊(cè)名稱和一般性名稱,我們只按一般性名稱進(jìn)行檢索。當(dāng)然你可以說直接用SQL查詢就行了,但是我們的系統(tǒng)是以實(shí)體對(duì)象為核心進(jìn)行設(shè)計(jì)的,廠家的數(shù)量也不會(huì)太多,大概1000條。為了不增加系統(tǒng)的復(fù)雜性,只考慮使用現(xiàn)有的數(shù)據(jù)訪問層接口進(jìn)行實(shí)現(xiàn)(按過濾條件獲取商品,以及獲取所有廠商),這時(shí)LINQ的便捷性就體現(xiàn)出來了。

借助IEnumerable接口和其輔助類,我們可以寫出以下代碼:

復(fù)制代碼 代碼如下:

public GoodsListResponse GetGoodsList(GoodsListRequest request)
{
    //從數(shù)據(jù)庫(kù)中按商品類別獲取商品列表
    IEnumerable<Goods> goods = GoodsInformation.GetGoodsByCategory(request.CategoryId);

    //用戶指定了商品名檢索字段,進(jìn)行模糊匹配
    //如果沒有指定,則不對(duì)商品名進(jìn)行過濾
    if (!String.IsNullOrWhiteSpace(request.GoodsName))
    {
        request.GoodsName = request.GoodsName.Trim().ToUpper();
       
        //按商品名對(duì) goods 中的對(duì)象進(jìn)行過濾
        //生成一個(gè)新的 IEnumerable<Goods> 類型的迭代器
        goods = goods.Where(g => g.GoodsName.ToUpper().Contains(request.GoodsName));
    }

    //如果用戶指定的廠商的檢索字段,進(jìn)行模糊匹配
    if (!String.IsNullOrWhiteSpace(request.ManufactureName))
    {
        request.ManufactureName = request.ManufactureName.Trim().ToUpper();

        //只提供了獲取所有廠商的列表方法
        //取出所有廠商,篩選包含關(guān)鍵字的廠商
        IEnumerable<Manufacture> manufactures = ManufactureInformation.GetAll();
        manufactures = manufactures.Where(m => m.Name.GeneralName.ToUpper()
                            .Contains(request.ManufactureName));

        //取出任何符合所匹配廠商的商品
        goods = goods.Where(g => manufactures.Any(m => m.Id == g.ManufactureId));
    }

    GoodsListResponse response = new GoodsListResponse();

    //將 goods 放到一個(gè) List<Goods> 對(duì)象中,并返回給客戶端
    response.GoodsList = goods.ToList();

    return response;
}

假如不使用IEnumerable這個(gè)接口,所實(shí)現(xiàn)的代碼遠(yuǎn)比上面復(fù)雜且難看。我們需要寫大量的foreach語句,并手工生成很多中間的 List 來不斷地篩選對(duì)象(你可以嘗試把第二個(gè)if塊改寫成不用IEnumerable接口的形式)。

看上去一切都很和諧,但是上面的代碼有一個(gè)隱含的bug,這個(gè)bug也是今天上午困擾了我許久的一個(gè)問題。

運(yùn)行程序,當(dāng)我不輸入廠商檢索條件的時(shí)候,程序運(yùn)行是正確的。但當(dāng)我輸入一個(gè)廠商的名字時(shí),系統(tǒng)拋出了一個(gè)空引用的異常。咦?為什么會(huì)有空引用呢?我輸入的廠商是數(shù)據(jù)庫(kù)中不存在的廠商,因此我覺得問題可以出在goods = goods.Where(g => manufactures.Any(m => m.Id == g.ManufactureId)) 這句話上。既然manufactures是空的,那么是不是意味著我不能調(diào)用其 Any 方法呢(lambda表達(dá)式中的部分)。于是我改寫成以下形式:

復(fù)制代碼 代碼如下:

if (manufactures != null)
    //取出任何符合所匹配廠商的商品
    goods = goods.Where(g => manufactures.Any(m => m.Id == g.ManufactureId));

還是不行,那么我對(duì)manufactures判斷其是否有元素,就調(diào)用其無參數(shù)的Any方法,這時(shí)問題依舊:

聰明的你肯定已經(jīng)看出問題出在哪了,因?yàn)閂isual Studio已經(jīng)提示得很清楚了。但我當(dāng)時(shí)還局限在“列表為空”這個(gè)框框中,因此遲遲不能發(fā)現(xiàn)原因。出錯(cuò)是發(fā)生在 manufactures.Any() 這句話上,而我已經(jīng)判斷了它不為空啊,為什么還會(huì)拋錯(cuò)呢?

后來叫了一個(gè)同事幫我看,他說的四個(gè)字一下子就提醒了我“延遲計(jì)算”。哦,對(duì)!我怎么把這個(gè)特性給忘了。在最初的代碼中(就是沒有對(duì) manufactures 為空進(jìn)行判斷),出錯(cuò)是發(fā)生在 goods.ToList() 這句話時(shí),而圖上的那個(gè)代碼段出錯(cuò)是發(fā)生在調(diào)用Any()方法時(shí)(圖中的灰色部分),而我單步跟蹤到 Any() 這句話上時(shí),出錯(cuò)的語句跳到 Where 子句(黃色部分),說明知道訪問 Any 方法時(shí)lambda表達(dá)式才被調(diào)用。

那么很顯然是 Where 語句中這個(gè) predicate 有問題:Manufacture的Name字段可能為空(數(shù)據(jù)庫(kù)中存在這樣的數(shù)據(jù),所以導(dǎo)致在 translate 的時(shí)候Name字段為空),那么改寫成以下形式就能解決問題,當(dāng)然我們不用對(duì) manufactures 列表進(jìn)行為空的判斷:

復(fù)制代碼 代碼如下:

manufactures = manufactures.Where(m => m.Name != null &&
                    m.Name.GeneralName.ToUpper().Contains(request.ManufactureName));

在此要感謝那位同事看出了問題所在,否則我不知道還得郁悶多久。

我之前在使用 LINQ 語句的時(shí)候知道它的延遲計(jì)算特性,但是沒有想到從根本上自 IEnumerable 的擴(kuò)展方法就有這個(gè)特性。那么很顯然,C#的編譯器只是把 LINQ 語句改寫成類似于調(diào)用 Where、Select之類的擴(kuò)展方法,延遲計(jì)算這種特性是 IEnumerable 的擴(kuò)展方法就支持的!我之前一直以為我每調(diào)用一次 Where 或者 Select(其實(shí)我SelectMany用得更多),就會(huì)對(duì)結(jié)果進(jìn)行過濾,現(xiàn)在看來并不是這樣。

即使是使用 Where 等擴(kuò)展方法, 執(zhí)行這些 predicate 的時(shí)間是在 foreach 和 ToList 的時(shí)候才發(fā)生。

為什么會(huì)這樣呢?看樣子這完全不應(yīng)該呀?Where子句的返回值就是一個(gè)IEnumerable的迭代器,按道理應(yīng)該已經(jīng)篩選了對(duì)象???為了徹底搞清楚這個(gè)問題,那么方法很明顯——看 .NET 的源代碼。

Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) 是它的方法頭,在看源代碼之前,相信你已經(jīng)知道微軟大概是怎么實(shí)現(xiàn)的了:既然Where接受一個(gè)Func類型的委托,并且都是在ToList 或者 foreach 的時(shí)候計(jì)算的,那么顯而易見實(shí)現(xiàn)應(yīng)該是……

好了,來看下代碼吧。IEnumerable的擴(kuò)展方法都在 Enumerable 這個(gè)靜態(tài)類中,Where方法的實(shí)現(xiàn)代碼如下:

復(fù)制代碼 代碼如下:

public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
    if (source == null) throw Error.ArgumentNull("source");
    if (predicate == null) throw Error.ArgumentNull("predicate");
    if (source is Iterator<TSource>) return ((Iterator<TSource>)source).Where(predicate);
    if (source is TSource[]) return new WhereArrayIterator<TSource>((TSource[])source, predicate);
    if (source is List<TSource>) return new WhereListIterator<TSource>((List<TSource>)source, predicate);
    return new WhereEnumerableIterator<TSource>(source, predicate);
}

很顯然,M$會(huì)用到 source 的類型,根據(jù)不同的類型返回不同的 WhereXXXIterator。等等,這就意味著Where方法返回的不是IEnumerable。從這里我們就可以清晰地看到M$其實(shí)是包裝了一層,那么顯而易見,應(yīng)該是只記錄了一個(gè)委托。這些WhereXXXIterator都是派生自 Iterator 抽象類,這個(gè)類實(shí)現(xiàn)了 IEnumerable<TSource> 和 IEnumerator<TSource> 這兩個(gè)接口,這樣用戶就能鏈?zhǔn)降厝フ{(diào)用。不過, Iterator 類不是public的,所以用戶只知道是一個(gè)  IEnumerable 的類型。這樣做的好處是可以向用戶隱藏一些底層實(shí)現(xiàn)的細(xì)節(jié),顯得類庫(kù)用起來很簡(jiǎn)單;壞處是可能會(huì)導(dǎo)致用戶的使用方式不合理,以及一些較難理解的問題。

我們暫時(shí)不看 Iterator 類的一些細(xì)節(jié),繼續(xù)看 WhereListIterator 的 Where 方法。這個(gè)方法在基類是抽象的,因此在這里實(shí)現(xiàn)它:

復(fù)制代碼 代碼如下:

public override IEnumerable<TSource> Where(Func<TSource, bool> predicate) {
    return new WhereListIterator<TSource>(source, CombinePredicates(this.predicate, predicate));
}

CombinePredicates是Enumerable靜態(tài)類提供的擴(kuò)展方法,不過它不是public的,只有在內(nèi)部才能訪問:

復(fù)制代碼 代碼如下:

static Func<TSource, bool> CombinePredicates<TSource>(Func<TSource, bool> predicate1, Func<TSource, bool> predicate2) {
    return x => predicate1(x) && predicate2(x);
}

自然,WhereListIterator 有幾個(gè)字段:

復(fù)制代碼 代碼如下:

List<TSource> source;
Func<TSource, bool> predicate;
List<TSource>.Enumerator enumerator;

這樣,相信大家都已經(jīng)知道了Where的工作原理,簡(jiǎn)單地總結(jié)一下:


1.當(dāng)我們創(chuàng)建了一個(gè) List 后,調(diào)用其定義在 IEnumerable 接口上的 Where 擴(kuò)展方法,系統(tǒng)會(huì)生成一個(gè) WhereListIterator 的對(duì)象。這個(gè)對(duì)象把 Where 子句的 predicate 委托保存并返回。

2.再次調(diào)用 Where 子句時(shí),對(duì)象其實(shí)已經(jīng)變成 WhereListIterator類型,此后再次調(diào)用 Where 方法時(shí),會(huì)調(diào)用 WhereListIterator.Where 方法,這個(gè)方法把兩個(gè) predicate 合并,之后返回一個(gè)新的 WhereListIterator。

3.之后的每一次 Where 調(diào)用都是執(zhí)行第2步操作。

可以看出,在調(diào)用 Where 方法時(shí),系統(tǒng)只是記錄了 predicate 委托,并沒有回調(diào)這些委托,所以此時(shí)自然而然就不會(huì)產(chǎn)生新的列表。

當(dāng)遇到foreach語句時(shí),會(huì)需要生成一個(gè) IEnumerator 類型的對(duì)象以便枚舉,此時(shí)就開始調(diào)用 Iterator 的 GetEnumerator 方法。這個(gè)方法只有在基類中定義:

復(fù)制代碼 代碼如下:

public IEnumerator<TSource> GetEnumerator() {
    if (threadId == Thread.CurrentThread.ManagedThreadId && state == 0) {
        state = 1;
        return this;
    }
    Iterator<TSource> duplicate = Clone();
    duplicate.state = 1;
    return duplicate;
}

在獲取迭代器的時(shí)候要考慮并發(fā)的問題,如果多個(gè)線程都在枚舉元素,同時(shí)使用一個(gè)迭代器肯定會(huì)發(fā)生混亂。M$的實(shí)現(xiàn)方法很聰明,對(duì)于同一個(gè)線程只使用一個(gè)迭代器,當(dāng)發(fā)現(xiàn)是另一個(gè)線程調(diào)用的時(shí)候直接克隆一個(gè)。

MoveNext方法在子類中定義,WhereListIterator的實(shí)現(xiàn)如下:

復(fù)制代碼 代碼如下:

public override bool MoveNext() {
    switch (state) {
        case 1:
            enumerator = source.GetEnumerator();
            state = 2;
            goto case 2;
        case 2:
            while (enumerator.MoveNext()) {
                TSource item = enumerator.Current;
                if (predicate(item)) {
                    current = item;
                    return true;
                }
            }
            Dispose();
            break;
    }
    return false;
}


switch語句寫得不容易看懂。在獲取迭代器后,逐個(gè)進(jìn)行 predicate 回調(diào),返回滿足條件的第一個(gè)元素。當(dāng)遍歷結(jié)束后,如果迭代器實(shí)現(xiàn)了 IDispose 接口,就調(diào)用其 Dispose 方法釋放非托管資源。之后設(shè)置基類的 state 屬性為-1,這樣今后就訪問不到這個(gè)迭代器了,需要重新創(chuàng)建一個(gè)。

至此,終于看到只有在迭代時(shí)才進(jìn)行計(jì)算的緣由了。其他的一些Iterator大體上都是類似的,只是MoveNext的實(shí)現(xiàn)方式不一樣罷了。至于M$為什么要單獨(dú)為 List 和 Array 寫一個(gè)單獨(dú)的類,對(duì)于數(shù)組來說可以直接根據(jù)下標(biāo)訪問下一個(gè)元素,這樣就可以避免訪問迭代器的 MoveNext 方法,可以提高一點(diǎn)效率。但對(duì)于列表來說,其實(shí)現(xiàn)方式和普通的類相同,估計(jì)是首先想使用不同的實(shí)現(xiàn)后來發(fā)現(xiàn)不好吧。

其他的擴(kuò)展方法,比如Select、Repeat、Reverse、OrderBy之類的好像也能鏈?zhǔn)秸{(diào)用,并且可以不限順序任意調(diào)用多次。這又是怎么實(shí)現(xiàn)的呢?

我們先來看Select方法。類似Where方法,Select也定義了對(duì)應(yīng)的三個(gè)Iterator:WhereSelectListIterator、WhereSelectArrayIterator和WhereSelectEnumerableIterator。每一種都定義了Select和Where方法:

復(fù)制代碼 代碼如下:

public override IEnumerable<TResult2> Select<TResult2>(Func<TResult, TResult2> selector) {
    return new WhereSelectListIterator<TSource, TResult2>(source, predicate, CombineSelectors(this.selector, selector));
}

public override IEnumerable<TResult> Where(Func<TResult, bool> predicate) {
    return new WhereEnumerableIterator<TResult>(this, predicate);
}

CombineSelectors的代碼如下:

復(fù)制代碼 代碼如下:

static Func<TSource, TResult> CombineSelectors<TSource, TMiddle, TResult>(Func<TSource, TMiddle> selector1, Func<TMiddle, TResult> selector2) {
    return x => selector2(selector1(x));
}


這樣子就把Select和Where連起來了。本質(zhì)上,運(yùn)行時(shí)的類型在WhereXXXIterator和WhereSelectXXXIterator之間進(jìn)行變換,每次都產(chǎn)生一個(gè)新的類型。

你可能會(huì)覺得對(duì)于每一種方法,M$都定義了一個(gè)專門的類,比如OrderByIterator等。但這樣做會(huì)引起類的爆炸,同時(shí)每一種Iterator為了兼容其他的類這樣要重復(fù)寫的東西簡(jiǎn)直無法想象。微軟把這些函數(shù)分成了兩類,第一類是直接調(diào)用迭代器,列舉如下:

1.Reverse:生成一個(gè)Buffer對(duì)象,倒序輸入后返回 IEnumerable 類型的迭代器。
2.Cast:以object類型取迭代器中的元素并轉(zhuǎn)型yield return。
3.Union、Ditinct:生成一個(gè)Set類型的對(duì)象,這個(gè)對(duì)象會(huì)訪問迭代器。
4.Concat、Zip、Take、TakeWhile、Skip、SkipWhile:yield return。


很顯然,調(diào)用這些方法會(huì)導(dǎo)致訪問迭代器,這樣 predicate 和 selector 就會(huì)開始進(jìn)行回調(diào)(如果是WhereXXXIterator或WhereSelectXXXIterator類型的話)。當(dāng)然,訪問聚集函數(shù)或者First之類的方法顯而易見會(huì)導(dǎo)致列表進(jìn)行迭代,這里不多說明了。

第二種就是微軟進(jìn)行特殊處理的 Join、GroupBy、OrderBy、ThenBy。這幾個(gè)方法是 LINQ 中的核心,偷懶怎么行?我已經(jīng)寫累了,相信各位看官也累了。但是求知心怎么會(huì)允許我們休息呢?繼續(xù)往下看吧。

先從最熟悉的排序開始。OrderBy方法最簡(jiǎn)單的重載如下(順帶一提,方法簽名看似非常復(fù)雜,其實(shí)使用起來很簡(jiǎn)單,因?yàn)閂isual Studio會(huì)自動(dòng)幫你匹配泛型參數(shù),比如 goods = goods.OrderBy(g => g.GoodsName);):

復(fù)制代碼 代碼如下:

public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector);

哇塞,返回值終于不是IEnumerable了,這個(gè)IOrderedEnumerable很明顯也是IEnumerable繼承過來的。在實(shí)現(xiàn)上,OrderedEnumerable<TSource>是一個(gè)實(shí)現(xiàn)了該方法的抽象類,OrderedEnumerable<TSource, TKey>繼承自此類,這兩個(gè)類都不對(duì)外公開。但微軟又公開了接口,這不是很奇怪么?難道是可以讓用戶自行擴(kuò)展?這點(diǎn)暫時(shí)不深究了。

OrderBy擴(kuò)展方法會(huì)返回一個(gè)OrderedEnumerable類型的對(duì)象,這個(gè)類對(duì)外公開了 GetEnumerator 方法:

復(fù)制代碼 代碼如下:

public IEnumerator<TElement> GetEnumerator() {
    Buffer<TElement> buffer = new Buffer<TElement>(source);
    if (buffer.count > 0) {
        EnumerableSorter<TElement> sorter = GetEnumerableSorter(null);
        int[] map = sorter.Sort(buffer.items, buffer.count);
        sorter = null;
        for (int i = 0; i < buffer.count; i++) yield return buffer.items[map[i]];
    }
}


OK,重點(diǎn)來了:OrderBy也是進(jìn)行延時(shí)操作!也就是說直到調(diào)用 GetEnumerator 之前,還是不會(huì)回調(diào)前面的 predicate 和 selector。這里的排序算法只是一個(gè)簡(jiǎn)單的快速排序算法,由于不是重點(diǎn),代碼省略。

到這里估計(jì)有些人已經(jīng)暈了,所以需要再次進(jìn)行總結(jié)。用一個(gè)例子來說明,假如我寫了如下這樣的代碼,應(yīng)該是怎么工作的呢(代碼僅僅是為了說明,沒有實(shí)際的意義)?

復(fù)制代碼 代碼如下:

goods = goods.OrderBy(g => g.GoodsName);
goods.Where(g => g.GoodsName.Length < 10);

執(zhí)行完第一句代碼后,類型變成了 OrderedEnumerable ,那么又來一個(gè) Where,情況會(huì)怎么樣呢?

由于 OrderedEnumerable 沒有定義 Where 方法,那么又會(huì)調(diào)用 IEnumerable 的 Where 方法。此時(shí)會(huì)發(fā)生什么呢?由于類型不是 WhereXXXIterator,那么…… 對(duì)!那么會(huì)生成一個(gè) WhereEnumerableIterator,此時(shí) List 這個(gè)信息就已經(jīng)丟失了。

有個(gè)疑問,我接下來再次調(diào)用 Where,此時(shí)這個(gè) Where 語句并不知道之前的一些 predicate,在接下來的迭代過程中,怎么進(jìn)行回調(diào)呢?

不要忘了,每一個(gè)類似這種類型(Enumerable、Iterator),都有一個(gè) source 字段,這個(gè)字段就是鏈?zhǔn)秸{(diào)用的關(guān)鍵。OrderedEnumerable 類型對(duì)象在初始的過程中記錄了 WhereListIterator 這個(gè)類型對(duì)象的引用并存入 source 字段中,在接下來的 Where 調(diào)用里,新生成的 WhereEnumerableIterator 類型對(duì)象中,又將 OrdredEnumerable 類型的對(duì)象存入 source 中。之后在枚舉的過程中,會(huì)按照如下步驟開始執(zhí)行:

1.枚舉時(shí)類型是 WhereEnumerableIterator,進(jìn)行枚舉時(shí),首先要得到這個(gè)對(duì)象的 Enumerator。此時(shí)系統(tǒng)調(diào)用 source 字段的 GetEnumerator。正是那個(gè)不太好理解的 switch 語句,曾經(jīng)一度被我們忽略的 source.GetEnumerator() 在此起了重要的作用。

2.source 字段存儲(chǔ)的是 OrderedEnumerator 類型的對(duì)象,我們參考這個(gè)對(duì)象的 GetEnumerator 方法(就是上面那個(gè)帶 Buffer 的),發(fā)現(xiàn)它會(huì)調(diào)用 Buffer 的構(gòu)造方法將數(shù)據(jù)填入緩沖區(qū)。Buffer 的構(gòu)造方法代碼我沒有列出,但是其肯定是調(diào)用其 source 的枚舉器(事實(shí)上如果是集合會(huì)調(diào)用其 CopyTo)。

3.這時(shí) source 字段存儲(chǔ)的是 WhereListIterator 類型對(duì)象,這個(gè)類的行為在最開始我們分析過:逐個(gè)回調(diào) predicate 和 selector 并 yield return。
4.最后,前面的迭代器生成了,在 MoveNext 的過程中,首先回調(diào) WhereEumerableIterator 的委托,再繼續(xù)取 OrderedEnumerable 的元素,直至完成。

看,一切都是如此地“順理成章”。都是歸功于 source 字段。至此,我們已經(jīng)幾乎了解了 IEnumerable 的全部玄機(jī)。

對(duì)了,還有 GroupBy 和 Join 沒有進(jìn)行說明。在此簡(jiǎn)單提一下。

這兩個(gè)方法的基礎(chǔ)是一個(gè)稱之為 LookUp 的類。LookUp表示一個(gè)鍵到多個(gè)值的集合(比較Dictionary),在實(shí)現(xiàn)上是一個(gè)哈希表對(duì)應(yīng)到可以擴(kuò)容的數(shù)組。GroupBy 和 Join 借助 LookUp 實(shí)現(xiàn)對(duì)元素的分組與關(guān)聯(lián)操作。GroupBy 語句使用了 GroupEnumerator,其原理和上面所述的 OrderedEnumerator 類似,在此不再贅述。如果對(duì) GroupBy 和 Join 的具體實(shí)現(xiàn)感興趣,可以自行參看源代碼。

好了,這次關(guān)于 IEnumerable 的研究總算告一段落了,我也總算是弄清了其工作原理,解答了心中的疑慮。另外可以看到,在研究的過程中要有耐心,這樣事情才會(huì)越來越明朗的。

相關(guān)文章

  • C#實(shí)現(xiàn)子窗體與父窗體通信方法實(shí)例總結(jié)

    C#實(shí)現(xiàn)子窗體與父窗體通信方法實(shí)例總結(jié)

    這篇文章主要介紹了C#實(shí)現(xiàn)子窗體與父窗體通信方法,實(shí)例總結(jié)了常用的四種窗體通信方法,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-09-09
  • C#程序加密工具.Net?Reactor詳細(xì)教程

    C#程序加密工具.Net?Reactor詳細(xì)教程

    .Net?Reacto加密工具是一款強(qiáng)大的?.NET?代碼保護(hù)和授權(quán)管理系統(tǒng),安全可靠、簡(jiǎn)單易用,主要用來幫助開發(fā)人員保護(hù)他們的?.NET?軟件產(chǎn)品,本文給大家詳細(xì)介紹C#程序加密工具.Net?Reactor教程,感興趣的朋友一起看看吧
    2022-02-02
  • c#的treeview綁定和獲取值的方法

    c#的treeview綁定和獲取值的方法

    這篇文章主要介紹了c#的treeview綁定和獲取值的方法,需要的朋友可以參考下
    2014-04-04
  • C#修改MAC地址類的實(shí)例

    C#修改MAC地址類的實(shí)例

    C#修改MAC地址類的實(shí)例,需要的朋友可以參考一下
    2013-03-03
  • C#實(shí)現(xiàn)按數(shù)據(jù)庫(kù)郵件列表發(fā)送郵件的方法

    C#實(shí)現(xiàn)按數(shù)據(jù)庫(kù)郵件列表發(fā)送郵件的方法

    這篇文章主要介紹了C#實(shí)現(xiàn)按數(shù)據(jù)庫(kù)郵件列表發(fā)送郵件的方法,涉及C#讀取數(shù)據(jù)庫(kù)及通過自定義函數(shù)發(fā)送郵件的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-07-07
  • C# Process調(diào)用外部程序的實(shí)現(xiàn)

    C# Process調(diào)用外部程序的實(shí)現(xiàn)

    這篇文章主要介紹了C# Process調(diào)用外部程序的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-02-02
  • C# WPF 通過委托實(shí)現(xiàn)多窗口間的傳值的方法

    C# WPF 通過委托實(shí)現(xiàn)多窗口間的傳值的方法

    這篇文章主要介紹了C# WPF 通過委托實(shí)現(xiàn)多窗口間的傳值的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-09-09
  • c#使用FreeSql生產(chǎn)環(huán)境時(shí)自動(dòng)升級(jí)備份數(shù)據(jù)庫(kù)

    c#使用FreeSql生產(chǎn)環(huán)境時(shí)自動(dòng)升級(jí)備份數(shù)據(jù)庫(kù)

    使用FreeSql,包含所有的ORM數(shù)據(jù)庫(kù),都會(huì)存在這樣的問題。在codefirst模式下,根據(jù)代碼自動(dòng)更新數(shù)據(jù)庫(kù),都建議不要在生產(chǎn)環(huán)境使用。因?yàn)槿菀讈G失數(shù)據(jù),本文提供一種自動(dòng)更新數(shù)據(jù)庫(kù)的解決的思路:在判斷需要升級(jí)時(shí),才自動(dòng)升級(jí),同時(shí)升級(jí)前先備份數(shù)據(jù)庫(kù)
    2021-06-06
  • C#調(diào)用CMD命令實(shí)例

    C#調(diào)用CMD命令實(shí)例

    這篇文章主要介紹了C#調(diào)用CMD命令實(shí)例本文只是給出一個(gè)比較簡(jiǎn)單的、入門級(jí)的例子,更多高級(jí)的操作技巧請(qǐng)參閱相關(guān)文章,需要的朋友可以參考下
    2014-08-08
  • c#生成excel示例sql數(shù)據(jù)庫(kù)導(dǎo)出excel

    c#生成excel示例sql數(shù)據(jù)庫(kù)導(dǎo)出excel

    這篇文章主要介紹了c#操作excel的示例,里面的方法可以直接導(dǎo)出數(shù)據(jù)到excel,大家參考使用吧
    2014-01-01

最新評(píng)論