ここでは、そもそもクラスライブラリとは何かということはおいておいて、その作り方と要点だけを見ていきます。 今回はC#でクラスライブラリを作り、それをVB.NETで利用するという方法をとりますが、もちろんその逆もできますし、C#のクラスライブラリをC#で使ったりVB.NET同士で作ったり使ったりすることもできます。 (個人的にはVB.NETでクラスライブラリを作る気にはなりませんが…) さらに、詳しいことはあとで説明しますが、.NET FrameworkのCLS(共通言語仕様)に準拠した言語同士ならこのクラスライブラリを使用することができます。

Sponsored Link

ソリューションの準備

それでは早速クラスライブラリを作りましょうといきたいところですが、その前にソリューションの準備を行います。 基本的にはダイアログに従って進めていくだけなので、特別な手順はほんの少しです。

クラスライブラリ用のプロジェクトを用意する

まず、何もソリューションが開かれていない状態で、「ファイル」メニューから「新規作成」「プロジェクト」をクリックします。 現れた「新しいプロジェクト」ダイアログで「Visual C# プロジェクト」の「クラスライブラリ」を選択します。 これでないと当然クラスライブラリを作ることはできません。 後は、場所とプロジェクト名に適当な名前を入れてやります。 ここでは「ClassLibrary」としました。

「新しいプロジェクト」ダイアログ

後はOKボタンを押してやればプロジェクトが作成されるはずです。 一部名前を変えていますが、プロジェクトの構成は図のような感じになります。 これ以降このプロジェクトはクラスライブラリとしてビルドされます。

作成したプロジェクトの構成

デバッグ用のプロジェクトを用意する

つぎに、デバッグ用のプロジェクトを先ほどのソリューションに追加します。 クラスライブラリにはエントリーポイントはなく、実行のしようがないので、作成したクラスライブラリを読み込みデバッグするためのプロジェクトを作成します。

「新しいプロジェクト」ダイアログ

先ほどと同じ要領で「新しいプロジェクト」ダイアログを開き、「Visual Basic プロジェクト」の「コンソール アプリケーション」を選択し、プロジェクト名と場所を指定します。  ここでは「SampleProject」としました。 このとき、必ず「ソリューションに追加」にチェックを入れてください。 そうしないと新しいソリューションのプロジェクトとして作成されてしまいます。 ちなみに、作成するクラスライブラリの用途によっては「Windows アプリケーション」でデバッグすべき場合等もあるので用途に応じて適切なものを選択してください。

後はOKボタンを押してやるだけです。 プロジェクトが作成されると、ソリューション構成は図のようになります。

ソリューション構成

ソリューションのプロパティを設定する

続いて、作成されたソリューションのプロパティ設定をします。 まず、「ソリューション エクスプローラ」でソリューションを右クリックし、ソリューションのプロパティページを表示させます。

まず、「共通プロパティ」の「スタートアッププロジェクト」をクリックします。 そこで、「シングル スタートアップ プロジェクト」を選択し、プロジェクトをデバッグ用のプロジェクトに設定します。

スタートアッププロジェクト

次に、「プロジェクト依存関係」をクリックします。 このタブでは、プロジェクト同士の依存関係を設定します。 この例ではデバッグ用のプロジェクトはクラスライブラリのプロジェクトに依存するので、「プロジェクト」にSampleProjectを設定し、表示されている依存先の「ClassLibrary」にチェックを入れます。

プロジェクト依存関係

一応、ここまででソリューションのプロパティの設定は完了しているのですが、クラスライブラリのコーディングがある程度完成していて、デバッグを行うたびにライブラリのビルドを行う必要がなくなった場合などには、「構成プロパティ」の「構成」タブで、プロジェクトをビルドするかしないかを設定することができます。

構成プロパティ

デバッグプロジェクトの参照にクラスライブラリを追加する

