C# コーディングスタイル

Last Updated 2011/09/21


コーディングのスタイルは十人十色、いろいろなスタイルがあるものです。すぐれて好みの問題ですから、これと決まったものはありません。自分だけが見るコードならどんなものでもいいでしょう。しかし、ソースコードを公開する場合は自分の好みをほかの人に押し付けるのは問題です。そこで、あらためてコーディングスタイルについて考えてみました。


Visual Studio のヘルプの中に以下の項目があります。

.NET の開発
. .Net Framework SDK ドキュメント
... 一般的なリファレンス
..... クラスライブラリ開発のデザインガイドライン

これは、.NET 対応のコンポーネント(クラスライブラリ)を開発する時のコーディングスタイルを規定するものです。コーディングスタイルのすべてについて触れているわけではありませんが、1 つのガイドラインではあります。

命名規則

変数名の付け方については、Microsoft が発行する文献を含めて、いろいろな資料で述べられています。過去の習慣、慣れ、好みがもっともあらわれやすい分野ですので、決定的なものはないようです。Microsoft が発行する文献でさえ、矛盾することを書いていたり、Microsoft がサンプルコードとして公開しているものが Microsoft がすすめるガイドラインに沿っていなかったりするので、ほとんど支離滅裂の感があります。

しかし、どんなスタイルが好みであろうと、絶対的な基準が 1 つあります。それは見ただけですぐにこれだと気付くようにしておくべきということです。その典型的な例がフィールド(メンバ変数)の名前の付け方です。

Pascal 形式と Camel 形式

伝統的な C/C++ プログラマは変数名や関数名にすべて小文字を使うことが多いので、C# でもそうする人がいます。インターネットのドメイン名がすべて小文字を使うのもその名残なのでしょう。C/C++ は識別子に対して大文字と小文字を区別します。もし、大文字と小文字を混在する名前をつけているとチョットしたタイプミスでバグを抱えてしまう可能性があります。そこで、小文字だけに統一しておけば起こりうる問題の 1 つを除去することができます。これが小文字だけを使うことにした理由だろうと想像します。

Note 昔の C/C++ コンパイラは事前のエラーチェックが不十分ですから、コンパイルを開始するまでエラーがあることを報告しません。しかし、現在のコンパイラはすぐに報告してきますから大文字と小文字を混在させても大きな問題にはなりません。

さて、小文字だけの名前はひどく読みにくいことはすべての人に理解してもらえると思います。そこで、C# では Pascal 形式と Camel 形式との 2 つの形式を使い分ける方法が推奨されています。

Pascal 形式は名前を構成する単語の最初の文字を大文字にするものです。たとえば、BackColor や FormBorderStyle などの類ですね。backcolor や formborderstyle よりも格段に読みやすくなることは見てのとおりです。この形式は、クラス名、プロパティ名、メソッド名など一般的な名前の付け方として採用されています。

一方、Camel 形式は名前を構成する単語の最初の単語の先頭の文字は小文字、それ以後の単語の先頭の文字を大文字にするものです。たとえば、backColor や formBorderStyle のようにします。これはローカル変数やメソッドの引数の変数名として使うことが多いですね。ちなみに、Camel はラクダです。名前の形がコブを持つラクダのようになることに由来するのでしょう。

フィールド(メンバ変数)名

C# は Object Pascal の流れをくむので、いろいろな面でその影響を受けています。たとえば、クラスのメンバ変数のことをフィールドと呼ぶといった具合です。Visual C++ プログラマはメンバ変数に "m_" を付ける習慣がありますが、私はフィールドをあらわす "F" を付けることをすすめます。というのは、"m_" の 2 文字よりコンパクトになることと、C# ではフィールドと呼ぶのにメンバ変数ではおかしいだろうと思うからです。

私はかつて C++ Builder を使っていたこともあって、"F" を付けることに若干の抵抗感がありましたが、現在では "F" を付けることにしています。つまり、慣れの問題に過ぎません。それなら、コンパクトなほうを選択すべきだと思いますね。

いずれにしろ、フィールド(メンバ変数)とローカル変数とを見ただけで区別できることが絶対的に重要です。

なお、フィールドの宣言(プロパティの宣言を含む)をコード内のアチコチにばらまく人がいますが、クラス宣言部の上部に位置すべきではないかと思います。ローカル変数についても同様に、メソッドの実装部の上部に集めるべきです。

定数名

