這個做法延伸做法 14 的內容,主要在討論 Abstract Method、Virtual Method 與 Interface 之間的差異。

下面是一個常見的抽象類別包含了一個 abstract 方法與 virtual 方法,它們的差異也很明顯,abstract 方法不需要提供實做但是強制要求 衍生類別必須提供實做細節,virtual 方法則是提供實做細節但不強制衍生類別提供新的細節。

如果你在 abstract 方法加上大括號那編譯器會馬上提示錯誤,同樣道理如果在 virtual 方法不加上大括號也會把上提示錯誤。

void Main()
{
	var m = new MyClass();
}

public abstract class MyBaseClass
{
	public abstract int Add();
	public virtual int Add1()
	{
		return 1;	
	}	
}

public class MyClass : MyBaseClass
{
	public override int Add()
	{
		throw new NotImplementedException();
	}
}

這裡直接把 abstract class 改成 interface 會發現竟然也可以定義 abstract 方法與 virtual 方法。

void Main()
{
	var m = new MyClass();
}

public interface MyInterface
{
	public abstract int Add();
	public virtual int Add1()
	{
		return 1;	
	}	
}

public class MyClass : MyInterface
{
	public int Add()
	{
		throw new NotImplementedException();
	}
}

這段程式碼可以看出 Abstract Method 與 Interface 確實有相似之處,都是在另一個型別提供定義然後在新的型別繼承或實做細節。

public class C
{
    public void M()
    {
        MyClass myClass = new MyClass();
    }
}

public interface MyInterface
{
    int Add();
    int Add1()
    {
        return 1;
    }
}

public class MyClass : MyInterface
{
    public int Add()
    {
        throw new NotImplementedException();
    }
}

接下來看看它們之間的差異,首先下面這段程式碼建立了一個 IMessage 介面與 MyClass 類別,由於 MyClass 有實做 IMessage 介面, 所以我們可以透過 IMessage 介面來呼叫 Message 方法。

void Main()
{
	MyClass b = new MyClass();
	b.Message(); // prints "MyClass".
	IMessage m = b as IMessage;
	m.Message(); // prints "MyClass"
}
interface IMessage
{
	void Message();
}
public class MyClass : IMessage
{
	public void Message() => Console.WriteLine(nameof(MyClass));
}

接下來新增 MyDerivedClass 衍生類別,注意到 MyClass.Message 方法並不是 virtual 所以不能透過 override 覆寫 Message 方法, 所以衍生類別使用 new 關鍵字建立新的方法並把舊的方法隱藏起來,這裡只是隱藏並沒有覆寫的操作發生,所以我們還是可以透過介面 IMessage 呼叫 Message 方法。

void Main()
{
	MyDerivedClass d = new MyDerivedClass();
	d.Message(); // prints "MyDerivedClass".
	IMessage m = d as IMessage;
	m.Message(); // prints "MyClass"
}
interface IMessage
{
	void Message();
}
public class MyClass : IMessage
{
	public void Message() => Console.WriteLine(nameof(MyClass));
}
public class MyDerivedClass : MyClass
{
	public new void Message() => Console.WriteLine(nameof(MyDerivedClass));
}

你可能會覺得 MyDerivedClass 是 MyClass 的衍生類別,那 MyDerivedClass 應該也有實做 IMessage 介面才對,但為什麼 呼叫 m.Message() 的時候是回傳 MyClass 而不是 MyDerivedClass?

是因為介面有兩種實做方式分別是 explicit implementation 與 implicit implementation,其中 explicit implementation 是比較使用到的, 下面可以把 MyClass 改成明確實做的方式,使用這種方式目的是為了要確保方法只能透過公開的介面來呼叫,所以在 MyClass 實做這個方法的時候 必須宣告為 private,以免有人從 MyClass 呼叫方法。

void Main()
{
	MyDerivedClass d = new MyDerivedClass();
	d.Message(); // prints "MyDerivedClass".
	IMessage m = d as IMessage;
	m.Message(); // prints "MyClass"
}
interface IMessage
{
	void Message();
}
public class MyClass : IMessage
{
	void IMessage.Message() => Console.WriteLine(nameof(MyClass));
}
public class MyDerivedClass : MyClass
{
	public void Message() => Console.WriteLine(nameof(MyDerivedClass));
}