ここまでで一見すべての作業が終わったように見えますが、重要な事が残っています。 クラスライブラリを使うためには、「参照の追加」でデバッグプロジェクトにクラスライブラリを追加しなければなりません。 「ソリューションエクスプローラ」でデバッグプロジェクトの「参照設定」を右クリックして、表示される「参照の追加」ダイアログの「プロジェクト」タブから、クラスライブラリのプロジェクトを追加します。

参照の追加

後はOKを押してやるだけです。 デバッグ用のプロジェクトにクラスライブラリへの参照が追加され、ソリューションエクスプローラでは図のようなソリューション構成になるはずです。

ソリューション構成

実際にインポートしてみる

このようにして追加されたクラスライブラリをデバッグプロジェクトから呼び出すには、普通の.NET Framework クラスライブラリを呼び出すときと同様に行います。 たとえば、名前空間をインポートしようとすると、図のように選択候補が表示され自作のクラスライブラリを参照できることがわかります。

名前空間のインポート

そして、このときのソリューション構成を見てみると図のようになっているはずです。 ちなみに気づいた方もいるかもしれませんが、スタートアッププロジェクトはボールド体で表示されます。 スタートアッププロジェクトを設定する前のソリューション構成の図と見比べてみてください。

準備が整ったソリューションの構成

クラスライブラリのコーディング

それでは準備が整ったところで早速クラスライブラリのコーディングを開始してみたいと思います。

クラスライブラリのコーディング

今回の例ではクラスライブラリのほうはC#で記述しますが、エントリーポイントがない以外はほとんどいつものコーディングと変わりありません。 クラスライブラリが提供すべきクラスをコーディングするだけでよいのです。 まず手始めに次のようなクラスをいくつか作成しました。

クラスライブラリのコーディング
using System;

namespace ClassLibrary
{
    // メッセージクラス
    public class MessageClass
    {
        public readonly string Message;

        public MessageClass( string message )
        {
            Message = message;
        }

        public void ShowMessage()
        {
            Console.WriteLine( Message );
        }
    }

    // 自作の0除算例外クラス
    public class OriginalDivideByZeroException : Exception
    {
        public OriginalDivideByZeroException( string message ) : base( message )
        {
        }
    }

    // 計算クラス (全メソッドはstatic)
    public class CalculationClass
    {
        public static int Add( int x, int y )
        {
            return x + y;
        }

        public static int Subtract( int x, int y )
        {
            return x - y;
        }

        public static int Multiply( int x, int y )
        {
            return x * y;
        }

        public static int Divide( int x, int y )
        {
            if ( y == 0 ) throw new OriginalDivideByZeroException( "0で除算しました。" );
            return x / y;
        }
    }

}

これらのクラスの概要を説明すると、まずMessageClassはコンストラクタで指定された文字列を、ShowMessage()メソッドによりコンソールに出力するクラスです。 本来ならクラスライブラリでConsole.WriteLine()は呼び出すべきではありませんが、あくまでサンプルなので使っています。 続いて、OriginalDivideByZeroExceptionクラスは、独自に定義した0による除算の例外です。 .NET FrameworkにもSystem.DivideByZeroExceptionというクラスがありますが、これもまたサンプルとしてあえて自作しました。 最後に、すべて静的メソッドであるCalculationClassは、簡単な計算を行うための静的メソッドを提供するためのクラスです。  Divide()メソッドで0による除算を行おうとした場合には先ほどのOriginalDivideByZeroException例外をスルーさせます。 それでは、これらのクラスを実際に使用してみます。

デバッグ用プロジェクト側のコーディング

デバッグ用プロジェクト、クラスライブラリを使う方のコーディングはクラスライブラリを作るよりも単純です。 なぜなら、.NET Frameworkのクラスを利用する場合と全く変わらないからです。 説明するよりも実際にコードと実行結果を見た方が早いです。

デバッグ用プロジェクト側のコーディング
Imports ClassLibrary

