VB.NETは共通言語仕様の夢を見るか?

はじめに

.NET Frameworkは言語に依存しないプラットフォームです。 つまり、C#で書かれたライブラリをVBで利用できたり、その逆が出来たりします。

じゃあ、C#で書かれたコードを全てVBで利用できるかといったら、そうではありません。

public class Class1
{
    public int add(int x, int y)
    {
        Console.WriteLine("add");
        return x + y;
    }

    public int ADD(int x, int y)
    {
        Console.WriteLine("ADD");
        return x + y;
    }
}

みたいなコードがあって、これをVBから利用します。 すると

Sub Main()
    Dim c = New Class1()
    Console.WriteLine(c.add(1, 2))
    '                   ~~~
    ' BC31429 class 'Class1' に同じ名前のメンバーが多種類存在するため、'add' があいまいです。
End Sub

みたいに怒られます。

こうなるともう VB側からはどうしようもありません。 泣こうが喚こうがコンパイルすら通りません。VBプログラマに出来ることと言ったら汚い言葉でC#プログラマを罵るぐらいです。

共通言語仕様

このように、それぞれの言語仕様によって命名規則や型がある言語では使えるが他の言語では使えないといったことが出てきます。

さらに.NET FrameworkC#VBだけではありません。 みんな大好きF#、スクリプト御三家のPythonRuby、シス管のつよいみかたPowerShell。 はたまたNetC◯B◯Lなんていう狂気と正気の狭間でプログラマを深淵に引きずり込む暗黒言語もあります。*1

そこで登場するのが共通言語仕様(Common Language Specification)です。

言語への非依存性、および言語非依存コンポーネント | Microsoft Docs

雑に大まかにいうと『異なる言語で実装されたコンポーネントを相互運用するために、コンポーネントが公開する機能が満たすべき規則及び、言語が最低限満たすべき仕様』ってことです。

そして、VBC#の言語仕様はそのCLSのスーパーセットですから、CLSに準拠したコンポーネントを利用できるというわけです。

上の例の場合、ADDaddは、『識別子が異なる場合は大文字小文字の違いだけではない』と『オーバーロードで解決出来る場合を除きスコープ内で名前は一意でなくてはならない』を満たさないのでCLSに準拠していないとなります。

<CLSCompliant(True)>

VBC#でCLSに準拠したコンポーネントを利用できるのは分かったけど、じゃあCLSに準拠したコンポーネントを作るときはどうすればいいのさ? もしかしてプログラマが規格票とにらめっこして頑張ってCLSに準拠するようにしないといけないの?』

それはさすがにメンドクサイしつらぽよポイント爆アゲなので、厄介ごとは全部コンパイラに任せましょう。

Visual Studioを使っているならAssemblyInfo.vbC#ならAssemblyInfo.cs)に<Assembly: CLSCompliant(True)>C#なら[assembly: CLSCompliant(true)])を突っ込みましょう。

すると、先ほどの例では

警告 CS3005 大文字、小文字の違いのみの識別子 'Class1.ADD(int, int)' は CLS に準拠していません。

と怒られます。

と、言った感じでVisual Studio 2015ではCLS違反を警告してくれます。

えっ、警告? えっ? 何? 警告を出すだけでコンパイルは通しちゃうの? 準拠してないのに準拠しちゃってますよーって属性くっつけちゃうの? それともアレ? コンパイルは通すけどCLSCompliantはしれっと外すの? 確認しちゃうよ? ILSpyで確認しちゃうよ?

f:id:jyuch:20160224220249p:plain

君には失望したよ

ちょいとヤフーでググると昔はCLSCompliant(true)がくっついていてCLS準拠違反があった場合は問答無用でコンパイルを弾いていたみたいですが、今はそうじゃなくなったみたいですね。

弊社個人の意見としては違反しないよって宣言してるくせに違反しているのにコンパイルを通すのはなんかおかしいと思いますし、違反したまま「CLSに準拠してるZE☆」と属性がくっついちゃってるのは間違ってると思います。*2

まぁ、個人のクッソどうでもいい意見は置いておいて、このようにCLSに準拠していない箇所をコンパイラに検出させることが出来ますので確認は簡単にできちゃいます。

ライブラリ側のCLS適用範囲

CLSはアセンブリ外に公開される機能が満たすべき規則です。そのためPrivateFriendといったアセンブリ外に公開されない要素については適用されません。

Publicは外部に公開されるためもちろん適用されます。またProtectedアセンブリ外で継承先に公開されますから適用範囲となります。

属性の適用範囲

ついでなので、CLSCompliant属性の適用範囲を確認してみましょう。

まず、アセンブリに付与されている場合はそのアセンブリに属しているすべての公開クラス・メソッド・プロパティ・フィールドがCLSに準拠する必要があります。 特定の要素だけがCLSに準拠していない場合はその要素に<CLSCompliant(False)>を付与する必要があります。

上の例では以下のように属性を付与させます。

[CLSCompliant(false)]
public uint add(uint x, uint y)
{
    Console.WriteLine("add");
    return x + y;
}

[CLSCompliant(false)]
public uint ADD(uint x, uint y)
{
    Console.WriteLine("ADD");
    return x + y;
}

なんでタイトルにVBってあるのにサンプルコードはC#なんでしょうね。ってツッコミはなしの方向でお願いします。

おわりに

ここまでダラダラCLSについて垂れ流したわけですが、特定の言語のみで実装する場合は正直気にする必要もない知識ですし、言語のチャンポンも後々のメンテナスで死にそうな感じなのでそこまで一応こんなものもあるのかな〜程度に認識で良いと思います。

ただ、ライブラリ開発者は一応気にした方が良いと良いと思います。VBで使えないのはつらいもんな

ちなみに余談ですが、弊社の某ライブラリはCLSに準拠してません。次回あたりまでに良い感じになんとかしておくと思うので乞うご期待くださいって感じで。

さらに余談ですが、当初はタイトルを

VB.NETでもCLSに準拠したライブラリを書きたい

だったのですが、内容がCLSCompliantをくっつけろで終わりそうだったのでこんなタイトルになりました。

github.com

おわり

*1:いやまぁ、こいつらがCLSに準拠しているかどうかまでは知りませんが。

*2:コンパイルオプションで特定の警告をエラーに昇格させることが出来るので、気になる人はそっちで対応しろって感じですかね?