連載
One Point .NET

クロス言語開発は本当にできるのか?

吉松 史彰
2002/06/20


Face/Off

 VB.NETとC#には、ほかにも異なる機能がある。インターフェイスの実装もその1つだ。例えば次のようなインターフェイスが定義されていたとしよう。

public interface IOfTheTiger {
  void Yell();
}

 C#では、インターフェイスを実装する方法は2つある。1つは、インターフェイスのメソッド定義と同じシグネチャのpublicメソッドを定義する方法である。

public class Rocky : IOfTheTiger {
  public virtual void Yell() {
    Console.WriteLine("Rocky: The Italian Stallion, Rocky Balboa!");
  }
}

 もう1つは、明示的にインターフェイスの実装を行うことだ。

public class Rocky : IOfTheTiger {
  void IOfTheTiger.Yell() {
    Console.WriteLine("IOfTheTiger: Adriaaaaaaaaann!");
  }
}

 どちらの場合も、呼び出し方は同じだ。

IOfTheTiger iot = new Rocky();
iot.Yell();

 2つ目の実装では、メソッド名が違うように見えるが、iot.IOfTheTiger.Yell()として呼び出すことはできない。

 両者の違いは何だろうか? その答えは次のコードで分かるだろう。

Rocky balboa = new Rocky();
balboa.Yell();

 このコードは、1つ目のRockyクラスに対してはコンパイルできるが、2つ目のRockyクラスに対してはコンパイル・エラーになる。理由は、2つ目のRockyクラスにはYellメソッドが定義されていないからである。2つ目のC#の構文は特殊で、publicやprivateという修飾ができない。何だか使えない機能のような気がしてしまうが、複数のインターフェイスが同じ名前のメソッドを定義しているときには、この実装方式が避けられないこともある。事実、この実装方式はC#の新機能として宣伝されている機能だ。

 さて、『Rocky』で大金持ちになったスタローンは、当然『Rocky2』を作ることになる。

public class Rocky2 : Rocky {
  public override void Yell() {
    Console.WriteLine("Rocky2: Rocky! Rocky! Rocky!");
  }
}

 このクラスをコンパイルできるのは、1つ目のRockyの実装があるときだけだ。2つ目の実装に対してこのように派生クラスを定義しても、コンパイル・エラーになってしまう。2つ目の場合、RockyクラスにはYellメソッドが存在しないからである。もし最初の開発者が2つ目の構文でインターフェイスを実装してしまって、そのインターフェイスの実装をどうしてもオーバーライドしたい場合は、C#では次のようにするしかない。

public class Rocky2 : Rocky, IOfTheTiger {
  public void IOfTheTiger.Yell() {
    Console.WriteLine("Rocky2: Rocky! Rocky! Rocky!");
  }
}

 こうすれば、Rocky2のインスタンスをIOfTheTigerインターフェイスにキャストしてYellメソッドを呼び出したときの動作を変更することができる。

 この点VB.NETはC#よりも高い柔軟性を持っている。VB.NETでは、Rockyクラスの1つ目の方式での実装が当然できる。

Public Class Rocky : Implements IOfTheTiger
  Public Overridable Sub Yell()
    Console.WriteLine("Rocky: The Italian Stallion, Rocky Balboa!")
  End Sub
End Class

 また、VB.NETではC#での2つ目の方式での実装を次のように行うことができる。

Public Class Rocky : Implements IOfTheTiger
  Private Sub Yell() Implements IOfTheTiger.Yell
    Console.WriteLine("IOfTheTiger: Adriaaaaaaaaann!")
  End Sub
End Class

 VB.NET版では、Private修飾がされている。また、メソッド名も特殊なものではなく、通常のメソッド名が使われている。この場合も、この2つ目の方式での実装に対しては、次のC#コードはコンパイルできるが、

IOfTheTiger iot = new Rocky();
iot.Yell();

 次のC#コードはコンパイルできない。この点もC#で書いたRockyクラスと同じである。

Rocky balboa = new Rocky();
balboa.Yell();

 VB.NETの柔軟性は、2つ目の形式の実装を次のようにも書けるところにある。

Public Class Rocky : Implements IOfTheTiger
  Public Sub CallHer() Implements IOfTheTiger.Yell
    Console.WriteLine("IOfTheTiger: Adriaaaaaaaaann!")
  End Sub
End Class

 この場合、次のコードがコンパイルできる。

Rocky b = new Rocky();
b.CallHer();
IOfTheTiger iot = b as IOfTheTiger;
iot.Yell();

 C#ではできないが、VB.NETでは、インターフェイスの明示的な実装と、クラスの公開メンバーを両立できるのである。

 Rockyは全世界で大ヒットしたので、当然Rocky2のVB.NET版も作られるだろう。

Public Class Rocky2 : Inherits Rocky
  Public Overrides Sub Yell()
    Console.WriteLine("Rocky2: Rocky! Rocky! Rocky!")
  End Sub