Module SampleProject

    Sub Main()

        ' MessageClassのインスタンスを作成
        Dim msg As New MessageClass("クラスライブラリからのメッセージ")

        msg.ShowMessage()

        ' CalculationClassクラスの共有メソッドを呼び出し
        Console.WriteLine(CalculationClass.Add(1, 2))
        Console.WriteLine(CalculationClass.Subtract(5, 3))
        Console.WriteLine(CalculationClass.Multiply(3, 4))
        Console.WriteLine(CalculationClass.Divide(6, 2))

        ' 自作例外OriginalDivideByZeroExceptionの捕獲
        Try

            Console.WriteLine(CalculationClass.Divide(1, 0))

        Catch ex As OriginalDivideByZeroException

            Console.WriteLine(ex.Message)

        End Try

    End Sub

End Module
実行結果
クラスライブラリからのメッセージ
3
2
12
3
0で除算しました。
Press any key to cotinue

先ほど述べたように、普通にクラスを利用する場合と全く変わりません。 動作についても説明するまでもないと思います。

出力結果とbinフォルダの中身

ビルド後の出力ペインへのビルド出力を見ると次のようになっていました。

------ ビルド開始 : プロジェクト : ClassLibrary, 構成 : Debug .NET ------

リソースを準備しています...
参照を更新しています...
メイン コンパイルを実行しています...

プロジェクトは最新のものです
サテライト アセンブリをビルドしています...



------ ビルド開始 : プロジェクト : SampleProject, 構成 : Debug .NET ------

リソースを準備しています...
参照を更新しています...
メイン コンパイルを実行しています...
サテライト アセンブリをビルドしています...



---------------------- 終了 ----------------------

    ビルド : 2 正常終了、0 失敗、0 スキップ

ビルドの順番を見ればわかるように、先にクラスライブラリがビルドされ、その次にデバッグ用のプロジェクトがビルドされています。 また、デバッグ出力は次のようになっています。

'DefaultDomain': 'd:\windows\microsoft.net\framework\v1.0.3705\mscorlib.dll' が読み込まれました。シンボルは読み込まれませんでした。
'SampleProject': 'E:\Visual C# .NET Program\BaseTechnologies\SampleProject\bin\SampleProject.exe' が読み込まれました。シンボルが読み込まれました。
'SampleProject.exe': 'e:\visual c# .net program\basetechnologies\sampleproject\bin\classlibrary.dll' が読み込まれました。シンボルが読み込まれました。
'SampleProject.exe': 'd:\windows\assembly\gac\microsoft.visualbasic\7.0.3300.0__b03f5f7f11d50a3a\microsoft.visualbasic.dll' が読み込まれました。シンボルは読み込まれませんでした。
プログラム '[3712] SampleProject.exe' はコード 0 (0x0) で終了しました。

3行目の出力の通り、SampleProject.exeは確実にClassLibrary.dllを読み込んでいます。 続いて、それぞれのbinフォルダに生成されたファイルを見てみることにします。

ClassLibraryのbinフォルダ
SampleProjectのbinフォルダ

このように、クラスライブラリはdllとその中間ファイルだけが生成されているのに対し、それを使う方は実行可能ファイルとクラスライブラリが生成されています。 ただ実際には生成ではなく、ビルド前にクラスライブラリのコピーが作られたのです。

共通言語仕様(CLS)

C#ではコンパイルできるのに、VB.NETでは使えないコード

さて、クラスライブラリを設計している段階で、CalclationClassはすべてuint型で計算さるように変更し、さらに新しくint型の値を保持し、+と-演算子をオーバーロードしそれにより計算を行うクラスを作成しました。 次のようなコードです。

クラスライブラリ
using System;

namespace ClassLibrary
{
    // 計算クラス (全メソッドはstatic、引数はuint)
    public class CalculationClass
    {
        public static uint Add( uint x, uint y )
        {
            return x + y;
        }

