[.NET] LINQ Deferred Execution

摘要:[.NET] : LINQ Deferred Execution


前言 :

看到91跟小朱都分享了,延遲執行的文章。
- 91 : [.NET]延遲執行(Deferred Execution) 簡單概念實作
- 小朱 :[.NET] LINQ 的延遲執行 (Deferred Execution)
喚醒了許久之前的記憶,記得也有對LINQ的運作下了一番功夫。
趁記憶還沒有消失。簡單的做個記錄,也希望對有需要的開發人員有幫助。


說明 :

簡單的說,在 Linq的延遲執行運作,主要有三個要點。
1. IEnumerable跟 foreach是 LINQ運作的核心。
2. IEnumerable套用 Decorator模式,對IEnumerable加入功能。
3. 使用 IEnumerable的擴充方法生成套用 Decorator的 IEnumerable,方便串接程式。


1. IEnumerable跟 foreach是 LINQ運作的核心。

在LINQ裡是以IEnumerable做為運作的目標跟結果,並且以foreach來做結果列舉的動作。
了解IEnumerable跟foreach之間的運作流程,是理解LINQ運作很重要的一步。


下面這段Code展示了拆解 Foreach機制後的程式碼,用來說明使用foreach列舉IEnumerable時的程式流程。
(更細節的資料,可以參考 Design Patterns裡 Iterator模式。)


原始碼 :

public static void Test001()
{
    // 建立字串陣列,並且轉型為IEnumerable
    string[] stringArray = new string[] { "A", "B", "C" };
    IEnumerable<string> stringEnumerable = stringArray;

    // 列舉 IEnumerable使用 Foreach
    foreach (string item in stringEnumerable)
    {
        Console.WriteLine(item);
    }

    // 拆解 Foreach得到的程式
    IEnumerator<string> stringEnumerator = stringEnumerable.GetEnumerator();
    while (stringEnumerator.MoveNext() == true)
    {
        string item = stringEnumerator.Current;
        Console.WriteLine(item);
    }
}

2. IEnumerable套用 Decorator模式,對IEnumerable加入功能。

Decorator模式的主要功能是 :「將額外權責動態附加於物件身上,不必延生子類別及可彈性擴增功能。」
將 Decorator模式套用到 IEnumerable之後,
可以將功能附加到 IEnumerable介面,卻又不改變使用foreach列舉的用法。


下面這段Code實做一個過濾字串用的Decorator,用來說明如何將 Decorator模式套用到 IEnumerable
(更細節的資料,可以參考 Design Patterns裡 Decorator模式。)


原始碼 :

public class StringFilterEnumerable : IEnumerable<string>
{
    // Fields
    private readonly IEnumerable<string> _sourceEnumerable;

    private readonly string _filterArgument = null;


    // Constructor
    public StringFilterEnumerable(IEnumerable<string> sourceEnumerable, string filterArgument)
    {
        #region Require

        if (sourceEnumerable == null) throw new ArgumentNullException();
        if (string.IsNullOrEmpty(filterArgument) == true) throw new ArgumentNullException();

        #endregion
        _sourceEnumerable = sourceEnumerable;
        _filterArgument = filterArgument;
    }


