這個做法延伸做法 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));
}

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

是因為介面有兩種實做方式分別是 explicit implementationimplicit 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 方法給之後衍生類別實做細節。