伝統的な Windows SDK では定数名をすべて大文字だけで構成しています。たとえば、Windows メッセージの WM_KEYDOWN です。構造体も大文字だけを使いますが、これは視認性という意味で分かりやすい方法だと思います。C# でも採用すべきでしょう。ただし、構造体のほうは .Net Framework の名前付け規則にならって Pascal 形式にすべきかもしれません。

名前空間名

名前空間に対する名前の付け方に対して、Microsoft は明確な指針を示しています。

  companyName.productName.className (会社名.製品名.クラス名)

クラス名にはたいていの人が似たような名前を付けるものです。それを区別するには名前空間名が重要な役割を果たします。私の場合は、以下のようにしています。

  emanual.Utility.FileOperation

型名

コードの中で型名を指定するとき、CLR の名前を使う人がいます。たとえば、typeof(System.Int32) のようにです。C# では C# の定義済み名前を使うべきだと思います。先の例では、typefo(int) のほうがいいでしょう。そのほか、Object は object、String は string のほうをすすめます。

コントロールのオブジェクト名

私は Windows プログラミングを Visual Basic から始めた影響もあって、コントロールのオブジェクト名には 2 〜 3 文字のプリフィックスを付けることにしています。たとえは、フォームは frmAbout、ボタンコントロールは btnFolder、リストボックスコントロールは lstFile といった具合です。

C# のコードでもこうしている人が意外に多いことには勇気付けられますが、「見ただけでそれが何であるかが分かることと、できるだけコンパクトに」の普遍的な基準にしたがえば、Visual Basic スタイルはすぐれた方法だと思います。

クラス名

Visual C++ ではクラス名の先頭に、Class をあらわす "C" を付けることが一般的です。Microsoft はこれを付けないようにいっていますが、「見ただけで分かる」の精神からすると付けたほうがいいように思います。しかし、ここは妥協して、クラス名には "C" を付けないで、クラスを含む .cs ファイルのほうに "C" または "cls" を付けたらどうかと考えます。たとえば、FileOperation クラスを格納するファイルの名前に CFileOperation.cs とするといった具合です。

インデント

インデント量は Visual Studio の [ツール]-[オプション] で設定する「タブ」のサイズおよびインデントのサイズで決まります。どれぐらいがいいかはコードの見易さによると思いますが、私は 2 文字にしています。ときどき、1 文字のコードを見ることがありますが、これはどうみても見づらいですね。インデントしていることが分からなければインデントの意味がありません。3 文字もいいですね。しかし、4 文字は必要ないように思います。

ところで、今でも 8 文字にしている人がいるのには驚きを禁じえません。昔、といっても MS-DOS 時代のことですが、そのころまでのコードエディタは 1 行あたり 80 バイトまでしか表示できませんでした。また、タブは 8 文字と固定でした。そういう事情があったので、コードの 1 行の長さを短くして、縦方向に並べる習慣がありました。たとえば、こんな感じです。

private void SaveCursorToFile(
    Cursor cursor, 
    string fileName)
{
  TypeConverter converter = 
      TypeDescriptor.GetConverter(typeof(Cursor));
  byte[] byteArray = converter.ConvertTo(
      cursor, 
      typeof(byte[])) as byte[];

  if (byteArray == null)
    return;

  System.IO.FileStream fs = new System.IO.FileStream(
      fileName, 
      System.IO.FileMode.Create);

  fs.Write(
      byteArray, 
      0, 
      byteArray.Length);

  fs.Flush();
  fs.Close();
}

つまり、この時代はこうせざるを得なかったのです。ひるがえって、現在の環境おいてタブの幅として、8 文字に設定する理由はあるのでしょうか。いまでも 8 文字にしている人は MS-DOS 時代の古いコードを見て、こうしなければならないと思い込んでいると考えざるを得ません。ぜひ、再考してほしいところです。

using 文の位置

using 文を名前空間の中に書く人がいますが、これはどういう理由なのでしょうか。プロジェクトにクラスなどを追加すると、そのテンプレ−トコードでは名前空間の外側にありますよね。たとえば、Windows フォームを追加すると、そのテンプレートコードは以下のようなものです。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace Test
{
  public partial class Form2 : Form
  {
    public Form2()
    {
      InitializeComponent();
    }

    private void Form2_Load(object sender, EventArgs e)
    {

    }
  } // end of Form2
} // end of Test

これを次のようにする人がいます。

namespace Test
{
  using System;
  using System.Collections.Generic;
  using System.ComponentModel;
  using System.Data;
  using System.Drawing;
  using System.Text;
  using System.Windows.Forms;

  public partial class Form2 : Form
  {
    public Form2()
    {
      InitializeComponent();
    }

    private void Form2_Load(object sender, EventArgs e)
    {

    }
  } // end of Form2
} // end of Test

