Chapter 3 - Item 19 : Specialize Generic Algorithms Using Runtime Type Checking

Effective C# (Covers C# 6.0), (includes Content Update Program): 50 Specific Ways to Improve Your C#, 3rd Edition By Bill Wagner 讀後心得

此節是 Item 18 的延伸,利用更明確的判斷執行階段型別;撰寫更有效率的程式碼。

以下用一個反轉列舉器做為例子。

寫法一:

public sealed class ReverseEnumerable<T> : IEnumerable<T>
{
    private IList<T> _originalSequence;
    private IEnumerable<T> _sourceSequence;

    public ReverseEnumerable( IEnumerable<T> sourceSequence )
    {
        _sourceSequence = sourceSequence;
    }

    IEnumerator IEnumerable.GetEnumerator( ) => GetEnumerator( );

    public IEnumerator<T> GetEnumerator( )
    {
        if ( _originalSequence == null )
        {
            _originalSequence = new List<T>( );

            foreach ( var item in _sourceSequence )
                _originalSequence.Add( item );
        }

        return new ReverseEnumerator( _originalSequence );
    }

    private class ReverseEnumerator : IEnumerator<T>
    {
        private IList<T> _collection;
        private int _currentIndex;
        public T Current => _collection [ _currentIndex ];

        object IEnumerator.Current => Current;

        public ReverseEnumertor( IList<T> collection )
        {
            _collection = collection;
            _currentIndex = collection.Count;
        }

        public void Dispose( )
        {
            // No implement but necessary,
            // because IEnumerator<T> implements IDisposable.
            // No protected Dispose( bool isDisposing ) needed,
            // because this class is sealed;
        }

        public bool MoveNext( ) => --_currentIndex >= 0;

        public void Reset( ) => _currentIndex = _collection.Count;
    }
}

寫法一有一處可以改進,在 ReverseEnumerable<T> 建構子處;我們初始化了一個不給定長度的 List。由於輸入的容器只要有實作 ICollection<T> 就可取得容器中含有的數量(實務上極有可能),若能事先知道數量就能省下複製陣列元素和垃圾回收的次數;知道了優化的原因與目的之後,接著改寫程式碼。

寫法二(只修改建構子部分):

public IEnumerator<T> GetEnumerator( )
{
    if ( _originalSequence == null )
    {
        var source = _sourceSequence as ICollection<T>;

        if ( source != null )
            _originalSequence = new List<T>( source.Count );

        else
            _originalSequence = new List<T>( );

        foreach ( var item in _sourceSequence )
            _originalSequence.Add( item );
    }

    return new ReverseEnumerator( _originalSequence );
}

寫法二建構子新增了輸入型別判斷式,若能夠成功轉型為 ICollection<T>;則利用該介面的實作屬性 Count 取得容器中數量,省去了後續多餘的操作。

然而寫法二仍有一些邊界條件沒有考慮到,string 類別也支援了隨機存取的功能;但並沒有實作 ICollection<T>。因此若要支援的更全面,需要增加定義以符合 string 需求。

寫法三:

新增 ReversStringEnumerator : IEnumertator<char>。

private sealed class ReverseStringEnumerator : IEnumerator<char>
{
    private int _currentIndex;
    private string _sourceSequence;
    public char Current => _sourceSequence [ _currentIndex ];

    object IEnumerator.Current => Current;

    public ReverseStringEnumerator( string source )
    {
        _sourceSequence = source;
        _currentIndex = source.Length;
    }

    public void Dispose( )
    {
        // No implement but necessary,
        // because IEumerator<T> implements IDisposable.
    }

    public bool MoveNext( ) => --_currentIndex >= 0;

    public void Reset( ) => _currentIndex = _sourceSequence.Length;
}
Note:除了容器中數量是呼叫 _sourceSequence.Lengh 而非 _sourceSequence.Count 外,其餘幾乎相同。

ReverseEnumerable<T> 建構子也需做對應的修改。

public IEnumerator<T> GetEnumerator( )
{
    if ( _originalSequence == null )
    {
        var sourceString = _sourceSequence as string;

        // Note that cast because T may not be a char at compile time.
        if ( sourceString != null )
            return new ReverseStringEnumerator( sourceString ) as IEnumerator<T>;

        var source = _sourceSequence as ICollection<T>;

        if ( source != null )
            _originalSequence = new List<T>( source.Count );
        else
            _originalSequence = new List<T>( );

        foreach ( var item in _sourceSequence )
            _originalSequence.Add( item );
    }

    return new ReverseEnumerator( _originalSequence );
}

ReverseEnumerable<T> 建構子新增了 string 型別的判斷。如此一來,這個反轉列舉器才算完整。

結論:
1. 利用執行階段的型別判斷,減少泛型帶來的可能效能損耗。

2. 考慮各種輸入可能,讓泛型支援更完整。