Effective C# 19.使用執行期型別檢查特化泛型演算法 Effective C# 19.使用執行期型別檢查特化泛型演算法 (Specialize Generic Algorithms Using Runtime Type Checking)

Published on Wednesday, October 16, 2024

這個做法在討論泛型演算法傳入的參數會影響演算法的效率。

以下是一個進行倒序的演算法,從建構函式可以看出演算法會將傳入的 IEnumerable<T> 複製到 sourceSequence,之後取得 ReverseEnumerator 進行到序。

public sealed class ReverseEnumerable<T> : IEnumerable<T>
{
	IEnumerable<T> sourceSequence;
	IList<T> originalSequence;

	public ReverseEnumerable(IEnumerable<T> sequence)
	{
		sourceSequence = sequence;
	}
	
	public IEnumerator<T> GetEnumerator()
	{
		if (originalSequence == null)
		{
			originalSequence = new List<T>();
			foreach (T item in sourceSequence)
				originalSequence.Add(item);
		}
		
		return new ReverseEnumerator(originalSequence);
	}

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

	private class ReverseEnumerator : IEnumerator<T>
	{
		int currentIndex;
		IList<T> collection;

		public ReverseEnumerator(IList<T> srcCollection)
		{
			collection = srcCollection;
			currentIndex = collection.Count;
		}

		public T Current => collection[currentIndex];

		object IEnumerator.Current => this.Current;

		public void Dispose()
		{
		}

		public bool MoveNext()
		{
			return --currentIndex >= 0;
		}

		public void Reset()
		{
			currentIndex = collection.Count;
		}
	}
}

這個演算法關鍵的就是取得傳入參數的 IEnumerator,但是這個演算法只有假設傳入的參數有實做 IEnumerable 介面,所以不管你實際傳入的 參數是什麼型別,編譯器只會讓你使用 IEnumerable 能用的那些方法,最後只能建立一個中繼的區域變數 originalSequence 因為 ReverseEnumerable 只支援有實做 IList 的參數,所以複製 originalSequence 是一個折衷的處理方法。

IEnumerable<T> sourceSequence;
IList<T> originalSequence;

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

public IEnumerator<T> GetEnumerator()
{
	if (originalSequence == null)
	{
		originalSequence = new List<T>();
		foreach (T item in sourceSequence)
			originalSequence.Add(item);
	}
	
	return new ReverseEnumerator(originalSequence);
}

我們可以優化一下上面的演算法,由於傳入參數只有最低要求實作 IEnumerable 就好,所以實際上執行期別可能為其它類型的參數,可以直接在建構函式 直接測試是否能轉型並直接賦值,這樣執行期別有實做 IList 就能直接略過下面的 if 檢查,或者直接添加額外的多載。

public ReverseEnumerable(IEnumerable<T> sequence)
{
	sourceSequence = sequence;
	originalSequence = sequence as IList<T>;
}

public ReverseEnumerable(IList<T> sequence)
{
   sourceSequence = sequence;
   originalSequence = sequence;
}

但還是有一些特例沒有實作IList<T> 但是有實做 ICollection<T> 的集合,我們可以多加一個 if 檢查這種特例。

public IEnumerator<T> GetEnumerator()
{
	if (originalSequence == null)
	{
		if (sourceSequence is ICollection)
		{
			var source = sourceSequence as ICollection;
			originalSequence = new List<T>(source.Count);
		}
		else
		{
			originalSequence = new List<T>();
		}
	}
	foreach (T item in sourceSequence)
		originalSequence.Add(item);

	return new ReverseEnumerator(originalSequence);
}

最後還有一種特例就是 string 類別,我們可以再加一個 ReverseStringEnumerator 最後輸出一個倒序的 char 集合。

public IEnumerator<T> GetEnumerator()
{
	if (sourceSequence is string)
	{
		return new ReverseStringEnumerator(sourceSequence as string) as IEnumerator<T>;
	}
	if (originalSequence == null)
	{
		if (sourceSequence is ICollection)
		{
			var source = sourceSequence as ICollection;
			originalSequence = new List<T>(source.Count);
		}
		else
		{
			originalSequence = new List<T>();
		}
	}
	foreach (T item in sourceSequence)
		originalSequence.Add(item);

	return new ReverseEnumerator(originalSequence);
}
	
private sealed class ReverseStringEnumerator : IEnumerator<char>
{
	private string sourceSequence;
	private int currentIndex;
	public ReverseStringEnumerator(string source)
	{
		sourceSequence = source;
		currentIndex = source.Length;
	}
	public char Current => sourceSequence[currentIndex];
	public void Dispose()
	{
	}
	object System.Collections.IEnumerator.Current
		=> sourceSequence[currentIndex];
	public bool MoveNext() => --currentIndex >= 0;
	public void Reset() => currentIndex = sourceSequence.
	Length;
}

Summary

這個做法主要在顯示如何用最少的約束條件,也能透過內部型別檢查獲得那些專屬於某型別的功能,而且這些檢查被隱藏在我們的泛型類別裡面, 並且就算沒有供特殊的類型檢查也能夠運行,這些特例檢查只是用來提示演算法運行速度。