はじめに
私の C# コーディングについての覚え書きです。何かの参考になれば幸いです。
まずは、.NET Framework ドキュメントの Guidelines for Names などを読んで実践しましょう。その上で、さらに“分かり易い”コードを書くためには、どんな工夫があるでしょうか。
コメントの書き方
コメントの内容は、ピリオド(句点)で終わる「説明文」にします。なるべく体言止めにはしません。(体言止めでは説明不足になってしまう嫌いがあるように思います。)コメントは「そのコードについて、全く知識のない者が読んで理解できること」が基本です。
// ○○のために○○する。
...;
また、メソッド内部の一行コメントでは、“そのコメントが及ぶ範囲”に留意します。通常は直後のブロックについてのコメントです。意味の塊毎に空行を入れてブロックを分割します。
// このブロックのコメント。
...;
...;
...;
// 上記コメントは、このブロックには及ばない。
...;
...;
...;
必要であればブロックを括ります。
// このブロックのコメント。
{
...;
...;
...;
...;
...;
...;
}
変数宣言
var
C# 3.0 から、変数宣言のキーワード var が導入されました。これにより、変数宣言から“自明である型の記述”を省略できます。
int x = 123;
Unit y = new Unit();
var x = 123;
var y = new Unit();
var は LINQ を実現するために追加された構文の一つですが、LINQ の使用如何にかかわらず、全ての場面で使用できます。特に理由がない限り(後述)、変数は var で宣言・初期化しています。
なぜ var を使う?
※var を積極的に使用する理由についてのご質問があったので追記です。
LINQ に限らず var を積極的に使用する理由としては、次の 3 点が挙げられます。
- 型の名称を左辺・右辺で重複して記述するのは冗長なので避けたい。
- むしろ C# 3.0 以降では var による変数宣言が標準書式だと捉える。
- 変数の型を明示することには、あまり強い意義はない。
特に 3 番目の理由について、「変数の型は、その名称と使い方によって明示される。本来的に必要な宣言ではない」との発想があります。
従来の変数宣言には、「変数を宣言する」効果と同時に「変数の型を明示する」効果もあります。
しかし変数の宣言で重要なのは、その型よりも名称です。たとえば、Windows API を利用する C/C++ 言語では「変数名に型名のプリフィックスを付ける」という命名規則(システムハンガリアン)もありますが、これは C# では推奨されません。変数宣言において、その厳密な型名は、あまり重要ではないのです。(ポリモーフィズム。具象クラスの型名は、そのインスタンスを格納する変数にとっては関心の対象外であるべき。)
一方で「過剰な var の使用は良いスタイルではない(型を明示すべきだ)」との意見もあり得ます。変数宣言における型名の有無がコードの可読性に及ぼす影響については議論の余地がありそうです。
var と default(T)
参照型の変数を null で初期化したいときもあります。この場合、そのままでは var は使えません。null、だけでは型を推論しようがないためです。
var unit = null;
何らかの形で型を示す必要があります。こういうときは default(T) キーワードで型を明示します。(default は値型でも有効。)
var unit = default(Unit);
※あるいは var x = null as T; でも同様に宣言できますが、まあ default が無難でしょう。(出力される中間コードに違いはない。)
var と out
var を意図的に使わない場面もあります。メソッドの out 引数を受け取るための変数の宣言には var を使いません。
Unit unit;
if (!units.TryGetValue(key, out unit))
{
...
}
値の初期化(右辺)を書かないことで、その変数が“out 引数を受信するためにある”ことを示唆します。(通常は、このような変数宣言は推奨されないために区別が可能。)
default(T) キーワード
総称 [generics] 型を作る際に、default(T) キーワードを使うことがあります。型パラメーター T が参照型であれば null を、値型であれば初期値(通常は 0)を意味します。
このキーワードは通常の型に対しても使用できます。通常の場面でも積極的に使ってみましょう。
たとえば、メソッドのパラメーターの引数に null を渡す際には、ただ null とだけ書くよりは、default キーワードで型を記述する方が分かり易いように思います。
var message = String.Format(null, format, items.Count);
throw new ObjectDisposedException(null);
var message = String.Format(default(IFormatProvider), format, items.Count);
throw new ObjectDisposedException(default(string));
クラスメンバーの宣言順序
クラスのフィールドやメソッドの宣言の順序には、特に規定はありません。基本的には自由な順番で記述できます。ただ、やはり一貫性は必要です。何らかのルールを設けることになるでしょう。
私は以下の順序で宣言しています。
- Fields
- Static Fields
- Properties
- Static Properties
- Events
- Constructors
- Destructor
- Methods
- Static Methods
- Operators
- Event Handlers
- Classes
各項目は #region ディレクティブで括ります。項目内では、公開するもの(public, internal, protected)を上に、非公開のもの(private)を下に置きます。
流石に「アルファベット順に並べる」というようなことはする必要はないでしょう。統合開発環境(IDE)の補助もあります。しかし「フィールドやメソッドが何のルールもなく適当な順序で入り乱れている」というのでは、保守しにくいコードになってしまうように思います。
追記 StyleCop の場合
StyleCop のコーディングスタイルでは、以下の順序で定義されます。基本的な考え方は同じでしたが、Static を先に書いたり、Fields の次に Constructors を書くようになっています。
- Constants
- Fields
- Constructors
- Destructors
- Delegates
- Events
- Enums
- Interfaces
- Properties
- Indexers
- Methods
- Structs
- Classes
partial の使い所
partial キーワードを使うと、一つのクラスを複数のソースコードファイルに分割して記述することができます。
public partial class Item
{
public void Method1()
{
...
}
}
partial class Item
{
public void Method2()
{
...
}
}
この機能は、主に IDE が UI のデザインのコードとロジックのコードを分離するために用いるものです。
partial は、プログラマーが自前でコードを分割するためにも使えます。たとえば、IDisposalbe を実装するクラスを作る際に、IDisposalbe 関連のフィールドやメソッドだけを抽出して T.IDisposable.cs ファイルに分離する、といった使い方が考えられます。
public partial class NativeResource
{
#region Fields
private bool disposed;
#endregion
#region Destructor
~NativeResource()
{
...
}
#endregion
#region Methods
public void Dispose()
{
...
}
protected virtual void Dispose(bool disposing)
{
...
}
protected void AssertNotDisposed()
{
...
}
#endregion
}
型名のキーワードと静的メソッド
非常に細かいことですが、次のコードは、
if (string.IsNullOrEmpty(source))
{
...
}
こう書きたいです。
if (String.IsNullOrEmpty(source))
{
...
}
"string" は、C# のキーワードです。毎回、System.String だとか System.Int32 などと書くのは面倒なために、基本型については、対応するキーワードが言語側に用意されている訳です。
ただ、「キーワードは、飽くまで便宜のための構文糖であり、本来は型の名前にメソッドを続ける」との発想から、私は下の書き方をします。……まあ、好みの問題ですね。
例外の名前は省略しない
try-catch で特定の例外について捕捉する場合、例外の名前空間は省略せずに記述します。
try
{
...
}
catch (System.ArgumentException)
{
...
}
catch (System.OutOfMemoryException)
{
...
}
catch (System.IO.IOException)
{
...
}
例外の型は階層構造を意識したいです。
更新履歴
2010-01-27
- 「変数宣言」に追記。
2009-05-24
- 「クラスメンバーの宣言順序」に追記。
2009-04-04
- 「partial の使い所」を追加。
- 「型名のキーワードと静的メソッド」を追加。
2009-03-29
- 「はじめに」に追記。
- 「default(T) キーワード」を追加。
- 「クラスメンバーの宣言順序」を追加。
2009-03-07
- 初版。一部、過去の記事から統合。