ワザワザ using 文の位置を移動するメリットとは何なんでしょうね。ぜひ聞いてみたいものです。意味のない(少なくとも、私はそう思います)ことをすべきではありません。何かの勘違いだろうと思います。

その後、何でこんなコードを書く人がいるのだろうといぶかっていたところ、原因が分かりました。Microsoft のサイトで公開しているサンプルコードの中にこういうコードを書く人物がいます。しかも、それは Visual Studio SDK のサンプルコード、つまり、Microsoft の公式のコードなのです。これを見れば、まねをする人が出てもおかしくはないですね。なにせ、Microsoft が推奨するコードなのですから。罪な話ではあります。

Note この記事を書いたあとに、こうせざるを得ないケースがあることに気付きました。名前空間名が重複する場合です。しかし、非常に特殊なケースですから、一般論としてはわざわざコードを移動する必要はないと考えます。

#region 文

コードをアウトライン表示する機能は便利ですが、すべてのメソッドに #region を設定するのはやりすぎだと思います。2 度とみることのないコード(たとえば、Form1.Designer.cs など)ならいざ知らず、全体を見通せるほうがいいのではないかと思います。

私と同じ悩みを持っている人は、Visual Studio アドインのページで #region 文を削除するアドインを公開していますので、利用してください。ソースコードも付いていますので、自分好みの動作に変更することも可能です。

中カッコ "{ }"

中カッコの使い方はコードの見た目のスタイルを決定づける重要な要素ですが、これについては Microsoft と Borland との意見は一致しています。両者とも以下のような書式をすすめています。

  if (condition)
  {
    ...
  }

.Net Framework SDK 内にあるサンプルコードやテンプレートもそうなっていますが、それをワザワズ変える人がいます。もちろん、好みの問題なので、自分で使うコードをどうしようと勝手なのですが、コードを公開する場合は標準的なスタイルにすべきではないかと思います。

この問題については、簡単に解決する手段があります。詳しくは、「ソースコードの整形」を参照してください。

this は必要か

以下のようなコードがあるとして、GetNumber メソッドを呼び出す側のコードに this を付けるべきかどうかを考えてみましょう。

private void button1_Click(object sender, System.EventArgs e)
{
  int index = GetNumber();

  あるいは

  int index = this.GetNumber();
}

private void GetNumber()
{
  int n = 0;
  return n;
}

私は付けることにしています。というのは、それが現在のクラスに属するメソッドであることがすぐに分かるからです。一方、フィールドには使いません。フィールドには "F" を付けるので、それがフィールドであることは自明だからです。わずかな手間ですが、見ただけでクラス内で定義したメソッド(C/C++ 的な表現ではメンバー関数ですね)であることが分かるメリットは大きいと考えます。

オブジェクト解放後、null を設定すべきか

オブジェクトが不要になった時点で、オブジェクトに null を設定すべきかどうかを考えてみましょう。まず、以下のコードを見てください。

private void button1_Click(object sender, EventArgs e)
{
  Font font = null;

  try
  {
    font = new Font("Arial", 16.0f);
    font.Dispose();
    font.Dispose();  ← 2 度 Dispose メソッドを呼び出してもエラーにはならない
  }
  catch (Exception ex)
  {
    MessageBox.Show(ex.Message);
  }

  if (font == null)
    Text = "null";
  else
    Text = font.ToString();  ← こちらが生きる
}

コード中に書き込みましたが、Dispose メソッドを 2 度呼び出してもエラーにはなりません。また、Dispose 後も font は null ではありません。font.ToString() は font が有効な時の値を出力します。

もちろん、font はローカル変数なので、別のメソッドから参照することはできませんし、button1_Click メソッドを抜け出た時点で無効になるはずです。

Note 「無効になる」と書きましたが、もし無効にならなければ .Net Framework のバグですね。ここは信じるほかないと思います。

次に、font をフィールドとして宣言した場合について見てみましょう。button1 をクリックしたあと、button2 をクリックしてください。

private Font FFont = null;

private void button1_Click(object sender, EventArgs e)
{
  try
  {
    FFont = new Font("Arial", 16.0f);
    FFont.Dispose();
  }
  catch (Exception ex)
  {
    MessageBox.Show(ex.Message);
  }
}

private void button2_Click(object sender, EventArgs e)
{
  Text = FFont.ToString(); ← これは FFont が有効であるかのような値を表示する
  Font = FFont;            ← こちらは失敗する
}

結果は書き込んだコメントを見てのとおりです。

