Effective C# 26.除泛型介面外還要實作傳統介面 Effective C# 26.除泛型介面外還要實作傳統介面 (Implement Classic Interfaces in Addition to Generic Interfaces)

Published on Sunday, October 20, 2024

這個做法提到在某幾種狀況下除了實做泛型介面最好也同時實做傳統介面,例如做法 20 裡面提到的 Car 類別就有同時實做 IComparable<T>IComparable 兩種介面。

首先先看以下 Name 類別,它只有實做 IComparable<T>IEquatable<T> 這兩個泛型介面。

public class Name : IComparable<Name>, IEquatable<Name>
{
	public string First { get; set; }
	public string Last { get; set; }
	public string Middle { get; set; }
	//  IComparable<Name> Members
	public int CompareTo(Name other)
	{
		if (Object.ReferenceEquals(this, other))
			return 0;
		if (Object.ReferenceEquals(other, null))
			return 1; // Any non-null object > null.
		int rVal = Comparer<string>.Default.Compare
			(Last, other.Last);
		if (rVal != 0)
			return rVal;
		rVal = Comparer<string>.Default.Compare
			(First, other.First);
		if (rVal != 0)
			return rVal;
		return Comparer<string>.Default.Compare(Middle,
			other.Middle);
	}
	// IEquatable<Name> Members
	public bool Equals(Name other)
	{
		if (Object.ReferenceEquals(this, other))
			return true;
		if (Object.ReferenceEquals(other, null))
			return false;
		// Semantically equivalent to using
		// EqualityComparer<string>.Default
		return Last == other.Last &&
			First == other.First &&
			Middle == other.Middle;
	}
}

假設我今天寫了一個檢查兩個物件是否相等的方法,並且傳入的類型是 System.Object,你會發現結果會是 false 並不是 true,這是因為 程式背後呼叫的是 System.Object.Equals 方法而不是執行我們在上面實做的 IEquatable<Name> 方法。

public static bool CheckEquality(object left, object right)
{
	if (left == null)
		return right == null;
	return left.Equals(right);
}

void Main()
{
	var n1 = new Name();
	var n2 = new Name();
	
	CheckEquality(n1, n2);
}

這個也比較好解決,如果 CheckEquality 方法是我們自己的程式碼,那就多寫一個泛型版本就能修正這個問題。

public static bool CheckEquality<T>(T left, T right)
	where T : IEquatable<T>
{
	if (left == null)
		return right == null;
	return left.Equals(right);
}

但如果不是的話就只能覆寫 Equals 方法然後內部在呼叫自己寫的 IEquatable<Name> 方法,同時也要記住有修改 Equals 方法就要修改 GetHashCode 方法 ,需要在 Name 類別內加入下面這段程式碼。

public override bool Equals(object obj)
{
	if (obj.GetType() == typeof(Name))
		return this.Equals(obj as Name);
	else return false;
}

public override int GetHashCode()
{
	int hashCode = 0;
	if (Last != null)
		hashCode ^= Last.GetHashCode();
	if (First != null)
		hashCode ^= First.GetHashCode();
	if (Middle != null)
		hashCode ^= Middle.GetHashCode();
	return hashCode;
}

最後還有處理運算子 ==!= 才算修改完成

public static bool operator ==(Name left, Name right)
{
	return left.Equals(right);
}
public static bool operator !=(Name left, Name right)
{
	return !left.Equals(right);
}

針對相等性的修改已經足夠了,但我們還有實做 IComparable<T> 接下來把 IComparable 也實做完成,同時也處理一下運算子, 最終成果會像下面這樣。

public class Name : IComparable<Name>, IEquatable<Name>, IComparable
{
	public string First { get; set; }
	public string Last { get; set; }
	public string Middle { get; set; }
	//  IComparable<Name> Members
	public int CompareTo(Name other)
	{
		if (Object.ReferenceEquals(this, other))
			return 0;
		if (Object.ReferenceEquals(other, null))
			return 1; // Any non-null object > null.
		int rVal = Comparer<string>.Default.Compare
			(Last, other.Last);
		if (rVal != 0)
			return rVal;
		rVal = Comparer<string>.Default.Compare
			(First, other.First);
		if (rVal != 0)
			return rVal;
		return Comparer<string>.Default.Compare(Middle,
			other.Middle);
	}

	public int CompareTo(object obj)
	{
		if (obj.GetType() != typeof(Name))
			throw new ArgumentException("Argument is not a Name object");
		return this.CompareTo(obj as Name);
	}

	// IEquatable<Name> Members
	public bool Equals(Name other)
	{
		if (Object.ReferenceEquals(this, other))
			return true;
		if (Object.ReferenceEquals(other, null))
			return false;
		// Semantically equivalent to using
		// EqualityComparer<string>.Default
		return Last == other.Last &&
			First == other.First &&
			Middle == other.Middle;
	}

	public override bool Equals(object obj)
	{
		if (obj.GetType() == typeof(Name))
			return this.Equals(obj as Name);
		else return false;
	}
	
	public override int GetHashCode()
	{
		int hashCode = 0;
		if (Last != null)
			hashCode ^= Last.GetHashCode();
		if (First != null)
			hashCode ^= First.GetHashCode();
		if (Middle != null)
			hashCode ^= Middle.GetHashCode();
		return hashCode;
	}

	public static bool operator ==(Name left, Name right)
	{
		return left.Equals(right);
	}
	public static bool operator !=(Name left, Name right)
	{
		return !left.Equals(right);
	}
	public static bool operator <(Name left, Name right)
	{
		return left.CompareTo(right) < 0;
	}
	public static bool operator >(Name left, Name right)
	{
		return left.CompareTo(right) < 0;
	}
	public static bool operator <=(Name left, Name right)
	{
		return left.CompareTo(right) <= 0;
	}
	public static bool operator >=(Name left, Name right)
	{
		return left.CompareTo(right) >= 0;
	}
}

Summary

這個做法主要在建議同時實做舊的介面,這樣做的目的主要在保持兼容性,但大部分的程式在搬到 .net core 之後都重寫過了,除非是自己公司 有需要兼容舊的環境才有必要繼續實做舊的介面。