    // Methods
    public IEnumerator<string> GetEnumerator()
    {
        return new StringFilterEnumerator(_sourceEnumerable.GetEnumerator(), _filterArgument);
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

public class StringFilterEnumerator : IEnumerator<string>
{
    // Fields
    private readonly IEnumerator<string> _sourceEnumerator;

    private readonly string _filterArgument = null;

    private string _currentResult = null;


    // Constructor
    public StringFilterEnumerator(IEnumerator<string> sourceEnumerator, string filterArgument)
    {
        #region Require

        if (sourceEnumerator == null) throw new ArgumentNullException();
        if (string.IsNullOrEmpty(filterArgument) == true) throw new ArgumentNullException();

        #endregion
        _sourceEnumerator = sourceEnumerator;
        _filterArgument = filterArgument;
    }

    public virtual void Dispose()
    {
        _sourceEnumerator.Dispose();
        _currentResult = null;
    }


    // Methods 
    public string Current
    {
        get
        {
            return _currentResult;
        }
    }

    object System.Collections.IEnumerator.Current
    {
        get
        {
            return this.Current;
        }
    }

    public bool MoveNext()
    {
        while (_sourceEnumerator.MoveNext() == true)
        {
            if (_sourceEnumerator.Current != _filterArgument)
            {
                _currentResult = _sourceEnumerator.Current;
                return true;
            }
        }
        return false;
    }

    public void Reset()
    {
        _sourceEnumerator.Reset();
        _currentResult = null;
    }
}

使用範例1 :

public static void Test002()
{
    // 建立字串陣列,並且轉型為IEnumerable
    string[] stringArray = new string[] { "A", "B", "C" };
    IEnumerable<string> stringEnumerable = stringArray;

    // 生成並且套用StringFilterEnumerable物件,過濾字串"B"
    stringEnumerable = new StringFilterEnumerable(stringEnumerable, "B");

    // 列舉 IEnumerable使用 Foreach
    foreach (string item in stringEnumerable)
    {
        Console.WriteLine(item);
    }
}

使用範例2 :

public static void Test002_1()
{
    // 建立字串陣列,並且轉型為IEnumerable
    string[] stringArray = new string[] { "A", "B", "C" };
    IEnumerable<string> stringEnumerable = stringArray;

    // 生成並且套用StringFilterEnumerable物件,過濾字串"B"+"A"
    stringEnumerable = new StringFilterEnumerable(new StringFilterEnumerable(stringEnumerable, "B"), "A");

    // 列舉 IEnumerable使用 Foreach
    foreach (string item in stringEnumerable)
    {
        Console.WriteLine(item);
    }
}

3. 使用 IEnumerable的擴充方法生成套用 Decorator的 IEnumerable,方便串接程式。

這段就比較好理解,只是將上一個要點建立的使用擴充方法來做物件生成動作。
主要要達成的目的就是將一些程式做隱藏的動作,在使用的時候可以比較方便。
(更細節的資料,可以參考:[擴充方法 (C# 程式設計手冊)])


原始碼 :

public static class StringEnumerableExtensions
{
    public static IEnumerable<string> Filter(this IEnumerable<string> sourceEnumerable, string filterArgument)
    {
        #region Require

        if (sourceEnumerable == null) throw new ArgumentNullException();
        if (string.IsNullOrEmpty(filterArgument) == true) throw new ArgumentNullException();

        #endregion
        return new StringFilterEnumerable(sourceEnumerable, filterArgument);
    }
}   

使用範例1 :

public static void Test003()
{
    // 建立字串陣列,並且轉型為IEnumerable
    string[] stringArray = new string[] { "A", "B", "C" };
    IEnumerable<string> stringEnumerable = stringArray;

    // 使用擴充方法套用StringFilterEnumerable物件,過濾字串"B"
    stringEnumerable = stringEnumerable.Filter("B");

    // 列舉 IEnumerable使用 Foreach
    foreach (string item in stringEnumerable)
    {
        Console.WriteLine(item);
    }
}

使用範例2 :

public static void Test003_1()
{
    // 建立字串陣列,並且轉型為IEnumerable
    string[] stringArray = new string[] { "A", "B", "C" };
    IEnumerable<string> stringEnumerable = stringArray;

    // 使用擴充方法套用StringFilterEnumerable物件,過濾字串"B"+"A"
    stringEnumerable = stringEnumerable.Filter("B").Filter("A");

    // 列舉 IEnumerable使用 Foreach
    foreach (string item in stringEnumerable)
    {
        Console.WriteLine(item);
    }
}

後記 :

依照前面章節的說明,細細去分析程式。
不難看出 LINQ的執行的時間點,不是在呼叫擴充方法的當下。
而是在使用foreach列舉IEnumerable時,才去執行。
這也就是 LINQ的延遲執行(Deferred Execution)。

期許自己
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。