        public static uint Subtract( uint x, uint y )
        {
            return x - y;
        }

        public static uint Multiply( uint x, uint y )
        {
            return x * y;
        }

        public static uint Divide( uint x, uint y )
        {
            if ( y == 0 ) throw new OriginalDivideByZeroException( "0で除算しました。" );
            return x / y;
        }
    }

    // 値クラス
    public class ValueClass
    {
        private int m_Value;

        public ValueClass( int val )
        {
            m_Value = val;
        }

        public static ValueClass operator+ ( ValueClass x,  ValueClass y )
        {
            return new ValueClass( x.m_Value + y.m_Value );
        }

        public static ValueClass operator- ( ValueClass x,  ValueClass y )
        {
            return new ValueClass( x.m_Value - y.m_Value );
        }

    }

    // 以下略

続いて、デバッグ側では先ほどのコードに、ValueClassのデバッグ用に次のコードを追加しました。

デバッグプログラム
' 途中略

' ValueClassのインスタンスを作成
Dim a As New ValueClass(3)
Dim b As New ValueClass(2)

Dim result As ValueClass = a - b

' 以下略

ここまで完了した時点で、コンパイルしてみました。 ところが次のようなエラーが出てしまいました。

------ ビルド開始 : プロジェクト : ClassLibrary, 構成 : Debug .NET ------

リソースを準備しています...
参照を更新しています...
メイン コンパイルを実行しています...

ビルドの完了 -- エラー 0、警告 0
サテライト アセンブリをビルドしています...



------ ビルド開始 : プロジェクト : SampleProject, 構成 : Debug .NET ------

リソースを準備しています...
参照を更新しています...
メイン コンパイルを実行しています...
E:\Visual C# .NET Program\BaseTechnologies\SampleProject\SampleProject.vb(13) : error BC30311: 型 'Integer' の値を 'System.UInt32' に変換できません。
E:\Visual C# .NET Program\BaseTechnologies\SampleProject\SampleProject.vb(13) : error BC30311: 型 'Integer' の値を 'System.UInt32' に変換できません。
E:\Visual C# .NET Program\BaseTechnologies\SampleProject\SampleProject.vb(14) : error BC30311: 型 'Integer' の値を 'System.UInt32' に変換できません。
E:\Visual C# .NET Program\BaseTechnologies\SampleProject\SampleProject.vb(14) : error BC30311: 型 'Integer' の値を 'System.UInt32' に変換できません。
E:\Visual C# .NET Program\BaseTechnologies\SampleProject\SampleProject.vb(15) : error BC30311: 型 'Integer' の値を 'System.UInt32' に変換できません。
E:\Visual C# .NET Program\BaseTechnologies\SampleProject\SampleProject.vb(15) : error BC30311: 型 'Integer' の値を 'System.UInt32' に変換できません。
E:\Visual C# .NET Program\BaseTechnologies\SampleProject\SampleProject.vb(16) : error BC30311: 型 'Integer' の値を 'System.UInt32' に変換できません。
E:\Visual C# .NET Program\BaseTechnologies\SampleProject\SampleProject.vb(16) : error BC30311: 型 'Integer' の値を 'System.UInt32' に変換できません。
E:\Visual C# .NET Program\BaseTechnologies\SampleProject\SampleProject.vb(21) : error BC30311: 型 'Integer' の値を 'System.UInt32' に変換できません。
E:\Visual C# .NET Program\BaseTechnologies\SampleProject\SampleProject.vb(21) : error BC30311: 型 'Integer' の値を 'System.UInt32' に変換できません。
E:\Visual C# .NET Program\BaseTechnologies\SampleProject\SampleProject.vb(33) : error BC30452: 演算子 '-' は、型 'ClassLibrary.ValueClass' および 'ClassLibrary.ValueClass' に対して定義されていません。
サテライト アセンブリをビルドしています...



---------------------- 終了 ----------------------

