More Effective C# 44.透過動態型別運用泛型引數執行期的型別 More Effective C# 44.透過動態型別運用泛型引數執行期的型別(Use Dynamic to Leverage the Runtime Type of Generic Type Parameters)

這個做法在說明泛型方法的限制與能夠利用 dynamic 動態類型來彌補這些限制。

首先先看一下這段程式碼,MyType 類別建立了隱含轉換,正常運作下能夠把 string 轉換成 MyType 的 StringMember, 但實際上會直接拋出 InvalidCastException

void Main()
{
	var answer1 = GetSomeStrings().Cast<MyType>();
	try
	{
		foreach (var v in answer1)
			Console.WriteLine(v);
	}
	catch (InvalidCastException)
	{
		Console.WriteLine("Cast Failed!");
	}
}

public class MyType
{
	public string StringMember { get; set; }
	public static implicit operator string(MyType aString) => aString.StringMember;
	public static implicit operator MyType(string aString) => new MyType { StringMember = aString };
}

public static string[] GetSomeStrings()
{
	return new string[] { "Hello, ", "World!" };
}

這是因為 Cast 只能進行參考型別轉換和裝箱轉換,沒辦法存取使用者自定義的型別轉換操作,並且泛型中的類型參數 T 並沒有用約束進行定義, 所以只能假設 T 只有包含 object 所定義的成員,當然就不可能有我們自己定義的轉換方式了。

當然我們可以直接跳過轉型的操作,直接使用建構函式建立新的物件。

void Main()
{
	var answer4 = GetSomeStrings().Select(n => new MyType { StringMember = n });
	var answer5 = from v in GetSomeStrings()
				  select new MyType { StringMember = v };
}

也可以使用 dynamic 讓型別轉換在執行時期解決,而不是編譯時期,這行程式碼能夠在執行時期將 item 轉換回 string,之後 MyType 的隱含轉換就能生效了。

public static IEnumerable<TResult> Convert<TResult>(this IEnumerable sequence)
{
    foreach (object item in sequence)
    {
        dynamic coercion = (dynamic)item;
        yield return (TResult)coercion;
    }
}

使用上的效果跟下面相同,就是要確認 item 的真實型別。

var convertedSequence = GetSomeStrings();
foreach (string item in convertedSequence)
{
	Console.WriteLine((MyType)item);
}

Summary

這個做法說明了 Cast 的缺陷,就是編譯時期只有有限的資訊導致泛型方法不能如預期般的運行,這時候透過 dynamic 在執行期別 把轉型缺失的資訊補上,可以彌補 Cast 的缺陷。