Standard
ECMA-372
C++/CLI 言語仕様書
西暦2005年12月 初版
ナビゲーション リンクのスキップ

34.7 クラスとメンバ


34.7.1 クラス定義

 ref クラス、値クラス、ないし、インターフェイス・クラスは対応する名前と視認性を持つクラスとして発行されるべきです。それは以下のようにマークされます。
  • "Marshal string(文字列マーシャル)"属性 ansi、autocode、ないし、unicode のどれか一つを持つ(§34.7.3
  • "Type layout(型レイアウト)"属性 auto、explicit、ないし、sequential のどれか一つを持つ(§34.7.3
  • "Special handling(特殊扱い)"属性 beforefieldinit, rtspecialname, serializable, specialname の任意の組み合わせ(serialization 永続化についてのより詳しい情報は、下の注意を参照のこと)
 ネスト化した ref クラスか値クラスは、適切なアクセス可能性を従え、nested でマークされるべきであり、そのクラスがネスト化される型の中に定義されるべきです。
 ref クラスは extends 節と共に発行されるべきです。extends 節は明示的に与えられた直接基底クラスか、デフォルトの基底クラス、[mscorlib]System::Object を指定します。もし、任意のインターフェイスを実装したクラスであれば、対応する implements 節が提示されるべきです。
 値クラスは [mscorlib]System::ValueType を継承しているべきであり、型レイアウト指定に sequential を持ち、 sealed でマークされているべきです。

 インターフェイス・クラスは interface と abstract でマークされているべきです。
[例:
public ref class B { … };

public ref struct D : B {
    ref class N { … };
};

private value struct S { … };

interface class I { … }; 
.class public auto ansi B extends [mscorlib]System.Object { … }
.class public auto ansi D extends B { 
  .class auto ansi nested public N extends [mscorlib]System.Object { … }
}

.class private sequential ansi sealed S extends:
  [mscorlib]System.ValueType { … }

.class interface private abstract auto ansi I { … }

 クラスのエンコード名は、少なくとも、ピリオドでそれぞれの識別子の組を区切っている、その親の名前空間に含んでいます。
[例:
namespace NS1 {
    public struct N {
        ref struct R1 { … };
    };
    namespace NS2 {
        public ref struct R2 {
            value struct V { … };
        };
    }
}
.class public sequential ansi sealed NS1.N extends
    [mscorlib]System.ValueType {
  .class auto ansi nested public R1 extends [mscorlib]System.Object { … }
}
.class public auto ansi NS1.NS2.R2 extends [mscorlib]System.Object {
  .class sequential ansi sealed nested public V extends
    [mscorlib]System.ValueType { … }
}

 ジェネリック型の詳しい情報は、§34.18 を参照のこと。
[注意:CLI 標準では永続化、復号化の過程を定義しておりません。 しかしながら、クラス定義に与えることができるメタデータ属性 serializable を定義することによってそれらの便宜を図る準備をしています。この属性は、デフォルトとして、親オブジェクトが永続化された時、その型の全てのインスタンス・データ・メンバが、維持されるべきであることを指し示しています。CLI 標準はまた、インスタンス・データ・メンバ定義に宛うことができる、そのメンバの親オブジェクトが永続化される時そのメンバが維持されないメタデータ属性 notserialized を定義しています。
 拡張された実装において、これらのメタデータ属性は、例えば、属性を理解するコンパイラによって System::Runtime::Serialization::SerializableAttribute と System::Runtime::Serialization::NonSerializedAttribute を呼ぶことによって、おのおのが、生成されるかもしれません。
 CLI 標準ライブラリ中の全ての型は serializable 属性を持つことが勧告されています。]

34.7.1.1 Abstract クラス
 明示的に abstract 宣言された ref クラスは abstract マークされたクラスとして発行されるべきです。[例:
public ref struct B abstract { … };
.class public abstract … B … { … }


34.7.1.2 Sealed クラス
 明示的に sealed 宣言された ref クラスは sealed マークされたクラスとして発行されるべきです。全ての値クラスは sealed マークをされるべきです。[例:
public ref struct B sealed { … };
private value struct C { … };
.class public … sealed B … { … }
.class private … sealed C … { … }


34.7.2 メンバ・アクセス

 各々の access-specifier(アクセス指定子)は以下のように対応するメタデータ・アクセス可能性属性を持ちます。
C++/CLI アクセス指定子メタデータ・アクセス可能性属性
private private
protected family
public public
internal assembly
protected public famorassem
public protected famorassem
protected private famorassem
private protected famorassem

 各々のメンバは必要に応じてそれ自身のアクセス可能性属性を持つべきでしょう。[例:
public ref class C {
private:
    int m1;
protected:
    int m2;
public:
    int m3;
internal:
    int m4;
protected public:
    int m5;
public protected:
    int m6;
private protected:
    int m7;
protected private:
    int m8;
};
.class public … C … {
    .field private int32 m1
    .field family int32 m2
    .field public int32 m3
    .field assembly int32 m4
    .field famorassem int32 m5
    .field famorassem int32 m6
    .field famandassem int32 m7
    .field famandassem int32 m8
}


34.7.3 データ・メンバ

 それぞれのデータ・メンバは対応する型とアクセス可能性属性を持つフィールドに対応しているべきです。(メンバのアクセス可能性についての情報は §34.7.2 を参照のこと)
 静的データ・メンバは static 属性を持つべきであり、インスタンス・データ・メンバは持たないべきです。[例:
public ref class C {
    int count;
    float* pCoeff;
    array<long long int>^ values;
    C^ next;
    System::Exception^ lastException;
    static int objectCount;
    static String^ name;
};
.class public … C … {
  .field private int32 count
  .field private float32* pCoeff
  .field private int64[] values
  .field private class C next 
  .field private class [mscorlib]System.Exception lastException
  .field private static int32 objectCount
  .field private static string name
}

 もし、静的データ・メンバが initializer(初期化子)を含んでいるのであれば、対応したフィールドの初期化は親クラスの静的コンストラクタ中で実行されるべきです。
 もし、ref ないし、値クラスが(名前空間 System::Runtime::InteropServices 中の)StructLayoutAttribute 属性を持つ場合、コンパイラはメタデータ中の型はカスタム属性として保持しないことを要請されています。その代わりに、コンパイラはそれを直接的にファイル・フォーマット中に発行するべきです。(そのようなメタデータの消費者はこのデータをファイル・フォーマットから取得し、それがカスタム属性であるかのようにデータを返すことを要請されています。)この属性はクラス定義で auto, explicit, そして、sequential 属性を通してデータ構造のレイアウト、アライメント( .pack 指定子を通して)、サイズ( .size 指定子を通して)、そして、文字列のマーシャリングをクラス定義の属性 ansi, auto, そして、unicode を通して、指定するために使われます。
 インスタンス・データ・メンバは(名前空間 System::Runtime::InteropServices 中の) FieldOffsetAttribute 属性を持つことができ、それは、そのメンバの厳密な配置を制御します。属性 StructLayoutAttribute と共に、コンパイラは属性それ事態を発行するよりも、ファイル・フォーマット中に直接 FieldOffsetAttribute の影響を発行するべきでしょう。

[例:
using namespace System::Runtime::InteropServices;
[StructLayout(LayoutKind::Explicit)]
public value class S1 {
    [FieldOffset(0)] int v;
    [FieldOffset(4)] unsigned char c;
    [FieldOffset(8)] int w;
};
.class public explicit ansi … S1 … {
  .pack …
  .size 0
  .field [4] private unsigned int8 c
  .field [0] private int32 v
  .field [8] private int32 w
}
[StructLayout(LayoutKind::Sequential, Pack=4)]
public value class S2 {
    int v;
    unsigned char c;
    int w;
};
.class public sequential ansi … S2 … {
  .pack 4
  .size 0
  .field private unsigned int8 c
  .field private int32 v
  .field private int32 w
}
[StructLayout(LayoutKind::Explicit, Size=12, CharSet=CharSet::Unicode)]
public ref class S3 {
    [FieldOffset(0)] int* pi;
    [FieldOffset(0)] unsigned int ptrValue;
};
.class public explicit unicode S3 … {
  .pack …
  .size 12
  .field [0] private int32* pi
  .field [0] private unsigned int32 ptrValue
}

 リテラル、initonly フィールドについての情報は、§34.7.11 §34.7.12 をそれぞれ参照のこと。
 フィールド定義は随意に noserialized 属性を含むことができます。(永続化(serialization)についてのより詳しい情報は §34.7.1 中の注記を参照のこと。)
 通常、フィールドは rtspecialname や specialname でマークされるべきではありません。しかしながら、 value__ と呼ばれる列挙クラスを発行するインスタンス・フィールドは rtspecialname と specialname でマークされるべきです。
 データ・メンバは(名前空間 System::Runtime::InteropServices 中の)属性 MarshalAsAttribute をそれらに宛うことが可能です。この属性のメタデータ情報は、§34.6.3 を参照のこと。

34.7.4 関数

 関数は .method 指定子として発行されるべきです。通常、メソッド定義は rtspecialname や specialname でマークされるべきではありません。(インスタンス、静的コンストラクタは例外です。それぞれ、§34.7.9 §34.7.10 を参照のこと。)静的関数の定義は static でマークされるべきで、インスタンス関数には instance がマークされるべきです。
 ref クラス、値クラス、そして、インターフェイス・クラスのメンバ関数は hidebysig でマークされるべきです。
 ref クラス、値クラス、そして、インターフェイス・クラスの仮想関数は strict でマークされ、それはそれらの型の引かそうメンバ関数はマークされるべきではありません。[注意:もしそれらもアクセス可能であるのであれば、CLI は strict virtual メソッドは上書きされることができるだけであることを勧告しています。]
 通常、発行されたメソッド名はそのソース中の宣言と同じであるべきです。しかしながら、インスタンス・コンストラクタ(§34.7.9 )、静的コンストラクタ(§34.7.10 )、プロパティ・アクセサ(§34.7.5 )、イベント・アクセサ(§34.7.6)、そして、静的演算子(§34.7.7 )は例外です。
 発行された返却型、そして、その型とパラメータ・リスト中のパラメータ順はその関数のソース宣言と直接的に対応しているべきです。
 関数のアクセス可能性はその .method 指定子の定義に反映されるべきです。(§34.7.2 参照)
 メソッド定義は適切な実装属性、cli managed のような、でマークされるべきです。(以下での議論を参照)
[例:
public ref class C {
    static void compressData(int* p1, String^ p2, Object^ p3) { … }
public:
    void Initialize() { … }
    void Initilaize(int i, int j) { … }
    virtual void Display() { … }
};
.class public … C … {
  .method private hidebysig static void compressData(int32* p1,
      string p2, object p3) cil managed { … }
  .method public hidebysig instance void Initialize() cil managed { … } 
  .method public hidebysig instance void Initilaize(int32 i, int32 j)
    cil managed { … }
  .method public hidebysig strict newslot virtual instance void Display()
    cil managed { … }
}


34.7.4.1 上書き関数
 override-specifier(上書き指定子) の使用は常にメタデータ中に .override 指定子を結果として残すべきで、override-specifier のない function-modifier(関数修飾子) override の使用は残すべきではありません。[例:次に与えられたコードに
public ref struct B {
    virtual void F() {};
    virtual void F(int i) {};
};
public ref struct D1 : B {
    virtual void F() override {}        // 明示的上書き B::F()
};
public ref struct D2 : B {
    virtual void F() override {}        // 明示的上書き B::F()
    virtual void G(int i) = B::F {}     // B::F(int) の名前付き上書き
};
public ref struct D3 : B {
    virtual void F() = B::F {}          // B::F() の明示的上書き
};
 クラス D2 と D3 のために生成された適切なメタデータは次のようになります:
.class public … D2 extends B { 
  .method public virtual instance void F() … {
  …
  }
  .method public newslot virtual final instance void G(int32 i) … {
    .override B::F       // B::F(int32) を上書き
    …
  }
}
.class public … D3 extends B {
  .method public newslot virtual final instance void F() … {
    .override B::F       // B::F() を上書き
    …
  }
}


34.7.4.2 sealed 関数修飾子
 明示的に sealed 宣言された ref クラス関数はメソッドを final でマークして発行されるべきです。[例:
public ref struct R {
    virtual void F() sealed { … }
};
.class … R … {
  .method … final instance void F() … { … }
}


34.7.4.3 abstract 関数修飾子
 明示的に abstract 宣言された ref クラス関数はメソッドを abstract でマークされて発行されるべきです。
[例:
public ref struct R {
    virtual void F1() = 0;
    virtual void F2() abstract;
    virtual void F3() abstract = 0;
};
.class … abstract … R … {
  .method … abstract … void F1() … { … }
  .method … abstract … void F2() … { … }
  .method … abstract … void F3() … { … }
}

 インターフェイス・クラス中の全てのインスタンス関数はメソッドを abstract でマークされて発行されるべきです。

34.7.4.4 newslot 属性
 new 関数修飾子は CLI 定義済み属性 newslot と厳密に対応します。[注意:CLI 標準、パーティションIIによれば:
"仮想メソッドは仮想メソッドの定義によって継承階層が導入されます。バージョン化の意味はその定義が newslot としてマークされているかどうかによって異なります:
 もし、定義が newslot でマークされていれば、その時、例え基底クラスが一致する仮想メソッドを提供していても、その定義は常に新しい仮想関数として生成されます。仮想関数へのどんな参照も、新しい仮想関数が定義される前では、オリジナルの定義を参照し続けるでしょう。
 もし、定義が newslot でマークされていなければ、その時、同名の仮想メソッドとシグネイチャが基底クラスから継承されていなければ、その定義は新しい仮想関数を生成します。もし、継承階層が変更され、その定義が継承された仮想関数と一致すれば、その定義はその継承関数の新しい実装であるものとして取り扱われるでしょう。"

 関数は以下のような場合でのみ newslot でマークされるべきです。
  • 関数がインターフェイスのメンバである。
  • 関数が ref クラスか値クラスの仮想関数であり、その関数の名前が基底クラス中のどこにも名称検索によって見つからない。[注意:インターフェイスは名前検索を無視するので、もし、その名前がインターフェイス中にのみ指定されていれば、その関数はまだ newslot でマークされています。]
  • 関数が new を使って宣言された仮想関数である。

34.7.4.5 特別な属性
 属性 InAttribute と OutAttribute (双方共に名前空間 System::Runtime::InteropServices 中にある)は関数パラメータに適用することができます。コンパイラはこれらの型をメタデータ中にカスタム属性として保持しないことが要請されています。その代わりに、コンパイラはこれらを直接的にファイル・フォーマット中に発行するべきでしょう。(そのようなメタデータの消費者はこのデータをファイル・フォーマットから取得し、それらがカスタム属性であるかのように返すことを要請されています。)[例:
public ref struct C {
    void F(int* p1, [In] int* p2, [Out] int* p3, [In, Out] int* p4) { … }
}; 
.class public … C … {
  .method public instance void F(int32* p1, [in] int32* p2,
      [out] int32* p3, [in][out] int32* p4) … { … }
}

 メソッド定義は様々な実装属性でマークされ得ます。それらのあるものは(名前空間 System::Runtime::InteropServices 中の)MethodImplAttribute 属性を通して指定されることができ、その属性は引数として、一つか、(同じ名前空間中の)型 MethodImplOptions の列挙の組み合わせを取ります。コンパイラはメタデータ中にこの型をカスタム属性として保持しないことを要請されています。その代わり、コンパイラはそれをファイル・フォーマット中に直接発行するべきです。(そのようなメタデータの消費者はこのデータをファイル・フォーマットから取得し、それがカスタム属性であるかのように返すことを要請されます。)[例:
public ref struct C {
    [MethodImpl(MethodImplOptions::NoInlining)] void F1() { … }
    [MethodImpl(MethodImplOptions::Synchronized |
     MethodImplOptions::NoInlining)] void F2() { … }
};
.class public … C … {
  .method public instance void F1() … noinlining { … }
  .method public instance void F2() … synchronized
      noinlining { … }
}


34.7.5 プロパティ

 プロパティは各々のアクセサごとに .property 指定子プラス .method 指定子として発行されるべきです。それ以外のメソッドは発行されるべきではありません。もし、プロパティが get アクセサ関数を持っていたら、.property 指定子は .get 指定子を含むべきです。もし、プロパティが set アクセサ関数を持っていたら、.property 指定子は .set 指定子を含むべきでしょう。メソッド定義は specialname でマークされているべきです。プロパティはそれ自身を rtspecialname や specialname でマークするべきではありません。
 インスタンス・プロパティの定義は instance でマークされるべきです。プロパティが含む任意の .set と .get 指定子はまた、対応するメソッド定義がそうであるように、instance でマークされるべきでしょう。静的プロパティには、そのメソッド定義だけは static でマークされるべきです。

 スカラーや名前付きインデックス化プロパティ P の get アクセサ関数のために発行されるメソッドの名前は get_P であるべきで、set アクセサ関数のためには set_P であるべきです。DefaultMemberAttribute 属性を持たない型中で宣言されたデフォルト・インデックス化プロパティのために、発行されたメタデータはそのプロパティがその属性によって指定された名前を持つ名前付きインデックス化プロパティであるかのようなものであるべきでしょう。

 プロパティのアクセス可能性はその .method(§34.7.2 参照)の定義中に反映されているべきです。[注意:プロパティの get と set アクセサ関数は異なるアクセス可能性を持つことができます。]
[例:
public value class Point { 
    static int pointCount = 0; 
    int x; 
    int y;

public:

    property int X {
        int get() { return x; }
        void set(int val) { x = val; }
    }
    ...
    static property int PointCount {
        int get() { return pointCount; }
    }
};
.class public ... Point ... {
  ...
  .property instance int32 X() {
    .set instance void Point::set_X(int32)
    .get instance int32 Point::get_X()
  }

  .method public specialname instance int32 get_X() ... { ... }

  .method public specialname instance void set_X(int32 val) ... { ... }

  .property int32 PointCount() { 
    .get int32 Point::get_PointCount()
  }
  .method public specialname static int32 get_PointCount() ... { ... }
}
][例:
public ref class IntVector {
    int length;
    array<int>^ values;
public:
    property int default[int] {
        int get(int index) { return values[index]; }
        void set(int index, int value) { values[index] = value; }
    }
}
.class public ... IntVector ... {
  .field private int32 length
  .field private int32[] values

  .property instance int32 Item(int32) {
    .get instance int32 IntVector::get_Item(int32) 
    .set instance void IntVector::set_Item(int32, int32)
  }
  .method public ... int32 get_Item(int32 index) ... { ... }
  .method public ... void set_Item(int32 index, int32 value) ... { ... }
}

 もし、プロパティが virtual を宣言されていたら、それを持つアクセサ・メソッドは newslot virtual でマークされるべきです。もし、プロパティが virtual 宣言されていなくても、その二つのアクセサの双方が宣言されているか、そのただ一つのアクセサであれば、その時、発行されるアクセサは newslot virtual でマークされるべきです。
 もし、プロパティが sealed で宣言されていれば、そのアクセサ・メソッドは newslot virtual final でマークされるべきです。もし、プロパティが sealed で宣言されていなくても、その二つのアクセサの双方が宣言されているか、ただ一つのアクセサが存在するのであれば、その時、発行されるアクセサは newslot virtual final でマークされるべきです。
 もし、プロパティが abstract を宣言されていたら、それを持つアクセサ・メソッドは newslot abstract virtual でマークされるべきです。もし、プロパティが abstract を宣言されていなくても、その二つのアクセサの双方が宣言されているか、そのただ一つのアクセサであれば、その時、発行されるアクセサは newslot abstract virtual でマークされるべきです。
 トリビアル・スカラー・プロパティの場合には、領域確保された private 背景保持フィールドは実装者の名前空間中に名前を持ち、適切に、静的フィールド、ないし、インスタンスを持つべきです。[例:
.class public ... C ... {
  .field private int32 '<backing_store>P' 
  .property instance int32 P() {
    .set instance void C2::set_P(int32) 
    .get instance int32 C2::get_P()
  }

  .method ... int32 get_P() ... {
    .maxstack  1
    .locals (int32 V_0)
    ldarg.0
    ldfld      int32 C2::'<backing_store>P'
    stloc.0
    ldloc.0
    ret
  }
  .method ... void set_P(int32 __set_formal) ... {
    .maxstack  2
    ldarg.0
    ldarg.1
    stfld      int32 C2::'<backing_store>P'
    ret
  }
}

 プロパティのアクセサ・メソッドは様々な実装属性によってマークすることができます。さらなる情報については §34.7.4 を参照のこと。

34.7.6 イベント

 イベントは .event 指定子を通して実装されます。その指定子は一つを追加し、一つを削除するアクセサ関数をおのおの .addon と .removeon 指定子を使って指し示すべきです。 raise アクセサ関数を持つイベントのために、その関数は .fire 指定子を使って .event 指定子中に参照されるべきです。 追加、削除、起動アクセサ関数の名前は xx がイベントの宣言名とすると、おのおのが add_xx、remove_xx、そして、raise_xxであるべきです。 全てのアクセサ関数は specialname でマークされるべきです。 もし、追加や削除アクセサ関数が MethodImpl(MethodImplOptions::Synchronized)属性を持っている場合、結果となるメソッドは syncronized でマークされるべきです。 (§34.7.4 を参照のこと)[例:
public delegate void EvtHandler(Object^ sender, EventArgs^ e);

public ref class Button {
    EvtHandler^ action;
public:
    event EvtHandler^ Click {
        [MethodImpl(MethodImplOptions::Synchronized)]
        void add(EvtHandler^ d) {}
        [MethodImpl(MethodImplOptions::Synchronized)]
        void remove(EvtHandler^ d) { … }
        void raise(Object^ sender, EventArgs^ e) { … }
    }
};
.class public … Button … {
  .field private class EvtHandler action

  .event specialname EvtHandler Click {
    .addon instance void Button::add_Click(class EvtHandler)
    .removeon instance void Button::remove_Click(class EvtHandler)
    .fire instance void Button::raise_Click(object,
    class [mscorlib]System.EventArgs)
}
.method public specialname instance void add_Click(class EvtHandler d)
   … synchronized { … }
.method public specialname instance void remove_Click(class
   EvtHandler d) … synchronized { … }
.method public specialname instance void raise_Click(object sender,
   class [mscorlib]System.EventArgs e) … { … }
}

 トリビアル・イベントは、トリビアル・イベントではストレージはそのデリゲートを保持するフィールドを領域確保し、おのおののイベントごとに、その追加、削除、そして、起動アクセサ関数はデリゲート・フィールドから追加、削除関数が生成され、イベントが起きるという点を除けば、非トリビアルなイベントと同様の方法で取り扱われます。生成された追加と削除アクセサ関数はそれらの親に当たるイベントと同じアクセス指定子を持つべきです。生成された起動アクセサ関数は family でマークされるべきです。
 生成された追加アクセサ関数はデリゲート・フィールドでそのイベントに渡されたデリゲート引数を結合するべきです。 生成された削除アクセサ関数はそのイベントのデリゲート・フィールドからそれに渡されたデリゲート引数を削除するべきです。 生成された起動アクセサ関数は、起動アクセサ関数に与えられた引数のリストを渡して、デリゲート・フィールドに Invoke メソッドを呼び出すべきです。 そのアクセサ関数はその Invoke 呼び出しによって変える値を返すべきでしょう。 スレッド・セーフであるために、生成された追加、削除アクセサ関数は syncronized でマークされるべきです。 生成された起動アクセサ関数はその様にマークされるべきではありません。[例:
public delegate int D(int);

public ref struct X {
    event D^ Ev;
};
.class public … X … {
  .field private class D '<Ev>'

  .event specialname D Ev {
    .addon instance void X::add_Ev(class D)
    .removeon instance void X::remove_Ev(class D)
    .fire instance int32 X::raise_Ev(int32)
  }
.method public specialname instance void add_Ev(class D '<value>')
  … synchronized {
    …
    ldfld class D X::'<Ev>'
    …
    call class [mscorlib]System.Delegate 
      [mscorlib]System.Delegate::Combine(class 
      [mscorlib]System.Delegate, class [mscorlib]System.Delegate) 
        …
    stfld class D X::'<Ev>' 
    …
  }
  .method public specialname instance void remove_Ev(class D '<value>')
    … synchronized {
      …
      ldfld class D X::'<Ev>'
      …
      call class [mscorlib]System.Delegate
        [mscorlib]System.Delegate::Remove(class [mscorlib]System.Delegate,
        class [mscorlib]System.Delegate)
          …
      stfld class D X::'<Ev>'
      …
  }
  .method family specialname instance int32 raise_Ev(int32 value0) … {
    …
    ldfld class D X::'<Ev>'
    …
    callvirt instance int32 D::Invoke(int32)
    …
    ret
  }
}


34.7.7 静的演算子

 実装がCLS-互換演算子のメタデータを発行する時、実装はC++演算子関数をその各々のCLS-互換名称、テーブル19-1:CLS-互換単項演算子、テーブル19-2:CLS-互換二項演算子に示すような、に変換するべきです。実装がメタデータから関数を取り込む時、その関数のCLS-互換名称はそれに対応したC++演算子関数識別子として、それらのテーブルに指示されているように、上書きされるべきでしょう。
 もし、演算子関数がCLS-互換演算子(§19.7.5.1 )の基準に一致しなければ、その演算子はテーブル19-4:C++依存単項演算子とテーブル19-5:C++依存二項演算子がそれらの関数を識別します。
 実装がC++依存関数(テーブル19-4:C++依存単項演算子とテーブル19-5:C++依存二項演算子)をメタデータから取り込む時、これらの関数はそれらに対応するC++識別子を使って取り扱われるべきでしょう。もし、その様な関数が演算子関数であるように見えない(例えば、その関数が3つの引数を取るとか)場合には、その関数名は内部の演算子関数名に置き換えるべきではなく、その関数はメタデータ中でその名前のまま呼ぶことができるべきです。
 全ての静的演算子関数は static と specialname でマークされるべきです。
[例:
 public ref class IntVector { 
     …
 public:
     static IntVector^ operator+(IntVector^ iv, int i);
     static IntVector^ operator+(int i, IntVector^ iv);
     static IntVector^ operator+(IntVector^ iv1, IntVector^ iv2);
     static IntVector^ operator-(IntVector^ iv);
     static IntVector^ operator++(IntVector^ iv);
     …
 };
 .class public … IntVector … { 
   .method public specialname static class IntVector op_Addition(
     class IntVector iv, int32 val) … { … }
   .method public specialname static class IntVector op_Addition(
     int32 val, class IntVector iv) … { … }
   .method public specialname static class IntVector op_Addition(
     class IntVector iv1, class IntVector iv2) … { … }
   .method public specialname static class IntVector op_UnaryNegation(
     class IntVector iv) … { … }
   .method public specialname static class IntVector op_Increment(
     class IntVector iv) … { … }
 }


34.7.8 非静的演算子

 メンバ関数として実装される非静的演算子のメタデータは、非静的演算子の場合には静的なものの代わりにインスタンス・メソッドとして実装するという点を除けば、静的演算子のものとちょうど一緒です。
 全ての非静的演算子関数は specialname でマークされるべきです。
 標準C++と同様であるために、演算子 ++ と演算子 -- のインスタンス版は前置と後置記述で分けて実装する必要があります。[例:
public ref class IntVector {
    …
public:
    IntVector^ operator+(int val);
    static IntVector^ operator+(int val, IntVector^ iv);
    IntVector^ operator+(IntVector^ iv2);
    IntVector^ operator-();
    IntVector^ operator++();
    IntVector^ operator++(int);
    …
};
.class public … IntVector … { 
  .method public specialname class IntVector op_Addition(int32 val)
    … { … }
  .method public specialname static class IntVector op_Addition(
    int32 val, class IntVector iv) … { … }
  .method public specialname class IntVector op_Addition(
    class IntVector iv2) … { … }
  .method public specialname class IntVector op_UnaryNegation() … { … }
  .method public specialname class IntVector op_Increment() … { … }
  .method public specialname class IntVector op_Increment(int32) … { … }
}

 関数 operator+(int, IntVector^) はその最初のパラメータが親クラス型やその型のハンドル型でないので、インスタンス・メソッドとして実装することはできません。]
 演算子がグローバル関数として実装される場合には、それらは assembly でマークされ、その名前はそのソースとなる言語のトークンの厳密なスペル、operator+ には'+'、operator- には'-'、operator++ は'++'、などなど、であるべきです。標準C++と同様であるために、operator++ と operator-- のインスタンス版は前置と後置の記法が分かれて実装されなければなりません。[例:
public ref class IntVector {
…
};
IntVector^ operator+(IntVector^ iv, int val);
IntVector^ operator+(int val, IntVector^ iv);
IntVector^ operator+(IntVector^ iv1, IntVector^ iv2);
IntVector^ operator-(IntVector^ iv);
IntVector^ operator++(IntVector^ iv);
IntVector^ operator++(IntVector^ iv, int);
.class public … IntVector … {
  …
}
.class public abstract … '…' {
  .method assembly specialname static class IntVector '+'(
    class IntVector iv, int32 val) … { … }
  .method assembly specialname static class IntVector '+'(
    int32 val, class IntVector iv) … { … }
  .method assembly specialname static class IntVector '+'(
    class IntVector iv1, class IntVector iv2) … { … }
  .method assembly specialname static class IntVector '-'(
    class IntVector iv) … { … }
  .method assembly specialname static class IntVector '++'( 
    class IntVector iv) … { … }
  .method assembly specialname static class IntVector '++'( 
    class IntVector iv, int32) … { … }
}


34.7.9 インスタンス・コンストラクタ

 ref クラスのインスタンス・コンストラクタはそのクラスの .ctor と呼ばれるインスタンス・メソッドとして発行されるべきです。コンストラクタのアクセス可能性はその定義のものを反映させるべきです。(§34.7.2 参照)そのメソッドは specialname, rtspecialname, instance, cil, そして、managed でマークされ、返値として void を、そして、対応するパラメータ・リストを持つべきです。[例:
public ref class C {
    int v;
    C() { ... }
public:
    C(int i) : v(i) { ... }
};
.class public ... C ... {
  .method private specialname rtspecialname instance void .ctor() ... {
    .maxstack ...
    ldarg.0
    call       instance void [mscorlib]System.Object::.ctor()
    ...
    ret
  }
  .method public specialname rtspecialname instance void .ctor(int32 i) ... {
    .maxstack ...
    ldarg.0
    call       instance void [mscorlib]System.Object::.ctor()
    ldarg.0
    ldarg.1
    stfld      int32 C::v
    ...
    ret
  }
}

 インスタンス・コンストラクタは様々な実装の属性でマークされることができます。詳しい情報は §34.7.4 を参照のこと。

34.7.10 静的コンストラクタ

 ref や値クラスの静的コンストラクタは、そのクラスの .cctor と呼ばれる、 private static メソッドとして発行されるべきです。メソッドは specialname, rtspecialname, static cil, そして、managed とvoid の返値と何の引数も持たないものとしてマークされるべきです。そのクラス自身、beforefieldinit でマークされるべきです。
[例:
public ref class B {
    static B() { ... }
public:
    ...
};
.class public beforefieldinit ... B ... {
  .method private specialname rtspecialname static void .cctor()
      cil managed { ... }
}

 静的コンストラクタは様々な実装属性でマークされ得ます。詳しい情報は §34.7.4 を参照のこと。

34.7.11 literal フィールド

 リテラル・フィールドは特定の初期値を持った public static literal フィールドとして実装されるべきです。[例:
public ref struct X {
    literal int Count = 100;
    literal String^ Greeting = "Hello";
};
.class public ... X ... {
  .field public static literal int32 Count = int32(0x00000064)
  .field public static literal string Greeting = "Hello"
}

 一般的なデータ・メンバのメタデータ生成についての情報は、詳しい情報は§34.7.3 参照のこと。

34.7.12 initonly フィールド

 initonly フィールドは、適切にインスタンス、ないし、静的 initonly フィールドとして実装されるべきです。 フィールドのアクセス可能性はそので意義を反映させるべきです。 明示的に初期化される静的 initonly フィールドごとに静的コンストラクタ中に配置される初期化コードはそれらのフィールドに宣言された構文上の順番で初期化されるようになるべきでしょう。[例:
public ref class X {
    initonly static int V1 = 5, V2 = V1;
    initonly static int V3 = V2 + 1;
    initonly static int V4;
public:
    initonly int V5;
    static X() { V4 = V1 + V3; }
    X(int i) { V5 = i; }
};
.class public ... X ... {
  .field private static initonly int32 V1
  .field private static initonly int32 V2
  .field private static initonly int32 V3
  .field private static initonly int32 V4
  .field public initonly int32 V5

  .method private specialname rtspecialname static void .cctor() ... {
    .maxstack  2
    ldc.i4.5
    stsfld     int32 X::V1

    ldsfld     int32 X::V1
    stsfld     int32 X::V2

    ldsfld     int32 X::V2
    ldc.i4.1
    add
    sdsfld     int32 X::V3

    ldsfld     int32 X::V1
    ldsfld     int32 X::V3
    add
    sdsfld     int32 X::V4
    ret
  }
}
 静的コンストラクタ中で、V1, V2, そして、V3 はその順で初期化され、その全ては V4 の代入の前に行われるべきです。]
 一般のデータ・メンバのメタデータ生成についての情報は、§34.7.3 を参照のこと。

34.7.13 デストラクタとファイナライザ


34.7.13.1 CLI Dispose パターン
 C++/CLI は ref クラス中にデストラクタとファイナライザ構文を、CLI ディスポーズ・パターンを使って実装します。 このパターンは CLI をターゲットとしている全ての言語で同意された三つの関数を使用しています。これらの関数は、
void Dispose();
void Dispose(bool);
void Finalize();
 そして、その定義は、必要にあわせて、コンパイラによって生成されます。二つの他の C++/CLI 特殊プライベート・ヘルパ関数もまた生成され、そして、Dispose(bool) に使われます。それらは、
void __identifier("~T")()
void __identifier("!T")()
であり、T はその親クラスです。
 CLI ディスポーズ・パターンは次を要請します。
  • 関数 Dispose() は System::IDisposable::Dispose() を実装する。
  • 関数 Finalize() は System::Object::Finalize() を上書きする。
  • 関数 Dispose(bool) は、System::IDisposable::Dispose() を実装する関数 Dispose() を持つクラスのメンバか、System::Object::Finalize() を上書きする Finalize() を持つクラスのメンバであるか、Dispose() や Finalize() 関数を持つ基底クラス中でそれ自身 Dispose(bool) を上書きする Dispose(bool) である。
 これらのシグネイチャをどれかを持つような関数定義を含むこと C++/CLI プログラムは不正です。[注意:もしその様な場合の検証結果が代わりにデストラクタと/やファイナライザを定義することをプログラマに勇気づけるのであれば、それはプログラマにとって大変よい手助けになるでしょう。]これらのシグネイチャを持つ関数定義は、しかしながら、存在することができます。
 もし、これらのシグネイチャのどれかを持つ関数定義が上の対応した要請を満たすのであれば、それは CLI ディスポーズ・パターンを実装するために使われ、そして、それらの関数を呼ぶ C++/CLI プログラムは不正となります。[注意:もし、その様な場合の検証結果が代わりにデストラクタを呼ぶことをプログラマに促すのであれば、プログラマにとって大変有用でしょう。] もし、これらのシグネイチャのどれかを持つ関数定義が上の対応する要求を満たさない場合には、それは CLI ディスポーズ・パターンを実装するのに使われることなく、C++/CLI プログラムはその関数を直接呼ぶことが許されます。
 System::IDisposable インターフェイスは CLI ディスポーズ・パターンにおいてデストラクションの導入位置として使われます。しかしながら、C++/CLI はデストラクタとファイナライザを通したクリーン・アップの直接サポートを提供するので、System::IDispose インターフェイスは直接的に使われることは決してありません。C++/CLI プログラムはこのインターフェイスを使うべきではありません。
[例:
public ref class B {
protected:
    !B() {}
public:
    ~B() {}
};
public ref class D : B {
protected:
    !D() {}
public:
    ~D() {}
};
.class ... B ... implements [mscorlib]System.IDisposable {
  .method ... void  '!B'() ... { ... }
  .method ... void Dispose(bool marshal( unsigned int8) A_1) ... {
      ldarg.1
      brfalse.s    IL_000b
      ldarg.0
      call         instance void B::'~B'()
      br.s         IL_001b
    IL_000B:
      nop

    .try {
      ldarg.0
      call         instance void B::'!B'()
      leave.s      IL_001b
    }
    finally {
      ldarg.0
      call instance void [mscorlib]System.Object::Finalize()
      endfinally
    }
    IL_001b:
      ret
  }
  .method ... void Dispose() ... {
      ldarg.0
      ldc.i4.1
      callvirt     instance void B::Dispose(bool)
      ldarg.0
      call         void [mscorlib]System.GC::SuppressFinalize(object)
      ret
  }
  .method ... void Finalize() ... {
      ldarg.0
      ldc.i4.0
      callvirt     instance void B::Dispose(bool)
      ret
  }
  .method ... void  '~B'() ... { ... }
}

.class ... D extends B {
  .method ... void  '!D'() ... { ... }
  .method ... void Dispose(bool marshal( unsigned int8) A_1) ... {
      ldarg.1
      brfalse.s    IL_0015

    .try {
      ldarg.0
      call         instance void D::'~D'()
      leave.s      IL_0013
    }
    finally {
      ldarg.0
      ldc.i4.1
      call         instance void B::Dispose(bool)
      endfinally
    }
  IL_0013:
    br.s           IL_0026
  IL_0015:
    nop

    .try {
      ldarg.0
      call         instance void D::'!D'()
      leave.s      IL_0026
    }
    finally {
      ldarg.0
      ldc.i4.0
      call         instance void B::Dispose(bool)
      endfinally
    }
  IL_0026:
      ret
  }
  .method ... void  '~D'() ... { ... }
}


34.7.13.2 デストラクタ
 ユーザー定義、ないし、コンパイラ生成デストラクタを持つ ref クラスは System::IDisposable を実装しているものとしてマークされるべきです。
 ref クラスのインスタンスのデストラクタは常に System::IDisposable へのオブジェクトとしてダイナミックに型変換されることで始まります。もし、キャストに成功したら、Dispose() 関数がキャストの結果を通して呼び出されるべきです。もし、キャストに失敗したのであれば、デストラクタは何も行いません。[注意:結果として、デストラクタは任意の ref クラス、値クラス、インターフェイス・クラスのインスタンスに呼ばれることが可能となります。]
 コンパイラは System::IDisposable::Dispose 関数を通して以外にデストラクタを呼ぶコードを生成するべきではありません。
 値クラスはデストラクタを持てないのですが、もし、値クラスが非直接的に(System::IDisposable を実装する他のインターフェイスを実装する結果として)System::IDisposable を実装すれば、コンパイラはそのインターフェイスを実装する対応した Dispose() 関数を発行するべきです。しかしながら、その Dispose() 関数は何もしません。
 デストラクタを宣言するインターフェイス・クラスには、そのデストラクタには何のメソッドも発行されるべきではありません。しかしながら、そのインターフェイスは System::IDisposable を実装しているものとしてマークされるべきでしょう。

34.7.13.3 ファイナライザ
 クラスのファイナライザはもし、そして、もしユーザーがそのクラスにファイナライザを記述したのであれば、生成されるべきです。
 任意の ref クラス T でファイナライザを呼ぶことは、直接的に __identifier("!T") 関数(§34.7.13.9 )を呼ぶ結果となります。

34.7.13.4 ディスポーズ・パターンをサポートするために生成される関数
 CLI ディスポーズ・パターンは三つの基本関数を使用します:Dispose()、Finalize()、そして、Dispose(bool)。二つの副次的関数、__identifier("~T")() と __identifier("!T")() は Dispose(bool) から呼び出されます。五つ全ての関数の定義は、下で規定するように、コンパイラによって生成されます。

34.7.13.5 Dispose() 関数
 このメンバ関数はデストラクションを通してクリーン・アップするための開始点です。
 この関数は以下のシナリオで任意の ref クラス T についてのみ発行されるべきです。
  • Dispose(bool) 関数がクラス T で導入されている(以下の Case#2 と Case#3)か、
  • Case#1 が使用され、System::IDisposable を実装する public virtual Dispose() がすでに導入された Case#1 を使う基底クラスがない。
 この関数は発行されません。
  • もし、ディスポーズ・パターンがすでに存在しており、
  • ディスポーズ・パターンの一部である Dispose() もまた存在しており、
  • そのクラスが明示的に System::IDisposable を実装している。
 この関数は以下のように、T の定義の内側では C++/CLI で記述されているかのように発行されるべきでしょう。
public:
    virtual void Dispose() sealed {
        this->Dispose(true);
        System::GC::SuppressFinalize(this);
    }
 コンパイラによって発行される任意の Dispose() 関数の親クラスは System::IDisposable を実装しているものとしてマークされるべきです。
 もし、T の基底クラスが Dispose() メソッドを持っており、System::IDisposable を実装していなければ、その基底クラス関数は T のために発行されるものによって隠されるべきです。Dispose() 関数は、その関数が System::IDisposable を実装した Dispose() の基底クラスの実装で上書きすることができなければ、メタデータ中で newslot でマークされるべきです。

34.7.13.6 Finalize() 関数
 この関数はファイナライゼーションを通してクリーン・アップするための開始点です。
 この関数もし次のような基準にあった場合にのみ任意の ref クラス T について発行されます。
  • コンパイラはクラス T に関数 __identifier("!T") を生成するでしょう。そして、
  • クラス T はディスポーズ・パターン(Case#2 と Case#3)を導入するか、もし、クラス T がディスポーズ・パターン(下のCase#1)を拡張するなら、Finalize() 関数をすでに導入しているディスポーズ・パターンの基底クラスはありません。

 この関数は以下のように、T の定義の内側では C++/CLIで記述されているかのように発行されるべきでしょう。
protected:
    virtual void Finalize() override {
        this->Dispose(false);
    }
 Finalize() 関数はメタデータ中で決して newslot でマークされるべきではありません。

34.7.13.7 Dispose(bool) 関数
 任意の ref クラスによって、関数 __identifier("~T") と __identifier("!T") の双方が、双方共にこのクラスに生成されているか、コンパイラが非トリビアル・デストラクタをそのクラス・メンバでクリーンにするために生成される必要がある場合にのみ、この関数は生成されます。
 この関数は、以下に示す Case#1、Case#2、Case#3 の三つの可能な形態を持ちます。(各々の場合、T の基底クラスは Base です。また、クラス T はデストラクタとファイナライザの双方を持っているものと仮定しています。もし、一つないし他のそれらの関数が省略された場合、対応する呼び出し __identifier("~T") や __identifier("!T") は省略されるべきでしょう。)これらの場合で各々どの場合が次のような決定木が選択されます。

Case #1: ディスポーズ・パターンの一部である Dispose(bool) が存在する場合に拡張されたディスポーズ・パターン
protected:
virtual void Dispose(bool calledFromDispose) override {
    if (calledFromDispose) {
        try {
            this->__identifier("~T")();
        } finally {
            try {
                this->Base::Dispose(true);
            } finally {
                // ここでメンバの消去処理を行う
            }
        }
    } else {
        try {
            this->__identifier("!T")();
        } finally {
             this->Base::Dispose(false);
        }
    }
}

Case #2: System::IDisposable() を実装した public な Dispose() がない場合のディスポーズ・パターンの紹介
protected:
virtual void Dispose(bool calledFromDispose) {
    if (calledFromDispose) {
        this->__identifier("~T")();
    } else {
        try {
            try {
                this->__identifier("!T")();
            } finally {
                // ここでメンバの消去処理を行う
            }
        } finally {
            this->Base::Finalize();
        }
    }
}

Case #3: 呼び出し可能な Dispose() が存在する場合のディスポーズ・パターンの紹介
protected:
virtual void Dispose(bool calledFromDispose) {
    if (calledFromDispose) {
        try {
            this->__identifier("~T")();
        } finally {
            try {
                this->Base::Dispose();
            } finally {
                // ここでメンバの消去処理を行う
            }
        }
    } else {
        try {
            this->__identifier("!T")();
        } finally {
            this->Base::Finalize();
        }
    }
}
訳注
P226 図省略

34.7.13.8 __identifier("~T")() 関数
 この関数は任意の ref クラス T について、そのクラスがユーザー定義デストラクタを持っている場合にのみ発行されるべきです。この関数の本文はユーザー定義デストラクタの本文と厳密に対応するべきです。コンパイラはこの関数中に他のどんなコードも生成するべきではありません。
 この関数は T の定義の中で、次のように C++/CLI で記述されているかのように発行されるべきです。
private:
    void __identifier("~T")() {
        // ユーザー定義デストラクタの本文をここに記述
    }

34.7.13.9 __identifier("!T")() 関数
 この関数は任意の ref クラス T について、そのクラスがユーザー定義ファイナライザを持っている場合にのみ発行されるべきです。この関数の本文はユーザー定義ファイナライザの本文と厳密に対応するべきです。コンパイラはこの関数中に他のどんなコードも生成するべきではありません。
 この関数は T の定義の中で、次のように C++/CLI で記述されているかのように発行されるべきです。
private:
    void __identifier("!T")() {
        // ユーザー定義ファイナライザ本文をここに記述
    }

文責:翻訳 => ヽ(゚∀。)ノうぇね