    ビルド : 1 正常終了、1 失敗、0 スキップ

このようにC#で作成したクラスライブラリ自体にはエラーがなく、正常にビルド完了しているのですが、一方でそれを利用する方では大量のエラーが発生しています。 このエラーを一つ一つ見ていくことにします。 といっても、実質的には二つだけのエラーのようです。

まず、一つ目の「型 'Integer' の値を 'System.UInt32' に変換できません。」ですが、VB.NETの方で引数に指定している値はVB.NETでいうInteger型のリテラル数値です。 対して、メソッドの引数としているのはC#でいうuint型です。 これらはそれぞれSystem.Int32とSystem.UInt32のエイリアスです。 C#側で引数はUInt32と指定されているのにも関わらず、VB.NETにはInt32型であるIntegerしか存在せず、UInt32への型変換が行えません。 そのためにこのようなエラーになります。 この場合、何らかの手段でUInt32に型変換してからメソッドに渡さなければなりません。

続いて、「演算子 '-' は、型 'ClassLibrary.ValueClass' および 'ClassLibrary.ValueClass' に対して定義されていません。」については、C#でちゃんと演算子をオーバーロードしていて、エラーも発生していないので何も問題ないように感じます。 しかし、VB.NETでは演算子のオーバーロードはサポートされていないのでVB.NETでこれをしようとすると、とたんにエラーになります。 これを回避する方法はありません(実はありますが・・・それは後ほど)。

共通言語仕様(CLS)に準拠したコード

このように、ある言語ではサポートされている言語仕様は他の言語ではサポートされていなかったり、ある言語でプリミティブ型として用意されている型は他の言語では存在しないということになると、クラスライブラリは何らかの仕様に従ってコーディングする必要性が出てきます。 そこにあらわれるのが「共通言語仕様 (CLS: Common Language Specification)」です。 これを簡単に説明すると、どの言語にも共通するような言語仕様を装備した仕様のことです。 簡単な図で表すと次の様な感じです。

CLSの概念

さらに、型に対して共通性を持たせるためにCLSの一部として「共通型システム (CTS: Common Type System)」も存在します。 System.Int32がC#ではint、VB.NETではIntegerとなっているのはこれによるものです。

つまり、共通言語仕様・CLSに準拠したコードでクラスライブラリを記述すれば、他のCLSに準拠した言語において使用できる汎用的なクラスライブラリを作ることができます。 それでは、CLSはどのような仕様なのか、MSDNに記述されているCLSの仕様から、その抜粋を次の表にまとめてみました。

CLSの仕様(抜粋)
機能 説明
一般 グローバル メンバ グローバルで静的なフィールドとメソッドは CLS 準拠ではありません。
名前付け 文字および大文字と小文字の区別 2 つの識別子の区別には、大文字と小文字の相違は使用できません。
シグネチャ 型やメンバのシグネチャに含まれる戻り値の型およびパラメータの型はすべて CLS 準拠であることが必要です。
プリミティブ型 .NET Framework クラス ライブラリには、コンパイラが使用するプリミティブ データ型に対応する型が含まれています。これらの型のうち、次の型が CLS 準拠です。Byte、Int16、Int32、Int64、Single、Double、Boolean、Char、Decimal、IntPtr、および String。
コンストラクタの呼び出し コンストラクタは、継承されたインスタンス データにアクセスする前に、基本クラスのコンストラクタを呼び出す必要があります。
型のメンバ オーバーロード インデックス付きのプロパティ、メソッド、コンストラクタはオーバーロードできます。フィールドとイベントはオーバーロードしません。 プロパティについては、型 (プロパティの getter メソッドの戻り値の型) によるオーバーロードはできませんが、インデックスの数や型が異なる場合のオーバーロードは可能です。 メソッドの場合は、それぞれのパラメータの数および型に基づくオーバーロードだけは可能です。
演算子のオーバーロードは CLS 準拠にはなりません。ただし、CLS には、Add() などの有用な名前を付け、メタデータのビットを設定する方法についてのガイドラインが用意されています。演算子のオーバーロードをサポートするコンパイラはこのガイドラインに準拠することが推奨されますが、必須ではありません。
変換演算子 op_Implicit または op_Explicit のいずれかがその戻り値の型でオーバーロードされた場合は、その代わりとなる変換方法を提供する必要があります。
ポインタ型 ポインタ ポインタ型および関数ポインタ型は CLS 準拠ではありません。
クラス型 継承 CLS 準拠のクラスは、CLS 準拠のクラスから継承する必要があります。System.Object は CLS 準拠です。
配列 要素型 配列の要素は、CLS 準拠の型であることが必要です。
次元 配列の次元数は、0 より大きい固定数であることが必要です。
範囲 配列の次元の下限値は 0 にする必要があります。

これより詳しくはMSDNの「言語間の相互運用性」→「言語の相互運用性の概要」→「共通言語仕様の概要」のページに載っていますので、これを参考にして下さい。

それでは早速CLSに準拠して先ほどのソースコードを書き直してみます。

CLS準拠コードに書き換えたクラスライブラリ
using System;

namespace ClassLibrary
{
    // 計算クラス (全メソッドはstatic、引数はlong)
    public class CalculationClass
    {
        public static long Add( long x, long y )
        {
            return x + y;
        }

