More Effective C# 11.在你的 API 中避免轉換運算子 More Effective C# 11.在你的 API 中避免轉換運算子(Avoid Conversion Operators in Your APIs)

這個做法在說明 implicitexplicit operator 在轉型中的應用場景與可能會導致錯誤發生時很難找到問題。

我們知道所有類型的基底類型都是 System.Object,也就代表能夠將衍生的類型轉型回 System.Object,這樣就能在某些需要傳入 Object 類型的方法 改用其他類型來替換 Object,這個就是使用 polymorphism 的特性來達到替換的功能。

例如下面的 Object.Equals 方法,就能夠直接傳入 int 類型進行替換。

void Main()
{
	int i = 1;
	int j = 1;
	Console.WriteLine(Object.Equals(i, j));
}

下面這段程式碼建立了兩個衍生類型 CarBus,這兩個類型有相關性可以說 BusCar 的一種特例,也就是說我們可以寫一個傳入 Car 類型方法 MoveForward 就可以與 Bus 一起共用這個方法,但是這兩個類別並沒有直接的繼承關係所以要透過 implicitexplicit operator 來達到這個需求。

void Main()
{
	Bus bus1 = new Bus(3);
	MoveForward(bus1);
	Console.WriteLine(bus1);
}

public static void MoveForward(Car car)
{
	car.Distance = 100;
}

public class Car : Vehicle
{
	private int _passenger;
	public int Distance { get; set; }
	public int Passenger
	{
		get { return _passenger; }
		set
		{
			if (value > 3)
				throw new ArgumentException("Too Many People");
			_passenger = value;
		}
	}

	public Car(int passenger)
	{
		Passenger = passenger;
	}

	//public Car(Bus bus)
	//{
	//	Passenger = bus.Passenger;
	//}

	public static implicit operator Bus(Car car)
	{
		return new Bus(car.Passenger);
	}
}

public class Bus : Vehicle
{
	private int _passenger;
	public int Distance { get; set; }
	public int Passenger
	{
		get { return _passenger; }
		set { _passenger = value; }
	}

	public Bus(int passenger)
	{
		Passenger = passenger;
	}
	
	public static implicit operator Car(Bus bus)
	{
		return new Car(bus.Passenger);
	}
}

public class Vehicle {}

但這段程式碼有一個問題就是在執行 implicit operator Car 這段邏輯的時候會建立一個新的 Car 物件,這裡把它稱之為中間物件, 這會導致 MoveForward 方法修改的是這個中間物件的屬性而不是 bus1,這就會導致中間物件產生之後被修改然後就直接被回收了, 並且這段程式碼可讀性也不是非常好,也很難排查問題。

並且可以看出它的關鍵邏輯就是幫你呼叫建構函式而已,那其實跟我們自己多載一個新的建構函式一模一樣,修改後的程式碼如下。

void Main()
{
	Bus bus1 = new Bus(3);
	Car car1 = new Car(bus1);
	MoveForward(car1);
	Console.WriteLine(car1);
}

這種方式也會產生中間物件,但並不會丟失修改的狀態也不會被直接回收,而且表達的語意也比較明確好懂,就是透過多載建構函式就能達到一樣的效果。


Summary

這個做法在說明 implicitexplicit operator 雖然可以讓你的轉型程式碼更加簡潔,但也更難排除問題, 並且方法可能修改的只是中間物件導致狀態遺失,這種問題在大型的應用程式中不好排查,而且透過多載建構函是也能達到同樣的效果,所以不建議用這兩個 operator。