你會發現沒辦法把 MyDerivedClass 類別改成明確實做的方式,因為它並沒有在他的類別明確實做 IMessage 介面,所以在 MyDerivedClass 類別 確實找不到實做 IMessage 的 Message 方法,最後只能到基底類別尋找並呼叫。

所以只要在 MyDerivedClass 類別明確實做 IMessage 就能改變執行的結果,這樣就能做到在基底類別提供預設實做, 等待未來有需要可以在衍生類別修改實做細節的效果。

void Main()
{
	MyDerivedClass d = new MyDerivedClass();
	d.Message(); // prints "MyDerivedClass".
	IMessage m = d as IMessage;
	m.Message(); // prints "MyDerivedClass"
}
interface IMessage
{
	public void Message();
}
public class MyClass : IMessage
{
	void IMessage.Message() => Console.WriteLine(nameof(MyClass));
}
public class MyDerivedClass : MyClass, IMessage
{
	public void Message() => Console.WriteLine(nameof(MyDerivedClass));
}

不過這裡還是使用 new 建立新方法,所以我們還是能透過 MyClass 呼叫隱藏的 Message 方法, 這個現象在以前的文章 Effective-CSharp-10 有討論過。

void Main()
{
	MyDerivedClass d = new MyDerivedClass();
	d.Message(); // prints "MyDerivedClass".
	IMessage m = d as IMessage;
	m.Message(); // prints "MyDerivedClass"
	MyClass b = d;
	b.Message(); // prints "MyClass"
}

interface IMessage
{
	void Message();
}
public class MyClass : IMessage
{
	public void Message() => Console.WriteLine(nameof(MyClass));
}

public class MyDerivedClass : MyClass, IMessage
{
	public new void Message() => Console.WriteLine(nameof(MyDerivedClass));
}

要處理的話可以透過修改基底類別的方法並將它宣告為 virtual,之後衍生類別使用 override 進行覆寫。

void Main()
{
	MyDerivedClass d = new MyDerivedClass();
	d.Message(); // prints "MyDerivedClass".
	IMessage m = d as IMessage;
	m.Message(); // prints "MyDerivedClass"
	MyClass b = d;
	b.Message(); // prints "MyDerivedClass"
}

interface IMessage
{
	void Message();
}
public class MyClass : IMessage
{
	public virtual void Message() => Console.WriteLine(nameof(MyClass));
}

public class MyDerivedClass : MyClass, IMessage
{
	public override void Message() => Console.WriteLine(nameof(MyDerivedClass));
}

你也可以改用建立抽象類別與方法達到同樣的效果,在 MyClass 中雖然有實做介面但是沒有提供細節,反而是將實做細節的動作推遲到衍生類別, 這段寫法展現出實做介面並不需要完整細節。

void Main()
{
	MyDerivedClass d = new MyDerivedClass();
	d.Message(); // prints "MyDerivedClass".
	IMessage m = d as IMessage;
	m.Message(); // prints "MyDerivedClass"
	MyClass b = d;
	b.Message(); // prints "MyDerivedClass"
}

interface IMessage
{
	void Message();
}
public abstract class MyClass : IMessage
{
	public abstract void Message();
}

public class MyDerivedClass : MyClass, IMessage
{
	public override void Message() => Console.WriteLine(nameof(MyDerivedClass));
}

也可以在基底類別實做介面細節,但是透過另一個內部的虛擬方法同樣能夠把運行細節往後推遲。

void Main()
{
	MyDerivedClass d = new MyDerivedClass();
	d.Message(); // prints "MyDerivedClass".
	IMessage m = d as IMessage;
	m.Message(); // prints "MyDerivedClass"
	MyClass b = d;
	b.Message(); // prints "MyDerivedClass"
}

interface IMessage
{
	void Message();
}
public class MyClass : IMessage
{
	protected virtual void OnMessage() {}
	public void Message()
	{
		OnMessage();
	}
}
public class MyDerivedClass : MyClass, IMessage
{
	protected override void OnMessage() => Console.WriteLine(nameof(MyDerivedClass));
}

Summary

實做介面與 abstract virtual 方法相比彈性更高,因為實做介面的時候可以為 virtual 或者是 abstract, 或者是像最後一個寫法另外提供 virtual 方法給之後衍生類別實做細節。