        public static long Subtract( long x, long y )
        {
            return x - y;
        }

        public static long Multiply( long x, long y )
        {
            return x * y;
        }

        public static long Divide( long x, long y )
        {
            if ( y == 0 ) throw new OriginalDivideByZeroException( "0で除算しました。" );
            return x / y;
        }
    }

    // 値クラス
    public class ValueClass
    {
        private int m_Value;

        public ValueClass( int val )
        {
            m_Value = val;
        }

        // 加算演算子
        public static ValueClass operator+ ( ValueClass x,  ValueClass y )
        {
            return new ValueClass( x.m_Value + y.m_Value );
        }
        
        // メソッドによる代替
        public ValueClass Add( ValueClass val )
        {
            return new ValueClass( this.m_Value + val.m_Value );
        }

        // 減算演算子
        public static ValueClass operator- ( ValueClass x,  ValueClass y )
        {
            return new ValueClass( x.m_Value - y.m_Value );
        }

        // メソッドによる代替
        public ValueClass Subtract( ValueClass val )
        {
            return new ValueClass( this.m_Value - val.m_Value );
        }

        // ToString()をオーバーライド
        public override string ToString()
        {
            return m_Value.ToString();
        }

    }


    // 以下略

まず、CalculationClassにおけるuintの部分は、uintの範囲を確実にカバーできるlong型で代用しました。 続いて、 ValueClassの演算子のオーバーロードはVB.NETなどでは使えないので、代替メソッドにより代用します。 同時に、C#同士や演算子のオーバーロードをサポートした他の言語での使用を想定して、演算子オーバーロードは残しておきます。 ちなみに、これらの処理はCLS準拠のガイドラインに従ったものです。

続いて、デバッグ側も仕様の変更に伴い次のように変えました。

CLS準拠のクラスライブラリを使用するコード
Imports ClassLibrary

Module SampleProject

    Sub Main()

        ' MessageClassのインスタンスを作成
        Dim msg As New MessageClass("クラスライブラリからのメッセージ")

        msg.ShowMessage()

        ' CalculationClassクラスの共有メソッドを呼び出し
        Console.WriteLine(CalculationClass.Add(1L, 2L))
        Console.WriteLine(CalculationClass.Subtract(5L, 3L))
        Console.WriteLine(CalculationClass.Multiply(3L, 4L))
        Console.WriteLine(CalculationClass.Divide(6L, 2L))

        ' 自作例外OriginalDivideByZeroExceptionの捕獲
        Try

