いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

.NET クラスライブラリ探訪-053 (System.Runtime.CompilerServices.RuntimeHelpers (2))(PrepareConstrainedRegions, ReliabilityContract, CER, Consistency, 信頼性のコントラクト, 事前コンパイル)


今回は、System.Runtime.CompilerServices.RuntimeHelpersクラスのPrepareConstrainedRegionsメソッドについて
ちょこっとメモメモ。前回の続きとなっています。

  • .NET クラスライブラリ探訪-052 (System.Runtime.CompilerServices.RuntimeHelpers)(オブジェクト識別ID取得, 特殊なGetHashCode, RuntimeHelpers.PrepareMethod, RuntimeHelpers.GetHashCode)


RuntimeHelpersクラスには、PrepareConstrainedRegionsメソッドというのがあります。
このメソッドは、すごく特殊なメソッドで実際に利用することは稀だと思うのですが
面白い動きをするのでメモしておきます。


MSDNにて、メソッドの説明文を見ると「コード本体を制約された実行領域 (CER) として指定します。」とあります。


CERというのは、制約された実行領域という意味で、実行されるコードの信頼性を高めるための機構です。
CER内で実行されるコードは、信頼性を高めるために制限を受けます。


詳細については、MSDNの以下のトピックに詳しく記載されています。


で、RuntimeHelpers.PrepareConstrainedRegionsメソッドですが
このメソッドがtryブロックの直前で実行されていると

対応するcatch, finallyブロックがCERとしてマークされます。

マークされると、CER内で呼び出されるコードに制約がかかり、さらに、コードが呼び出せる事を保証するために事前コンパイル(メソッドが準備)されます。


ただし、決まり事があって、この場合対象となるのは

System.Runtime.ConstraintedExecution.ReliabilityContractAttribute

が付与されたメソッドやクラスのみとなります。ReliabilityContractAttribute属性は
CERを扱う際にでてくる属性で、コードの信頼性を保証するための属性です。


この属性は、引数にConsistency列挙体とCer列挙体を受け取ります。
CER内にて、事前コンパイルされるのは、Consistencyが

  • WillNotCorruptState
  • MayCorruptInstance

と指定されている場合のみです。


それぞれの列挙体はいろいろな値をもっています。
詳細については、以下をご参照ください。


どんな動作をするのかは、サンプルを見てもらった方が分かりやすいと思います。
以下サンプルです。まず、CERを利用しない版。

  #region RuntimeHelpersSamples-02
  /// <summary>
  /// RuntimeHelpersクラスのサンプルです。
  /// </summary>
  public class RuntimeHelpersSamples02 : IExecutable
  {
    // サンプルクラス
    static class SampleClass
    {
      static SampleClass()
      {
        Console.WriteLine("SampleClass static ctor()");
      }

      internal static void Print()
      {
        Console.WriteLine("SampleClass.Print()");
      }
    }
    
    public void Execute()
    {
      try
      {
        Calc();
      }
      finally
      {
        SampleClass.Print();
      }
    }
    
    void Calc()
    {
      for (int i = 0; i < 10; i++)
      {
        Console.Write("{0} ", (i + 1));
      }
      
      Console.WriteLine("");
    }
  }
  #endregion


実行結果は、以下のようになります。

  1 2 3 4 5 6 7 8 9 10 
  SampleClass static ctor()
  SampleClass.Print()

当たり前ですが、try内の処理が行われた後、finallyブロックに入り
静的コンストラクタが実行され、該当メソッドが実行されています。


このサンプルをCERで実行されるようにしたものが以下のサンプルとなります。
SampleClass.Printメソッドはfinally内で実行されるので、信頼性のコンストラクトを
付与しています。

  #region RuntimeHelpersSamples-02
  /// <summary>
  /// RuntimeHelpersクラスのサンプルです。
  /// </summary>
  public class RuntimeHelpersSamples02 : IExecutable
  {
    // サンプルクラス
    static class SampleClass
    {
      static SampleClass()
      {
        Console.WriteLine("SampleClass static ctor()");
      }

      //
      // このメソッドに対して、CER内で利用できるよう信頼性のコントラクトを付与.
      // ReliabilityContractAttributeおよびConsistencyやCerは
      // System.Runtime.ConstrainedExecution名前空間に存在する.
      //
      // RuntimeHelpers.PrepareConstrainedRegionsメソッドにて
      // 実行できるのは、Consistency.WillNotCorruptStateおよびMayCorruptInstanceの場合のみ.
      //
      // 尚、この属性はメソッドだけではなく、クラスやインターフェースにも付与できる。
      // その場合、クラス全体に対して信頼性のコントラクトを付与したことになる。
      //
      [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
      internal static void Print()
      {
        Console.WriteLine("SampleClass.Print()");
      }
    }
    
    public void Execute()
    {
      //
      // RuntimeHelpers.PrepareConstrainedRegionsを呼び出すと、コンパイラは
      // そのメソッド内のcatch, finallyブロックをCER(制約された実行領域)としてマークする。
      //
      // CERとしてマークされた領域から、コードを呼び出す場合、そのコードには信頼性のコントラクトが必要となる。
      // コードに対して、信頼性のコントラクトを付与するには、以下の属性を利用する。
      //  [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
      //
      // CERでマークされた領域にて、コードに信頼性のコントラクトが付与されている場合
      // try内の本処理が実行される前に、catch, finallyブロックのコードを事前コンパイルされる。
      //
      // なので、例えばfinallyブロック内にて静的コンストラクタを持つクラスのメソッドを呼びだしていたり
      // すると、try内の本処理よりも先にfinallyブロック内の静的コンストラクタが呼ばれる事になる。
      // (事前コンパイルが行われると、アセンブリのロード、静的コンストラクタの実行などが発生するため)
      //
      RuntimeHelpers.PrepareConstrainedRegions();
      
      try
      {
        // 事前にRuntimeHelpers.PrepareConstrainedRegions()を呼び出している場合
        // 以下のメソッドが呼び出される前に、catch, finallyブロックが事前コンパイルされる.
        Calc();
      }
      finally
      {
        SampleClass.Print();
      }
    }
    
    void Calc()
    {
      for (int i = 0; i < 10; i++)
      {
        Console.Write("{0} ", (i + 1));
      }
      
      Console.WriteLine("");
    }
  }
  #endregion


tryの直前で、RuntimeHelpers.PrepareConstrainedRegions()を呼び出し
CERとしてマークすることを指定します。CERの状態でfinallyブロック内の処理は実行されます。
つまり事前コンパイルされます。


以下、実行結果です。

  SampleClass static ctor()
  1 2 3 4 5 6 7 8 9 10 
  SampleClass.Print()


tryブロック内のコードが実行される前に、事前コンパイル(メソッドが準備される)ので
先に静的コンストラクタが実行されていることが分かります。


以下、参考資料です。


================================
過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。