End Class

 さて、このコードはコンパイルできるだろうか? その答えは、Rockyクラスの実装に依存する。RockyクラスがC#で、しかも2つ目の方式で実装されていた場合はコンパイルできない。この場合でも、Rocky2をC#で作れば、結果的にIOfTheTigerインターフェイスのYellメソッドをオーバーライドできることはすでに説明したとおりだ。それなら、同じ方法でVB.NET版Rocky2を作れるのだろうか?

Public Class Rocky2 : Inherits Rocky : Implements IOfTheTiger
  Public Sub Yell2() Implements IOfTheTiger.Yell
    Console.WriteLine("Rocky2: Rocky! Rocky! Rocky!")
  End Sub
End Class

 このコードは、やはりコンパイルできない。VB.NETでは、基底クラスが実装しているインターフェイスを、明示的に派生クラスで再実装できないのである。派生クラスでは、基底クラスのインターフェイスを実装しているメソッドをオーバーライドする以外にない。ところが、C#の2つ目の方式では、インターフェイスを実装しているメソッドをオーバーライドできない。クラスの公開メンバーではないからである。お手上げだ。

 一方、VB.NETで基底クラスを作った場合でも、おかしなことになる可能性はある。

Public Class Rocky : Implements IOfTheTiger
  Public Overridable Sub Yell()
    Console.WriteLine("Who's this?")
  End Sub
  Public Overridable Sub CallHer() Implements IOfTheTiger.Yell
    Console.WriteLine("IOfTheTiger: Adriaaaaaaaaann!")
  End Sub
End Class

 基底クラスがこのように定義されてしまうと、C#プログラマーは混乱するだろう。「RockyクラスはIOfTheTigerインターフェイスを実装しています」とだけいわれて、C#プログラマーが次のようなコードを書いたら、きっと意図しない結果になるだろう。

public class Rocky2 : Rocky {
  public override void Yell() {
    Console.WriteLine("Rocky2: Rocky! Rocky! Rocky!");
  }
}

 これでは、IOfTheTigerインターフェイスのYellメソッドの実装をオーバーライドしたことにはならない。

What Lies Beneath

 さて、ここまで見てきて、それでもクロス言語開発が魅力的に見えるだろうか(いや、見えるというならそれでもいいのだが……)? それでは、このクロス言語開発を実現するための機能は何のためにできたのだろうか?

 .NET Frameworkのセールス・ポイントの1つは、Common Type System(CTS)である。CTSは、CLSを拡張したものである。CLR上で実行されるコードを生成するプログラミング言語は、最低でもCLSに準拠することが求められている。

 .NET Frameworkでは、CTSによって、プログラミング言語に固有のものだった型システムを、プログラミング言語から奪い取った。その理由は、安全なコードの実行と、危険なコードの排除である。昨今しょっちゅう話題に上る、プログラムの不具合を突いたセキュリティ攻撃、特にバッファ・オーバーフロー攻撃は、コンパイラやリンカ、それにOSが検出できなかった、コードの危険性を突いて実行されるものだ。CLRでは、プログラムが安全に実行できるものであることを保証するために、型システムをランタイムに導入して、コードの安全性確認(ベリファイ)を行えるようにしたのだ。具体的には、配列にポインタ演算でアクセスするようなコードは、配列サイズが無視されてしまうので、安全性が確認できない。

 CLRが安全性を確認できない、いわゆるアンセーフ・コードは、そもそもVB.NETでは作れないし、C#でも“unsafe”オプションを明示的に使わないと作れないようになっている(C++コンパイラは“/clr”オプションを使うと、アンセーフ・コードを生成する。現行のC++コンパイラは、「セーフ・コード」を生成することはできない)。アンセーフ・コードは、.NET Frameworkのセキュリティ機構によって、「ユーザーが明示的にインストールした結果、ローカルHDDにある場合以外は」実行できないように設定されている。アンセーフ・コードは、デフォルトでは、例えばネットワーク越しに実行することができない。

The Right Staff

 ランタイムが型システムを共通化したため、結果としてプログラミング言語間で型に関する統一的な合意ができたことになり、クロス言語開発ができるようになってしまった。だが、あくまでもこれは副作用にすぎないので、さまざまな穴が存在するのだ。もう一度書こう。CLRにとって、クロス言語開発ができるということは、あくまでも副作用なのである。それが「できる」からといって、それを「するべきだ」ということにはならないのだ。皆さんのプロジェクトで利用するプログラミング言語を決めるときには、ぜひこの点を考慮してみてほしい。End of Article


 INDEX
  連載 One Point .NET
  クロス言語開発は本当にできるのか?
    1.Misery/Guilty/Basic Instinct
  2.Face/Off/What Lies Beneath/The Right Staff
 
「連載 One Point .NET」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間