            Console.WriteLine(CalculationClass.Divide(1L, 0L))

        Catch ex As OriginalDivideByZeroException

            Console.WriteLine(ex.Message)

        End Try

        ' ValueClassのインスタンスを作成
        Dim a As New ValueClass(3)
        Dim b As New ValueClass(2)

        Dim result As ValueClass = a.Subtract(b)

        Console.WriteLine(result)

        result = a.Add(b)

        Console.WriteLine(result)

    End Sub

End Module

まず、CalclationClassのメソッドを使用する際には、long(System.Int64,VB.NET-Long)で渡す必要があるので、Long型の数値リテラルを表すサフィックスLをつけます。 つけなくてもVB.NETでは自動的にLongに変換されますが、ここではわかりやすさのためにつけています。 続いてValueClassを用いた例では、演算子ではなく代替メソッドにより演算させます。 このコードをコンパイルすると無事成功するはずです。 実行結果は次のようになります。

実行結果
クラスライブラリからのメッセージ
3
2
12
3
0で除算しました。
1
5
Press any key to continue

さらに、このときのクラスビューはこのようになっているはずです。

クラスビュー

属性によるCLS準拠のチェック

CLS準拠のクラスライブラリをコーディングするに当たり、便利な属性が存在します。 今回の例のように、C#でコーディングしていて、コンパイルは成功したが、いざVB.NETで使用するとエラーが生じ、調べてみるとCLSには準拠していなかったという事態が生じると思います。 そこで、C#でのコンパイルの時点で、コードがCLSに準拠しているか否かをチェックするための属性CLSCompliantAttributeが存在します。 ひとまず先ほどのコードにそれを適用した例を見てみます。

CLSCompliantAttributeの適用
using System;

[assembly: CLSCompliant(true)] // このアセンブリをCLS準拠とする

namespace ClassLibrary
{
    // 計算クラス
    public class CalculationClass
    {

        [CLSCompliant(true)] // このメソッドをCLS準拠とする
        public static long Add( long x, long y )
        {
            return x + y;
        }

        [CLSCompliant(true)] // このメソッドをCLS準拠とする
        public static uint Subtract( uint x, uint y )
        {
            return x - y;
        }

        [CLSCompliant(false)] // このメソッドをCLS非準拠とする
        public static uint Multiply( uint x, uint y )
        {
            return x * y;
        }

        // 属性の指定無し
        public static uint Divide( uint x, uint y )
        {
            if ( y == 0 ) throw new OriginalDivideByZeroException( "0で除算しました。" );
            return x / y;
        }
    }