クラスのフィールド(メンバ変数)として宣言したオブジェクトはクラスのインスタンス自体が不要になって時点で Dispose します。それ以外の時点で Dispose する場合は null を設定しておくほうが安全です。つまり、使わないはずのオブジェクトを使える状態にしておくのは危険です。

sealed 属性

.Net Framework のクラスの中に sealed 属性を付けたクラスがあります。たとえば、OpenFileDialog クラスです。sealed 属性を持つクラスはそれから派生するクラスの作成を抑制するものですが、OpenFileDialog クラスから派生するクラスを作ってはいけない理由があるのでしょうか。

Microsoft はコモンダイアログボックスをカスタマイズしてもらいたくないのかと思っていたら、コモンダイアログボックスをカスタマイズするテクニックを公開しています。といっても、コードを 1 から作っているのですが。

sealed 属性を付けるほうがよいケースを考えてみても、これはというものを思いつきません。WEB サイトでソースコードを公開するクラスの中にも sealed 属性をつけている人がいますが、ぜひその理由を聞いてみたいものです。もし、Microsoft の例にならっているだけなら勘違いだからやめましょう。

/// タイプのコメントを使うべきか

ソースコードにはコードの意味を説明するためのコメントを書き込みます。自作のアプリケーションであっても時間がたつとそのうち忘れてしまうので、コメントはいくら多くても多すぎるということはありません。ましてや、他人が読む機会のあるコードであればなおさらです。

さて、C# にはクラス、プロパティ、メソッドなどの説明文として、/// タイプのコメントを使うと、XML の形式のドキュメントを作成する機能があります。プログラムコードのドキュメント化という意味では 1 つの解決策ではあります。しかし、それを生かしている人はどれぐらいいるのでしょうか。というのは、キッチリとした /// タイプのコメントを見たことはありません。

/// タイプのコメントの意義は認めつつも私は使いたいとは思いません。冗長すぎて、煩わしさしか感じないからです。それよりも、コード内に詳しいコメントを書く手間をおしまないほうがよっぽど重要だと思います。

Visual Studio アドインのページで "///" タイプのコメント行をバッサリと削除するアドインを公開していますので、興味のある人は試してください。

ソースコードの整形

ソースコードの見易さは好みの問題があって、一概には言えません。Microsoft が推奨するコーディングガイドは中カッコの使い方などには触れていないため、コードの見易さはいまだ未解決の問題です。ソースコードの見易さを決定付ける主な要素をリストアップしてみましょう。

気付きにくいのですが、Visual Studio 2008/2010 にはソースコードを整形する機能があります。メニュー [編集]-[詳細]-[ドキュメントのフォーマット] です。とりあえず、その機能を見てもらいましょう。以下のようなコードがあるとして、このメニュー項目を選択してください。

private void DoExecute(string text)
{
  if(this.GetStringByteCount(text)<=FLimit)
    FStringList.Add(text);
  else
  { int index;
    while(true)
    { index = this.GetLimitColumnIndex(text);
      // 折り返し位置が行頭禁止文字であるかぎり、インデックスを戻す
      while (Array.IndexOf(NOT_HEAD_CHARS,text[index+1])&gt;=0)
        --index;
      // 行頭禁止文字ではない文字の 1 つ手前の文字が行末禁止文字の場合はさらにインデックスを戻す
      while (Array.IndexOf(NOT_TAIL_CHARS,text[index])&gt;=0)
        --index;
      FStringList.Add(text.Substring(0,index+1));
      text=text.Substring(index+1,text.Length-index-1);
      if (this.GetStringByteCount(text)<=FLimit)
      { FStringList.Add(text);
        break;
      }
    }
  }
}

以下は整形後のコードです。

private void DoExecute(string text)
{
  if (this.GetStringByteCount(text) <= FLimit)
    FStringList.Add(text);
  else
  {
    int index;
    while (true)
    {
      index = this.GetLimitColumnIndex(text);
      // 折り返し位置が行頭禁止文字であるかぎり、インデックスを戻す
      while (Array.IndexOf(NOT_HEAD_CHARS, text[index + 1]) <= 0)
        --index;
      // 行頭禁止文字ではない文字の 1 つ手前の文字が行末禁止文字の場合はさらにインデックスを戻す
      while (Array.IndexOf(NOT_TAIL_CHARS, text[index]) <= 0)
        --index;
      FStringList.Add(text.Substring(0, index + 1));
      text = text.Substring(index + 1, text.Length - index - 1);
      if (this.GetStringByteCount(text) <= FLimit)
      {
        FStringList.Add(text);
        break;
      }
    }
  }
}

