這個做法提供了幾條建議可以讓你寫的多載 API 能夠更加實用以及清楚。

在之前的做法有提到編譯器在選擇最適當的方法有一連串的步驟,所以最好不要增加判斷的變數不然會造成編譯器判斷失誤。

例如這個類別透過多載建立了兩個方法名稱為 Add 的方法,理論上它們要做的事情應該相同但實際上卻沒有,這就會導致誤會, 這種時候就不應該使用多載,反而要提供更明確的名稱例如 AddToListAddToEachValue

public class Vector
{
	private List<double> values = new List<double>();
	
	public void Add(double number) => values.Add(number);
	public void Add(IEnumerable<double> sequence)
	{
		int index = 0;
		foreach (double number in sequence)
		{
			if (index == values.Count)
				return;
			values[index++] += number;
		}
	}
}

下面這段程式碼透過多載建立了針對特定型別的執行方法,並且內部執行的邏輯也一致,但有一個問題是如果今天傳入的是 short,那就會強迫編譯器 幫你選擇更好的多載。

public void Scale(float scaleFactor)
{
   for (int index = 0; index < values.Count; index++)
       values[index] *= scaleFactor;
}
public void Scale(double scaleFactor)
{
   for (int index = 0; index < values.Count; index++)
       values[index] *= scaleFactor;
}

所以這種情況建議是提供完整的多載方法,例如下面這樣。

public void Scale(short scaleFactor)
 {
    for (int index = 0; index < values.Count; index++)
        values[index] *= scaleFactor;
 }
 public void Scale(int scaleFactor)
 {
    for (int index = 0; index < values.Count; index++)
        values[index] *= scaleFactor;
 }
 public void Scale(float scaleFactor)
 {
    for (int index = 0; index < values.Count; index++)
        values[index] *= scaleFactor;
 }
 public void Scale(double scaleFactor)
 {
    for (int index = 0; index < values.Count; index++)
        values[index] *= scaleFactor;
 }

接下來這段程式碼也是類似的問題,注意到提供的引數,一個是 int 一個是 long,最後會呼叫的是第二個都是 double 的多載版本, 解決方法也一樣就是明確提供所有合理的組合。

void Main()
{
	Point p = new Point { X = 5, Y = 7 };
	p.Scale(5, 7L);
}

public class Point
{
	public double X { get; set; }
	public double Y { get; set; }
	public void Scale(int xScale, int yScale)
	{
		X *= xScale;
		Y *= yScale;
	}
	public void Scale(double xScale, double yScale)
	{
		X *= xScale;
		Y *= yScale;
	}
}

這裡的衍生類別擁有一個同名的方法 Scale,注意並沒有使用 new 或者 override,這樣就會造成混淆如果目的是要提供覆寫, 那應該要在基底類別選告成 virtual 比較適當。

void Main()
{
	Point3D p2 = new Point3D { X = 1, Y = 2, Z = 3 };
	p2.Scale(3);
}

public class Point
{
	public double X { get; set; }
	public double Y { get; set; }
	public void Scale(int scale)
	{
		X *= scale;
		Y *= scale;
	}
}
public class Point3D : Point
{
	public double Z { get; set; }
	public void Scale(double scale)
	{
		X *= scale;
		Y *= scale;
		Z *= scale;
	}
}

最後是泛型會導致的判斷問題,下面這段程式碼,第一個呼叫的是泛型 Max<int> 的方法,第二個是Max(double,double), 第三個則是 Max<float> 的方法,會造成這個結果是因為泛型只要有一個參數型別吻合就會優先使用,所以對第一個與第三個呼叫來說 使用 Max(double,double) 需要經過轉換,使用泛型版本第一個參數一定是完全吻合,代表只有第二個參數才有需要轉換,所以用泛型版本比較快速。

void Main()
{
	double a1 = Utilities.Max(1, 3);
	double a2 = Utilities.Max(5.3, 12.7f);
	double a3 = Utilities.Max(5, 12.7f);
}

public static class Utilities
{
	public static double Max(double left, double right) =>
		Math.Max(left, right);
		
	public static T Max<T>(T left, T right)
		 where T : IComparable<T> =>
		(left.CompareTo(right) > 0 ? left : right);
}

Summary

這個做法提供了幾個案例讓我們了解語意模糊不清會帶來的困擾,所以建立多載並不是越多越好而是要提供符合最低需求的多載方法即可, 過多的方法只會產生複雜度,並沒有增強可用性。