    // 以下略

このコードをコンパイルするとデバッグ出力には次のように出力されます。

------ ビルド開始 : プロジェクト : ClassLibrary, 構成 : Debug .NET ------

リソースを準備しています...
参照を更新しています...
メイン コンパイルを実行しています...
e:\visual c# .net program\basetechnologies\classlibrary\classlibrary.cs(18,32): error CS3001: 引数の型 'uint' は CLS に準拠していません。
e:\visual c# .net program\basetechnologies\classlibrary\classlibrary.cs(18,40): error CS3001: 引数の型 'uint' は CLS に準拠していません。
e:\visual c# .net program\basetechnologies\classlibrary\classlibrary.cs(18,17): error CS3002: 'ClassLibrary.CalculationClass.Subtract(uint, uint)' の戻り値の型は CLS に準拠していません。
e:\visual c# .net program\basetechnologies\classlibrary\classlibrary.cs(30,30): error CS3001: 引数の型 'uint' は CLS に準拠していません。
e:\visual c# .net program\basetechnologies\classlibrary\classlibrary.cs(30,38): error CS3001: 引数の型 'uint' は CLS に準拠していません。
e:\visual c# .net program\basetechnologies\classlibrary\classlibrary.cs(30,17): error CS3002: 'ClassLibrary.CalculationClass.Divide(uint, uint)' の戻り値の型は CLS に準拠していません。

ビルドの完了 -- エラー 6、警告 0
サテライト アセンブリをビルドしています...

まず、ソースコードの3行目で、このアセンブリ全体をCLS準拠であるとしています。 こうすることにより、全ての要素がCLS準拠であるという前提でコンパイルさます。 また、メソッド毎にも指定できます。 17行目でSubtract()メソッドはCLS準拠であるとしているのに、次の行では非準拠型のuintを使用しています。 そのため、エラーが表示されます。 続いて、23行目では引数をfalseに設定しているので、このメソッドはCLS非準拠になります。 そのため、uintを用いてもエラーになりません。 しかしこのような場合、このメソッドを公開する必要性があるのであれば、必須ではありませんが代替メソッドを用意する必要があります。

続いて、29行目で何も属性を指定しなかった30行目のDivide()メソッドは、アセンブリ全体がCLS準拠であるとしているため、Multiply()メソッドのようにCLS非準拠を明記しないとCLS準拠であると解釈されます。 そのためuintを使用していることによりエラーが生じます。

本当にVB.NETには演算子オーバーロードは存在しないか

今まではCLS準拠のコードを用いてクラスライブラリを作成してきました。 そこで学んだのは、VB.NETでは演算子の多重定義(オーバーロード)を使用できないので、代替メソッドを用意する必要がある、というものでした。 しかし、実のところは代替メソッドを使用しなくてもVB.NETからでも使用できます。 次のコードがその例です。

C#で記述したクラスライブラリ
using System;

[assembly: CLSCompliant(true)]

namespace ClassLibrary
{
    public class ValueClass
    {
        private int m_Value;

        // コンストラクタ
        public ValueClass( int val )
        {
            m_Value = val;
        }

        // 加算演算子
        public static ValueClass operator+ ( ValueClass x,  ValueClass y )
        {
            return new ValueClass( x.m_Value + y.m_Value );
        }
        
        // 減算演算子
        public static ValueClass operator- ( ValueClass x,  ValueClass y )
        {
            return new ValueClass( x.m_Value - y.m_Value );
        }

        // 明示的な型変換演算子
        public static explicit operator int ( ValueClass x )
        {
            return x.m_Value;
        }
    }
}
VB.NETで演算子のオーバーロードを使用したクラスを利用する
Imports ClassLibrary

Module SampleProject

    Sub Main()

        Dim a As New ValueClass(3)
        Dim b As New ValueClass(2)

        Dim result As ValueClass
        Dim value As Integer

        result = ValueClass.op_Addition(a, b) ' 加算
        value = ValueClass.op_Explicit(result) ' 明示的型変換

        Console.WriteLine(value)

        result = ValueClass.op_Subtraction(a, b) ' 減算
        value = ValueClass.op_Explicit(result) ' 明示的型変換

        Console.WriteLine(value)

    End Sub

End Module

ここで使用したクラスでは、加減算演算子と明示的型変換演算子をオーバーロードしています(型変換演算子についてはこの文章を参考にして下さい)。 コードの先頭でCLSCompliant属性を指定しているので、このコードはCLS準拠となります。

作成されたクラスライブラリを使用する側では、代替メソッドによりこれらの演算子に変わる方法で演算する必要があるのですが、クラスライブラリ側では代替メソッドは一切用意していません。 しかしながらop_Additionやop_Explicitなどのメソッドにより適切にコードが実行されています。

これらの謎のメソッドはいつ作成されたかというと、C#のコンパイラがIL(中間言語:Intermediate Language)を生成するときに、自動的に作成されます。 インテリセンスなどではあらわれないのですが、使用することができます。 引数の取り方や戻り値は演算子のシグネチャと同じになります。

メンバ一覧にはあらわれない

このことから、VB.NETでは演算子の多重定義はできないが、利用することができるといえます。 ただ、op_Additionが代替メソッドであるといわれればそれまでですが・・・