おおむね、私の好みに合致していますので、私自身は満足ですが、これと逆の操作をしたい人はどうすればいいのでしょうか。実は、Visual Studio の機能をカスタマイズする方法がああります。

Visual Studio のソースコードの整形機能のカスタマイズ

[ツール]-[オプション]-[テキストエディタ]-[C#]-[書式指定] の各項目を設定します。たとえば、中カッコ "{ }" の位置は [改行] の [中かっこの改行オプション] の「新しい行に型の始めかっこを配置する」などのチェックをはずすと開くカッコは改行しない位置に配置されます。

私はデフォルトの状態で満足なので、個々の項目の動作方法をテストしていませんが、[書式指定] の各項目の条件を変えてテストすれば自身の好みに近い状態になるかもしれません。

なお、各項目の説明文をクリックしてフォーカスを与えると、その下側にコードのプレビューが表示されます。たとえば、[行間] の「メソッド名と始めかっこの間にスペースを挿入する」にフォーカスを与えると、そのプレビューは次のようになります。

  int Method()
  {
    return 3;
  }

この項目にチェックを入れると、以下のようになります。

  int Method () ← Method と ( との間にスペースを挿入する
  {
    return 3;
  }

演算子

C# の演算子、予約済みキーワード、ステートメントの使い方を広い意味のコーディングスタイルと考え、私なりの意見を述べたいと思います。なお、私は格別 C# の内部情報に詳しいわけではないことをお断りしておきます。

as機 能

型を変換する

構文例

  string s = someObject as string;

  if (s != null) ← 型キャストできなかったとき null を返す
  {
    ....
  }

解 説

型を変換する場合、C# では直接キャストする方法と as 演算子を使う方法とがあります。型を変更できるのは互換性のある参照型の間でのみで、直接型キャストしようとしてこのルールにしたがっていない場合は例外が発生します。一方、as 演算子を使うと型キャストに失敗した時 null を返すだけで例外は発生しません。ただし、ユーザー定義型に対しては使えません。

私の意見

型を変換可能かどうかはコンパイル時に分かることです。実行時に型変換に失敗することがあるということであれば、as 演算子を使って null 以外の値が戻るかどうかをチェックする意味はありますが、コンパイルしたときにすでに分かっていることをあらためてチェックする必要はないと考えます。要するに、直接型キャストでも as 演算子でもどちらを使ってもよいということになるのではないでしょうか。

ところで、C# は巧みにポインタという言葉を避けていますが、内部的にはポインタを使っていることは間違いありません。ポインタは 4 バイトの数値にすぎませんから基本的には何にでもキャスト可能です。しかし、そうはならないのはコンパイラがその内容を知っているからです。

コンパイラが知っているといいましたが、すべてを分かっているわけではありません。間違ったキャストをして実行時に怒られた経験があると思います。これを as 演算子を使ったからといって防ぐことはできません。

 

checked と unchecked機 能

整数型の算術演算に対してオーバーフローのチェックを有効・無効にする

構文例

演算子として使う場合

  n3 = checked((short)(n1 + n2));

ステートメントとして使う場合

  checked
  {
    n3 = (short)(n1 + n2);
  }

解 説

オーバーフローまたはアンダーフローに対するコンパイル時の動作は、[プロジェクト]-[オプション] の [ビルド] のページの中にある [詳細設定] を選択すると表示されるダイアログボックスの中の [演算のオーバーフローおよびアンダーフローのチェック] の設定に依存します。これにチェックを入れておくと、実行時のパフォーマンスの低下はまぬがれませんので、必要な時に checked を使うことをすすめます。一方、チェックしたくないときは unchecked を使います。

構文例を見てのとおり、checked および unchecked は演算子としてもステートメントとしても使うことができます。

コンパイルの設定はデフォルトの状態(オーバーフローをチェックしない)で以下のコードを実行してみましょう。

private void button1_Click(object sender, EventArgs e)
{
  short n1 = 32767;
  short n2 = 32767;
  short n3 = 0;

  try
  {
    n3 = (short)(n1 + n2);
  }
  catch (OverflowException ex)
  {
    MessageBox.Show(ex.Message);
  }

  Text = String.Format("{0} {1}", n3, n3.GetType());
}

例外は発生せず、以下の結果になります。n3 はオーバーフローしていますから正しい値にはなりません。

実行結果:-2 System.Int16

コンパイラの設定でオーバーフローのチェックを有効にすると、上記のコードは例外を発生します。次に、コンパイラの設定はデフォルトの状態(オーバーフローをチェックしない)で以下のコードを実行します。これは checked 演算子を使う例です。

private void button1_Click(object sender, EventArgs e)
{
  short n1 = 32767;
  short n2 = 32767;
  short n3 = 0;

  try
  {
    n3 = checked((short)(n1 + n2));
  }
  catch (OverflowException ex)
  {
    MessageBox.Show(ex.Message);
  }

  Text = String.Format("{0} {1}", n3, n3.GetType());
}

「算術演算の結果オーバーフローが発生しました。」例外が発生します。

以下は、checked ステートメントを使う例です。

private void button1_Click(object sender, EventArgs e)
{
  short n1 = 32767;
  short n2 = 32767;
  short n3 = 0;

  try
  {
    checked
    {
      n3 = (short)(n1 + n2);
    }
  }
  catch (OverflowException ex)
  {
    MessageBox.Show(ex.Message);
  }

  Text = String.Format("{0} {1}", n3, n3.GetType());
}

私の意見

オーバーフローまたはアンダーフローする可能性があるかどうかはアプリケーション開発者なら予想が付きます。そうならないようにデータを事前チェックしておくほうが重要です。

 

const と readonly機 能

変更できない変数として宣言する

構文例

		public class Age
    {
      public static readonly Color BACKCOLOR = Color.FromArgb(64, 128, 255);
      public const string Name = "Microsoft"; // Age.Name で参照することができる
      private const int THIS_YEAR = 2010;
      private readonly int FYear;
    
     public Age(int year)
     {
        FYear = year; // コンストラクタで初期化する
      }
    
      public void ChangeYear()
      {
        FYear = 1967; // コンパイル時にエラー
      }
    
      public int GetThisYear()
      {
        return THIS_YEAR;
      }
    }
		

解 説

const と readonly との機能はほぼ同じですが、使用可能な変数の種類と初期化の方法が異なります。

-constreadonly
使用場所フィールドおよびローカル変数フィールド
初期化の方法宣言時宣言時またはクラスのインスタンス化時

readonly はコンストラクタ内で初期化できるので、オブジェクト作成時に値を設定することができます。一方、const は宣言時に値を設定するだけです。

readonly フィールドを out または ref の引数として渡すことはできません。ただし、static なフィールドの場合は、static なコンストラクタ内でのみ out または ref の引数として渡すことができます。

複数の const 宣言する場合は、次のような構文が可能です。

  public const double x = 1.0, y = 2.0, z = 3.0;

const 定数は別の定数式で使うことができます。

  public const int c1 = 5;
  public const int c2 = c1 + 100;

const 定数に対して static は使えません。

 

fixed機  能

unsafe コンテキストでのみ使用可能で、移動可能な変数のガベージコレクタによる再配置を防止する

構文例

  Point point = new Point();

  fixed (int* p = &point.x)
  {
    *p = 1;
  }

解 説

マネージ変数は通常メモリ内を移動可能で、移動しても .Net Framework が追跡するので、見失うことはありません。

Note 移動可能とは、メモリ管理上の都合で現在の位置から別の位置にメモリの内容をコピーすることです。

しかし、unsafe コンテキスト、つまり、ポインタを直接扱うようなアンマネージコード内では、マネージ変数のメモリ領域が移動してしまうとポインタが保持する内容の意味がなくなってしまいます。そこで、一時的に移動を防止するために fixed ステートメントを使います。つまり、fixed ステートメントはマネージ変数へのポインタだけを指定できます。

unsafe モードでは stackalloc を使うことでスタックにメモリを割り当てることができます。スタックはガベージコレクタの対象にはならないので、固定する必要はありません。

上記の構文のほかに可能な構文を紹介します。

同じ型の場合は、複数のポインタを初期化できます。

  fixed (byte* ps = srcarray, pd = dstarray)
  {
    ...
  }

異なる型のポインタを初期化する場合は、fixed ステートメントを入れ子にします。

  fixed (int* p1 = &p.x)
  {
    fixed (double* p2 = &array[5])
    {
      // Do something with p1 and p2.
    }
  }

私の意見

最近はメモリ管理に関する情報が少なくて不安ではありますが、それだけ Windows の機能が充実してきたと考えていいと思います。Windows 3.1 時代を知っている人はともかくとして、Windows XP 以後に Windows プログラミングを始めた人はメモリ領域の移動とか、固定の意味を理解できないかもしれません。あまり深刻に考えなくてもいいとは思いますが、興味のある人は Windows 3.1 時代の本を見るのもいいかと思います。本屋にはありませんが、公共図書館にいけばあると思います。

 

lock機  能

ステートメントブロックをクリティカルセクションとして宣言する

構文例

  Object obj = new Object();

  lock (obj)
  {
    ....
  }

解 説

クリティカルセクションとは .Net Framework の Monitor クラスの機能と同じです。つまり、lock ステートメントブロックを抜け出るまで、指定のオブジェクトに別のスレッドからのアクセスを抑制します。

構文例のコードを Monitor オブジェクトを使って書き換えると以下のようになります。

  Object obj = new Object();

  Threading.Monitor.Enter(obj);
  try
  {
    ....
  }
  finally
  {
    Threading.Monitor.Exit(obj);
  }

なお、lock を public な型、たとえば、lock(this)、lock(typeof(MyType))、lock("MyString") のようなものに対して使うことはできません。

 

out と ref機  能

メソッドの引数が参照渡しであることを宣言する

構文例

public class Example
{
  private void MethodRef(ref int n)
  {
    n = 123; // 設定しなくてもよい
  }

  private void MethodOut(out int n)
  {
    n = 987; // 設定しなければならない
  }

  private void button1_Click(object sender, EventArgs e)
  {
    int refValue = 0; // 初期化必要
    MethodRef(ref refValue);

    int outValue; // 初期化不要
    MethodOut(out outValue);

    Text = refValue.ToString() + " " + outValue.ToString();
  }
}

解 説

out および ref はメソッド側のパラメータとメソッドの呼び出し側の引数との両方で指定し、メソッド側で変更したパラメータの内容はメソッドの呼び出し側に制御が戻った時に対応する変数の値として反映されます。ただし、メソッドの呼び出し側で引数を初期化しておかなければなりません。一方、out のほうは初期化しない状態でメソッドに渡すことができます。ただし、メソッドのほうで必ず値を設定しなければなりません。

out と ref とは、実行時の取り扱いは異なりますが、コンパイル時の取り扱いは同じです。したがって、オーバーロードする 2 つのメソッドのうち一方が ref 引数を受け取り、もう一方が out 引数を受け取るようにはできません。たとえば、次のように SampleMethod メソッドをオーバーロードすることはできません。

class Test
{
  public void SampleMethod(ref int n)
  {
  }

  public void SampleMethod(out int n)
  {
  }
}

ただし、一方のメソッドが ref 引数または out 引数を受け取り、もう一方のメソッドがどちらでもない引数も受け取る場合は、オーバーロード可能です。

class Test
{
  public void SampleMethod(int n)
  {
  }

  public void SampleMethod(out int n)
  {
  }
}

プロパティは変数ではないので、ref パラメータとして渡すことはできません。

メソッドに配列を渡す場合も手順は同じです。つまり、ref を使う場合は初期化済みの配列を渡し、out の場合はメソッドのほうで配列の値を設定しなければなりません。

 

stackalloc

機 能

スタック領域にメモリブロックを割り当てる

構文例

type * ptr = stackalloc type[expr];

引 数
type    アンマネージ型
ptr     ポインタ名
expr    整数型の式

解 説

unsafe モードでは stackalloc を使うことで、スタックにメモリを割り当てることができます。スタックは、ガベージコレクションの対象にならないので、fixed ステートメントを使って固定する必要はありません。

メモリブロックの有効期間は、このブロックを定義するメソッドの有効期間と同じです。メソッドから制御が戻る前にメモリを解放することはできません。つまり、ローカル変数を初期化するときにのみ有効です。

stackalloc を使用すると、CLR のバッファオーバーラン検出機能が自動的に有効になり、バッファオーバーランを検出すると、プロセスをできる限り迅速に終了させます。

私の意見

fixed の項でも触れましたが、メモリ管理用語としてのスタック "stack" とかヒープ "heap" という言葉の重要度が昔に比べると低くなりました。アンマネージコードを扱う人はそれなりの知識を持っているでしょうからここでは説明しませんが、マネージコードだけで可能な処理をアンマネージコードでやる人がいるのは残念ですね。

 

unsafe

機 能

unsafe コンテキストであることを宣言する

構文例

  unsafe
  {
    // unsafe なステートメントブロック
  }

  // メソッドの引数も unsafe コンテキストである
  unsafe static void FastCopy(byte* src, byte* dst, int count)
  {
    .... // unsafe コンテキスト
  }

解 説

unsafe コンテキストとはポインタを直接扱う状況をさします。unsafe なコードを含む場合は、[プロジェクト]-[オプション]-[ビルド] のページの [アンセーフコードの許可] にチェックを入れなければなりません。

unsafe はメソッド、ステートメントブロック、型に対して設定できます。

なお、C# の unsafe なコードは文字通りの危険性はなく、CLR が安全性を検査できないコードという意味でしかありません。

 

using

機 能

オブジェクトの有効期間を指定する

構文例1

  using (Font font1 = new Font("Arial", 10.0f))
  {
    ....
  }

構文例2

  Font font2 = new Font("Arial", 10.0f);

  using (font2)
  {
    ....
  }

構文例3

  using (Font font3 = new Font("Arial", 10.0f), font4 = new Font("Arial", 10.0f))
  {
    ....
  }

解 説

C# には、ガベージコレクションによって不要になったオブジェクトを自動的に解放する機能がありますが、オブジェクトが不要になった時点で占有していたメモリ領域をただちに開放するわけではありません。それをいつ実行するかはガベージコレクション次第です。しかし、ファイルハンドルやネットワーク接続などの制限のあるリソースは、通常、できるだけ速やかに解放する必要があります。using ステートメントを使用すると、オブジェクトをいつ解放するかを指定できます。

つまり、using ステートメントの末尾に到達するか、例外が発生してステートメントの末尾に到達する前に制御がステートメントのブロックを離れたときにオブジェクトを開放します。

なお、using ステートメントを適用できるオブジェクトは、IDisposable インターフェイスを実装していなければなりません。

using ステートメントでは、複数のオブジェクトを指定できますが、構文例3のように、using ステートメントの中でオブジェクトを作成しなければなりません。

私の意見

上記の構文例のような場合は、Font オブジェクトが不要になった時点で、Dispose メソッドを呼び出せばいいだけの話ではないでしょうか。using ステートメントで数行を書き、コードをインデントする手間より、Dispose メソッドを呼び出す 1 行のコードを書くほうが簡単だと思います。

ところで、using ステートメントを使うと、ただちにガベージコレクションされる保証があるのでしょうか。少なくとも、Dispose メソッドを呼び出しておけばメモリリークする可能性はありません。

私は public な Dispose メソッドを持つオブジェクトの場合は Dispose メソッドを呼び出すべきであるという意見です。クラスの作成者が Dispose メソッドを呼び出すことを期待していると考えるからです。一方、public な Dispose メソッドを持たないオブジェクトの場合は using ステートメントを使うほうが安全なのかもしれません。

ちなみに、グラフィックス関係のほとんどのクラスは public な Dispose メソッドを持ちます。しかも、これを呼び出さないと確実にメモリリークします。最悪の場合、パソコンがフリーズする可能性さえあります。

 

volatile

機 能

フィールド(メンバ変数)が複数のスレッドによって変更される可能性があることを宣言する

構文例

  public class MyClass
  {
    private volatile int FIndex;

    MyClass(int index)
    {
      FIndex = index;
    }
  }

解 説

volatile は、通常、lock ステートメントを使用しない場合に複数のスレッドからアクセスするフィールドに対して適用します。

volatile を適用できる型は次のとおり。

  • 参照型
  • ポインタ型(unsafe コンテキスト内)
  • sbyte、byte、short、ushort、int、uint、char、float、bool などの整数型
  • 整数ベースの型の列挙型
  • 参照型であることが判明しているジェネリック型パラメータ
  • IntPtr 型および UIntPtr 型

型はクラスまたは構造体のフィールドでなければなりません。また、ローカル変数に対して volatile を宣言することはできません。

 

yield

機 能

列挙子オブジェクトに値を挿入、または反復処理を終了する場合に、iterator ブロック内で使用する

構 文

  yield return expression;
  yield break;

引 数

expression   列挙子オブジェクトの値

解 説

IEnumerable インターフェースを実装するクラスを作成するには、GetEnumerator メソッドを実装しなければなりませんが、yield を使うと簡単に実装できます。具体的には使用例を参照してください。

yield は、iterator ブロック内でのみ使用可能で、次の場合には使えません。

  • unsafe ブロック内
  • 匿名メソッド内
  • yield return expression; の場合、catch ブロックを 1 つ以上含まむ try ブロック内

メソッドのパラメータとして、ref または out は使用できません。

使用例

private void button1_Click(object sender, EventArgs e)
{
  foreach (int n in this.Power(2, 8))
  {
    textBox1.Text += String.Format("{0} ", n);
  }
}

private IEnumerable Power(int number, int exponent)
{
  int counter = 0;
  int result = 1;

  while (counter++ < exponent)
  {
    if (result > 48) ← 列挙を中止する条件
      yield break;

    result *= number;
    yield return result;
  }
}

実行結果:2 4 8 16 32 64

−以上−