[C#]列舉切割器(IEnumerable Split)

  • 12276
  • 0
  • .Net
  • 2011-08-15

有時會有將大量的資料做分割作業,如資料庫查尋出幾萬多筆,要以1000筆為一單位作業,相同的東西寫多了就想要簡化流程,所以就有了這個Extension。

有時會有將大量的資料做分割作業,如資料庫查尋出幾萬多筆,要以1000筆為一單位作業,相同的東西寫多了就想要簡化流程,所以就有了這個Extension。

 

Method的定義


public static void Split<T>(this System.Collections.Generic.IEnumerable<T> list, int batch, System.Action<IEnumerable<T>> action)

///Method2 舊版,會先將IEnumable讀過一遍,將結果存在另一個集合中,本方法至少是2N的執行次數且使用便多的記憶體
public static System.Collections.Generic.IList<IEnumerable<T>> Split<T>(this System.Collections.Generic.IEnumerable<T> list, int batch)

 

使用方式

與Linq的使用方法雷同,只要是IEnumable(包含List<>,DbSet<>等等)都可以切割



//Method 1
list.Split(100, sublist =>
{
    //會執行10次此action
    foreach (var item in sublist)
    {
        //something
    }
});

//如果覺得寫成Lambda不好看,也可以用一般寫法
list.Split(100, ActionExtend);

private void ActionExtend(IEnumerable<int> sublist)
{
    foreach (var item in sublist)
    {
        //something
    }
}

//Method2
var batchs = list.Split(100); //會產生IEnumable<IEnumable<int>>
foreach (var batch in batchs)
{
    foreach (var sublist in batch)
    {
        //something
    }
}

 

原理

Method2是在去年就寫好的,因為寫起來不夠順,又覺得浪費不少記憶體空間與多了一次的迴圈執行,最近就改寫成Method1。

我建立了SplitEnumerableWarpper、SplitEnumeratorWarpper,讓它在foreach時使用原本的Enumerator只是多了一層計數器到設定的量就終止,換下一個Action繼續讀原本的Enumerator,額外的成本只有判斷式與計數器這些必要成本。


public static void Split<T>(this IEnumerable<T> list, int size, Action<IEnumerable<T>> action)
{    
    var e = list.GetEnumerator();
    var warp = new SplitEnumerableWarpper<T>(e, size);
    while (warp.Next()) //有下一個批次就執行Action
    {
        action(warp);
    }
}

//SplitEnumerableWarpper
public bool Next()
{
    //判斷還有沒有下一個批次
    if (_enumeratorWarp == null)
    {
        //第一次執行
        _enumeratorWarp = new SplitEnumeratorWarpper<T>(_enumerator, _size);
        return true;
    }else if (_enumeratorWarp.MoveResult)
    {
        //_enumerator還可以繼續執行
        _enumeratorWarp = new SplitEnumeratorWarpper<T>(_enumerator, _size);
        return true;
    }
    else
    {
        //原_enumerator已經結束了
        return false;
    }
}


//SplitEnumeratorWarpper
public bool MoveNext()
{
    包裝原本Enumerator的MoveNext,超過計數就結束
    if (this.MoveCount < this._size)
    {
        this.MoveResult = this._enumerator.MoveNext();
        if (this.MoveResult)
        {
            this.MoveCount++;
        }

        return this.MoveResult;
    }

    return false;
}

註:Enumerator是.Net中為了foreach而延伸的類,有二個主要成員

MoveNext: 往下巡覽,可回傳True,不可回傳False,當False時結束foreach

Current: 當前值

詳情請參考:http://msdn.microsoft.com/zh-tw/library/system.collections.ienumerator.aspx

下載IterateExtension.cs,同檔中包含更之前寫的萬用String Join使用IEnumerable Extension

 

2011-08-15:

朋友說有一個小問題,如長度0的集合,也會進Action,因為原本必需要在Action中使用到MoveNext(),才會知道集合有沒有結束,所以改成進集合前會先執行一次MoveNext(),如果不能往下就不執行Action。