自己動手重新實現LINQ to Objects: 5 - Empty

本文翻譯自Jon Skeet的系列博文“Edulinq”。

本篇原文地址:

http://msmvps.com/blogs/jon_skeet/archive/2010/12/24/reimplementing-linq-to-objects-part-5-empty.aspx 


這一篇繼續講非擴展方法。這次我們要講的是Empty,它有可能是最簡單的LINQ操作符了。
 

Empty是什麼?
 

Empty是一個泛型的,靜態的方法,它只有一個簽名形式,不接受任何參數:
 

public static IEnumerable<TResult> Empty<TResult>()
 

它返回一個特定類型的空序列。這就是它的唯一作用。

它的行爲只有一點比較有趣:文檔上說Empty會對空序列做緩存。換句話說,對於同一個類型參數來講,它每次都會返回同一個空序列。
 

我們要測試什麼?
 

能夠測試的東西也就只有兩點:
 

返回序列爲空。

對每個類型參數來說,返回值會被緩存起來。
 

和測試Range的時候的方法一樣,我們用一個叫做EmptyClass的別名來引用包含Empty的類型。下面是測試代碼:
 

[Test] 

public void EmptyContainsNoElements() 

    using (var empty = EmptyClass.Empty<int>().GetEnumerator()) 

    { 

        Assert.IsFalse(empty.MoveNext()); 

    } 

[Test] 

public void EmptyIsASingletonPerElementType() 

    Assert.AreSame(EmptyClass.Empty<int>(), EmptyClass.Empty<int>()); 

    Assert.AreSame(EmptyClass.Empty<long>(), EmptyClass.Empty<long>()); 

    Assert.AreSame(EmptyClass.Empty<string>(), EmptyClass.Empty<string>()); 

    Assert.AreSame(EmptyClass.Empty<object>(), EmptyClass.Empty<object>()); 

    Assert.AreNotSame(EmptyClass.Empty<long>(), EmptyClass.Empty<int>()); 

    Assert.AreNotSame(EmptyClass.Empty<string>(), EmptyClass.Empty<object>()); 

}
 

當然,以上代碼並不能證明緩存不是每個線程一份。不過,這些測試也夠了。
 

來動手實現吧!
 

現在看來,Empty的實現要比它的描述更有趣。如果不是要做緩存,我們可以這樣實現Empty
 

// Doesn't cache the empty sequence 

public static IEnumerable<TResult> Empty<TResult>() 

    yield break

}
 

不過我們需要遵守關於緩存的文檔。要實現緩存其實也不難。有一個很方便的事實可以爲我們所用,空數組是不可變的。數組的長度是固定的,通常無法使一個數組是隻讀的。數組中的任何一個元素都是可以改變的。不過一個空數組是不包含任何元素的,所以也就沒有什麼可被改變的。這樣,我們就可以反覆的重用同一個數組了。

現在你可能會猜我會用Dictionary<Type, Array>來實現,不過我們可以利用一個小手段。在一個泛型類型中,可以用一個靜態變量來實現針對類型參數的緩存,因爲每一個傳入了類型參數的泛型類型的靜態變量都是不同的。

很不幸,Empty是一個非泛型類型中的方法。所以我們需要創建另一個泛型類型來包含緩存。這很容易做到,而且CLR還幫我們做到了線程安全的類型初始化。所以,我們最後的實現會是這樣的:
 

public static IEnumerable<TResult> Empty<TResult>() 

    return EmptyHolder<TResult>.Array; 

         

private static class EmptyHolder<T> 

    internal static readonly T[] Array = new T[0];        

}
 

以上的實現遵守了所有的關於緩存的文檔,而且代碼行數也很少。不過這個實現方式需要你很好的瞭解.NET中泛型的工作方式。這種做法和我們上一篇採取的策略相反,我們選擇了一種比較難懂的方式,而沒有選擇使用字典的易懂的方式。不過我很滿意這種方案,因爲一旦你瞭解了泛型類型和靜態變量的工作方式,這段代碼就很簡單了。
 

結論
 

Empty的實現就是這樣的。下一個操作符Repeat有可能會更簡單,雖然它也要分成兩個方法來實現。
 

附錄
 

因爲以上講解的方法有點難懂,所以下面再提供另一種實現:
 

public static IEnumerable<TResult> Empty<TResult>()

{

    return EmptyEnumerable<TResult>.Instance;

}

#if AVOID_RETURNING_ARRAYS

private class EmptyEnumerable<T> : IEnumerable<T>, IEnumerator<T>

{

    internal static IEnumerable<T> Instance = new EmptyEnumerable<T>();

    // Prevent construction elsewhere

    private EmptyEnumerable()

    {

    }

    public IEnumerator<T> GetEnumerator()

    {

        return this;

    }

    IEnumerator IEnumerable.GetEnumerator()

    {

        return this;

    }

    public T Current

    {

        get { throw new InvalidOperationException(); }

    }

    object IEnumerator.Current

    {

        get { throw new InvalidOperationException(); }

    }

    public void Dispose()

    {

        // No-op

    }

    public bool MoveNext()

    {

        return false// There's never a next entry

    }

    public void Reset()

    {

        // No-op

    }

}

#else

private static class EmptyEnumerable<T>

{

    internal static readonly T[] Instance = new T[0];       

}

#endif
 

這下大家都滿足了吧:) 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章