読者です 読者をやめる 読者になる 読者になる

亀岡的プログラマ日記

京都のベッドタウン、亀岡よりだらだらとお送りいたします。

C#とNVIパターン

C#

先日、怖いもの知らずにもEffective C#C++大阪に行ってきたのですよ。

かなーり濃ゆいC++の話が聞けて満足でした。その中で色々気になる話をメモっていたのですが、そんななかでC++11にて、C++で暗黙実装されるメソッド(コンストラクタ・デストラクタ・コピーコンストラクタ・ムーブコンストラクタ・コピー演算子・ムーブ演算子など)を、デフォルトのものを使うように指定できるdefaultや、実装をなくす(外から見えなくする?)deleteといったキーワードが追加されている、ということを知りました。
(いや実際には会社で新機能の説明はざっくり受けてきたので聞いたことはあるはずなのですが完全に忘れていたという・・・)


んでんで、調べていたらイグトランスさんのページに行き着いて

そこでNVIパターンというのを知りまして。んでそれについてC#どとうなるのかな?と調べていたら色々知らなかったことが出てきたので、ちょっとメモをしておこうかと。

NVIパターンとは?

NVIパターンというのは、Non Virtual Interfaceの略でして、読んで字のごとく「Virtualなメソッドをインタフェースとして公開するな」というものです。なぜかというと、Virtualなメソッドをインタフェースとして使う、ということは「機能をオーバーライドにより書き換えてもらう」という機能と「外部にその機能を使ってもらう」という機能がひとつのメソッドに集まってしまうので、単一機能の原則に反するからですね。

これは、デザインパターンの一つTemplate Methodの実装などによく現れています。

AbstractClass で定義されている抽象メソッドの可視性が protected なのは、このメソッドが AbstractClass#templateMethod() 内でのみ使用されることを想定しているからである。

さてさて。
しかしながらprotectedにしていると、本来TemplateMethodとして組み合わされてつかわれるべき各々のメソッドが継承先から個別で呼べてしまう、という欠点もあります。なので、C++ではprivate virtualで宣言する場合が多いそうなのですよね。(イグトランスさんのページでもそうなっています)

だからC#でもこう書きたいです。

public class Base{
	public int SomeMethod(int a, int b)
	{
		return Hoge(a) + Fuga(b);
	}

	virtual  private int Hoge (int a)
	{
		throw new NotImplementedException ();
	}

	virtual private int Fuga (int b)
	{
		throw new NotImplementedException ();
	}
}

ですが。

Error CS0621: `Test.Base.Hoge(int)': virtual or abstract members cannot be private (CS0621) (Test)
Error CS0621: `Test.Base.Fuga(int)': virtual or abstract members cannot be private (CS0621) (Test)

(´・ω・`)

C#にはprivate virtualなんて無かった。なんで?

翻ってC#には、private virtualなんてありません。あらら。なのでprotected継承ですよね、という話になります。
なんでなんでしょ?それを考えてたらこんなページに行き着いてました。

そこには割と興味深いお話が。

This code would actually compile in the initial .NET 1.0 release of C#, and the technique was perfectly valid in the CLR based upon the fact that the CLI spec at the time and C# wanted to match the C++ semantics as closely as possible.

.NET 1.0ではC++との互換性の観点から、private virtualはValidだったとのこと。それが.NET 1.1では取り除かれたそうです。それはなぜか。

If a private virtual method can be overridden, then so can an internal virtual method. And therein lies the problem. It would allow you to override the behavior of an internal virtual method on some random class in a particular assembly, and that is the source of the security hole. So, a tradeoff was made, and every release starting with the 1.1 release has this feature disabled.

private virtualがValidならば、当然internal virtualもValidなはず。そうすると、有るアセンブリの中にあるメソッドの振る舞いを外部のアセンブリから変えうる、ということになります。これはセキュリティホールになり得るので、セキュリティ上の安全を確保するために、C++との互換性を下げるのとトレードオフに、この機能を削った、ということですね。(なので当然C++/CLIでもこの機能はオミットされています)

確かにC++では別アセンブリにあるクラスをこちら側でオーバーライドすることは実質非常にむずかしいです(ですよね??)なので、こういったことが問題にならなかった、ということなんでしょうね。

基本的には何事にも理由はあるもんです。何も考えずに機能がないことに対して声高に避難をする前に、作った時の思想・哲学というものは理解すべきですよね。


なかなか興味深い調べ物ができました。ネット上に素晴らしい情報を置いてくれている皆さんに感謝。