C# CODING GUIDELINES
この記事は古い記事です。
あたらしい記事はこちらです。→ C# CODING GUIDELINES 2024
目次
- 目次
- このドキュメントについて
- 本書の目的
- 開発環境
- Visual Studio
- 命名規則
- サンプルコード
- 共通スタイルの説明
- 二文字の名前(※変更)
- 名前空間
- アセンブリ
- リソース
- ファイル
- クラス
- フィールド(※変更)
- 静的フィールド(≒グローバル変数)(※追加)
- コントロールのフィールド(※変更)
- プロパティ
- メソッド(≒関数、サブルーチン、ファンクション)
- 非同期メソッド(※追加)
- パラメータ(≒引数)
- ローカル変数、ループ変数
- コンパイル時定数、実行時定数
- 抽象クラス
- インターフェイス
- デリゲート(≒関数ポインタ)
- イベント
- 構造体
- 列挙体
- コーディング規則
- レイアウト規則
- コメント規則
- 長い名前
- 多い引数
- 多い演算子
- 自動プロパティ(※変更)
- 空のコンストラクタ(※追加)
- イベント処理(※変更)
- イディオム
- タブとスペースではスペースを使う
- ifの中括弧の省略はしない
- 否定形の名前は避ける
- マジックナンバーは使わない
- 変数は一度だけ設定する
- ガード節は積極的に使う
- プロパティはステートレスにする
- i++ と ++i
- for の比較演算子
- 比較演算子の向き
- nullではなく空の配列を返す
- ループよりLINQを使う
- キャストは as および is を使う
- 変わった書き方は避ける
- private なフィールドには _-(アンダーバー) を付ける
- this. および ClassName. の推奨
- var の使いどころ
- const と readonly の使い分け
- メソッドとプロパティの使い分け
- クラスと構造体の使い分け
- 解放が必要なオブジェクトには using を使う
- Dispose() 後は null を設定する
- catch 時の throw の使い分け
- 引数でコレクションを受けるときはできるだけ抽象度の高いものを使う
- Array, ArrayList, List の使い分け
- Hashtable, Dictionary の使い分け
- 一定時間スリープさせる方法
- アクセス修飾子について
- 修飾子の順番
- プリミティブ型(エイリアス)とCLS型の使い分け
- unsigned 型はなるべく使わない
- interface と abstract class の使い分け
- 拡張メソッドについて
- コードの依存関係
- ユーティリティクラスの是非
- コメント
- コメント タグ
- インテリセンスの表示例
- 付録
- 逐語的リテラル文字列
- 複合書式指定
- プログラミング原則、格言
- 拡張機能および外部ツール
- 参考資料
このドキュメントについて
本書の目的
命名規則、コーディング規則を遵守して生産性を上げることを目的としています。
自分で書いたコードでも長い間メンテナンスしなければ他人のコードと同じです。
一定の規則に従い、読みやすく、バグの少ない、メンテナンスのしやすいコードを目指しましょう。
規約に従うことは、多くの問題を改善し、技術的負債を減らします。
MSDN .NET Framework3.5 名前に関するガイドライン および MSDN C#のコーディング規則 を参考にしています。
MSDNのガイドラインはライブラリのPublicな範囲を対象としているので、Privateな範囲には必ずしも当てはまるわけではありません。
本書では、短い頭字語、コントロール名、privateフィールドに関してのみMSDNとは異なる規則を設けています。
開発環境
開発環境はVisual Studio 2019(以下VS, VC#)を想定しています。
基本的には言語仕様(C#)のバージョンは.NET Frameworkや実行環境(CLR)に依存しません。
極稀にジェネリック(C#2.0)やインターフェイスのデフォルト実装(C#8.0)のようにランタイム依存の変更が加わる場合があります。
できるだけ最新の開発環境を使用しましょう。
ルールをチェックできる拡張機能などは積極的に導入してください。
Visual Studio
Visual Studio .NET (2002) - 2002/03/22
C#1.0: 言語仕様
CLR 1.0: 実行環境
.NET Framework 1.0: ライブラリ
Visual Studio .NET 2003 - 2003/06/25
C#1.2: コメント(/** */)
CLR 1.1: バグ修正
.NET Framework 1.1: IPv6, ODBC, OracleClient
Visual Studio 2005 - 2006/02/01
C#2.0: ジェネリック(List<T>※CLR依存)、イテレータ(IEnumerable<T>)、null許容型(int?)、アクセサの公開レベル、静的クラス
CLR 2.0: 総称型(Generics)
.NET Framework 2.0: x64, ClickOnce, 総称型(Generics)
Visual Studio 2008 - 2008/02/08
C#3.0: 自動プロパティ、LINQ、暗黙的型付け(var)、ラムダ式、拡張メソッド、partial
.NET Framework 3.0: WPF、WCF、WF、WCS
.NET Framework 3.5: LINQ
.NET Framework 3.5 SP1
Visual Studio 2010 - 2010/06/18
C#4.0: オプション引数, 動的型付け(dynamic)
CLR 4.0: 性能改善(ガベージコレクタ)
.NET Framework 4.0: NuGet, PLINQ, TPL, DLR, MEF, Velocity, NUI, System.Tuple, dynamic
Visual Studio 2012 - 2012/09/12
UWP(Windowsストアアプリ)
C#5.0: async/await, Caller Info
CLR 4.5: 性能改善、WinRT
.NET Framework 4.5: Task
Visual Studio 2013 - 2013/10/17
Git(ソースコード管理)
.NET Framework 4.5.1
Visual Studio 2015 - 2015/07/20
Roslyn(コンパイラ), Xamarin(スマートデバイス開発), Unity(ゲーム開発), ASP.NET5
C#6.0: 初期化子(自動プロパティ、インデックス)、式本体関数メンバ、null条件演算子(?.)、文字列挿入($)、nameof演算子、using static、catch when文、await拡張、#pragma warningなど。
.NET Framework 4.5.2
.NET Framework 4.6: RyuJIT, .NET Native
Visual Studio 2015 Update 1 - 2015/11/30
ASP.NET5 RC1, REPL(Interactiveウィンドウ), csi.exe(*.csxスクリプト)
.NET Framework 4.6.1
Visual Studio 2015 Update 2 - 2016/03/30
パフォーマンスの改善, fuzzy matching, null条件演算子やawait演算子のリファクタリング
Visual Studio 2015 Update 3 - 2016/06/27
メモリ消費量の改善
Visual Studio 2017 15.0 - 2017/03/07
インストーラーの変更, ライトウェイト ソリューション ロード, EditorConfig(.editorconfig), Go To Types(Ctrl + T)
C#7.0: ValueTuple、出力変数、型スイッチ、参照戻り値、ローカル関数、非同期の戻り値型、二進数リテラル(0b1111_1111)、throw式拡張など
.NET Framework 4.6.2
.NET Core 1.0
.NET Standard 1.0: .NET Framework 4.5, .NET Core 1.0, Mono 4.6, Xamarin.Android 7.0(Android 7.0 Nougat - API 24, JDK 1.8), Xamarin.iOS 10.0(OSX 10.11.5 El Capitan, Xcode 8.0 GM)
Visual Studio 2019 16.1 - 2019/05/21
Visual Studio Live Share, IntelliCode(既定で無効), Xamarinの強化, editorconfigのエクスポート, 他
C#7.3(VS2017 15.7), C#8.0(preview)
.NET Framework 4.7.2
.NET Core 2.2
Visual Studio 2019 16.3 - 2019/09/23
IDEの改善, 署名証明書ファイル(.pfx)の作成機能が復活, リファクタリングの改善
C#8.0: インターフェイスのデフォルト実装(※CLR依存), null合体代入演算子(?=), nullable参照型(T?), 再起パターン, switch式拡張, 範囲アクセス(a..b), 非同期ストリーム(await foreach), using変数宣言(using var)など
.NET Framework 4.8
.NET Core 3.0: .NET Core 3.0 WPFデザイナ, gRPC対応
Visual Stduio 2017以降では随時アップデートが行われます。
以降の更新履歴はMicrosoft公式のリリースノートを参照してください。
命名規則
短い頭字語、コントロール名、privateフィールドに関してのみMSDNとは異なる規則を設けています。
ReSharperのデフォルトルールとも完全に一致はしません。
正しい名前付けを心掛けることにより、コードが洗練されます。
正しい名前を付けるには、正しいクラス構造が必要だからです。
意味のある、わかりやすい、正しい名前付けを心がけてください。
既存のコードを修正する場合は、そのスタイルに従ってください。
他のスタイルを混入させることは、品質の劣化となります。
サンプルコード
namespace Com.Domain.Sample
{
public sealed class SingletonClass
{
#region singleton pattern
private static SingletonClass Instance;
private SingletonClass() { }
public static SingletonClass GetInstance()
{
if (SingletonClass.Instance == null)
{
SingletonClass.Instance = new SingletonClass();
}
return SingletonClass.Instance;
}
#endregion
public void DoSomething(params string[] args)
{
if (args == null)
{
return;
}
bool exists = args.Any(x => !String.IsNullOrWhiteSpace(x));
int count = args.Count(x => !String.IsNullOrWhiteSpace(x));
var strings = args.Where(x => !String.IsNullOrWhiteSpace(x));
foreach (var s in strings)
{
Console.WriteLine(s);
}
}
}
public class FieldAndProperty
{
private int privateField;
private readonly int readonlyPrivateField;
// "const" is pascal case.
// don't use "const". "readonly" instead.
//private const float Zero = 0f;
//public const float Pi = 3.1415927f;
// "static" is pascal case.
private static int PrivateStaticField;
private static readonly int DaysInWeek = 7;
public static readonly int DayOfWeekCount = 7;
// don't use public fields. properties instead.
//public int PublicField;
//public readonly int ReadonlyPublicField;
//public static int ClassField;
// "property" is pascal case.
public Color Color { get; set; }
}
}
共通スタイルの説明
Pascal / Camel形式を基本とします。
大文字 / 小文字の違いのみによる区別は避けてください。
Pascal形式: PascalCaseName, IoException, XmlTransfer
Camel形式: camelCaseName, ioException, xmlTransfer
以下の識別名はCamel形式とし、それ以外はPascal形式とします。
パラメータ、privateなフィールド、ローカル変数
コメント以外は半角英数のみで記述し、英単語を使用してください。
日本語名が必要な場合は、コメントに含めます。ローマ字綴りは禁止とします。
スコープの短い局所変数では省略形を許可しますが、それ以外では単語は省略せずに記述してください。
例えば、表記揺れでjpeg, jpg, message, msg, window, win, windowsなどが混ざることは避けてください。
コレクションやリストの変数名は複数形とします。
単数形: File file
複数形: List<File> files, byte[] bytes
二文字の名前(※変更)
ID, OK, IP, DBなど二文字からなる言葉は短い名前といいます。
MSDNでは二文字からなる短い頭字語はすべて大文字としています。
ID(Identifier)とOK(Okay)は頭字語ではなく、省略形となり区別が必要となります。
ここではMSDN形式を推奨しますが、規定とはせず、表記ゆれも許容します。
Pascal形式(MSDN): IdManager, OkButton, IPAddress, DBTable, IOReader, XmlReader
Camel形式: idManager, okButton, ipAddress, dbTable, ioReader, xmlReader
名前空間
Pascal形式とします。
名前空間は一意でなければなりません。
社名、ソリューション名(製品名)、プロジェクト名(部品名)などを組み合わせてピリオドでつなぎます。
ドメイン名を利用してもかまいません。
名前空間の例:
Microsoft.CSharp
Com.Domain.<Product>.<Component>
Jp.Domain.<Solution>.<Project>
Jp.Co.Domain.<TeamName>
アセンブリ
実行ファイルはプロジェクト名(ProjectName.exe)、DLLは名前空間およびプロジェクト名(Com.Domain.Solution.ProjectName.dll)を用います。
DLLの名前が重複する可能性がなければ、プロジェクト名(ProjectName.dll)でもかまいません。
実行ファイル: ProjectName.exe
DLLファイル: Com.Domain.SolutionName.ProjectName.dll
リソース
Pascal形式とします。
階層構造を明示し、使用箇所を明確にします。
共通で使うリソースなどもあるので、わかりやすいリソース名としましょう。
リソース: Menu.File.Open.Icon, ArgumentNullExcception.Message
ファイル
クラス名と同じ名前にします。
基本的には1ファイルにつき1クラスですが、その他のファイルから参照していない依存度の高いクラスは、1つのファイルにまとめるか内部クラスにします。
クラス
Pascal形式とします。役割を表す名前をつけます。
処理中心のクラスには接尾辞として-erをつけます。
派生クラスは、基本クラス(継承元)の名前を含めます。
クラス: String, Window, Task
処理中心のクラス: StringBuilder
Exceptionを継承したクラス: ArgumentException, ArgumentNullException
EventArgsを継承したクラス: KeyDownEventArgs
フィールド(※変更)
privateはCamel形式とし、それ以外はPascal形式とします。
フィールドは、インスタンスフィールドやメンバ変数とも呼ばれます。
public, internalはなるべく使用せず、外部からアクセスするにはプロパティを用意します。
接頭辞として_-(アンダーバー)を付けることを禁止しませんが、推奨ではありません。
表記揺れで二つの形式が混ざることだけは避けてください。
論理値を格納する変数に限り、例外的に、is-, can-, has-を用いることができます。
ReSharperを導入できる場合のみ_-(アンダーバー)を付けることを推奨します。
静的フィールド(≒グローバル変数)(※追加)
staticが付与された場合はPascal形式とします。
静的フィールドは、クラスフィールドやクラス変数とも呼ばれます。
コントロールのフィールド(※変更)
既定の修飾子がprivateなのでCamel形式となります。
Windows Formsの場合はModifiersプロパティ、XAML(WPF, UWP)の場合はFieldModifierプロパティとなります。
コントロール名に限り、ハンガリアン形式で記述することを許可しますが、規定ではありません。
コントロール名の短縮形式の代わりにui-を使用できます。
表記揺れで別の形式が混ざることは避けてください。
イベントメソッドは<コントロール名>+<イベント名>とします。
イベントメソッドはPascal形式に修正が必要です。
コントロール名: userNameLabel, userNameTextBox, passwordLabel, okButton
ハンガリアン記法: lblUserName, txtUserName, lblPassword, btnOk
ui接頭辞: uiUserName, uiPassword, uiOk, uiCancell, uiStatus
イベントメソッド: BtnOk_Click, TxtUserName_Validating
共通イベントメソッド: Window_Loaded, TextBox_GotForcus, TextBox_KeyDown
プロパティ
Pascal形式とします。名詞、形容詞とします。
C# 3.0以降は自動プロパティにより、get/setアクセサの中身を省略できるようになりました。
元になる型があれば、プロパティ名と型名は同じにしてください。
論理値を格納するプロパティの場合は、形容詞で表現できないか検討します。
プロパティ: Name, Closed, Size, Position, Length
型と同じプロパティ: public System.Drawing.Color Color { get; set; }
論理値を返すプロパティ: Opened, Closed, Enabled, Visible, Shown
メソッド(≒関数、サブルーチン、ファンクション)
Pascal形式とします。動詞とします。
インスタンスを生成するメソッドはCreate-やNew-を使います。
論理値を返すメソッドにはIs-, Can-, Has-を使います。
特別な場合を除きIs+<形容詞>、Can+<動詞>、Has+<過去分詞>とします。
型を変換するメソッドは例外的にTo-を使います。
インスタンスを生成するメソッド: CreateInstance, NewInstance
論理値を返すメソッド: IsNull, IsNullable, IsEmpty, CanRead, HasChanged
型を変換するメソッド: ToString, ToInt32
非同期メソッド(※追加)
メソッドの命名規則に従います。
接尾辞として-Asyncを付けます。
public static async void Main()
{
// 非同期開始
var task = this.ReadAsync();
// 同期的に完了を待てます。
await task;
}
// 非同期メソッドの戻り値はTask, Task<T>とします。
public Task ReadAsync()
{
return Task.Run(() => this.Read());
}
パラメータ(≒引数)
Camel形式とします。説明的な名前をつけます。
パラメータ名と型を見ただけで使用法が判断できるような名前が理想的です。
ジェネリックの型パラメータには接頭辞としてT-をつけます。
メソッドパラメータ: Console.WriteLine(string message)
型パラメータ: Dictionary<TKey, TValue>
※Parameters = 仮引数, Arguments = 実引数
ローカル変数、ループ変数
ローカル変数(局所変数)は、Camel形式とします。
できるだけ最小のスコープ内で、使用する直前に宣言します。
省略した名前はローカル変数以外では禁止とします。
名前を省略した場合でも、できるだけ意味の分かる名前を付けます。
ループ変数はi, jとし、それ以上必要な場合は設計を見直します。
省略形: ctor(Constructor), addr(IPAddress), conn(Connection), btn(Button)
意味の分かる省略形: bldr, builder(StringBuilder), reader(StreamReader)
意味の分からない省略形: sb(StringBuilder), sr(StreamReader)
ループ変数: for(var i = 0; i < x; i++)
コンパイル時定数、実行時定数
Pascal形式とします。
全て大文字で定義するスタイルではないので注意してください。
コンパイル時定数はconst、実行時定数はstatic readonlyを指します。
constはなるべく使用せず、static readonlyにできないか検討します。
抽象クラス
Pascal形式とし、クラスの命名規則に従います。
接尾辞として-Baseをつけます。
抽象クラス: abstract class TextBoxBase
TextBoxBaseを継承したクラス: TextBox, RichTextBox
インターフェイス
Pascal形式とし、クラスの命名規則に従います。
インターフェイスには接頭辞としてI-をつけ、機能を定義したものには接尾辞として-ableをつけます。
機能を定義したインターフェイス: interface IDisposable
デリゲート(≒関数ポインタ)
Pascal形式とし、クラスの命名規則に従います。
デリゲートはメソッドへの参照を保持できるクラスです。
メソッドのシグネチャを指定するため記述が独特となります。
デリゲートには接尾辞として-Callbackを追加します。
イベント用デリゲートには接尾辞として-EventHandlerを追加します。
delegate void ActionCallback();
delegate void ActionEventHandler(object sender, EventArgs e);
イベント
Pascal形式とします。イベント名は動詞とします。
時制を表す場合は-ing(現在進行形)や-ed(過去形)とします。
クラス内のイベントメソッドは、On+<イベント名>とし、引数はEventArgsの拡張クラスに限定します。
クラス外のイベントメソッドは、<コントロール名>_<イベント名>とします。
// イベント用デリゲート変数(<イベント名>)
// 外部のメソッドを関連付けます。
public event EventHandler Click;
// 内部イベント(On+<イベント名>)
// 何かの処理によりイベントが発生します。
protected virtual void OnClick(EventArgs e)
{
// TODO: 何らかの処理
// ...
// 関連する外部メソッドにも通知します。
this.Click?.Invoke(this, e);
}
構造体
Pascal形式とします。
多数の軽量オブジェクトを表す場合に使用します。(例: Color, Point, Rectangleなど)
またアンマネージドなDLLと情報をやり取りする場合にも使用します。
列挙体
列挙体、列挙値ともにPascal形式とします。
複雑な構造が必要な場合は抽象クラスの使用を検討します。
基本は単数形ですが、Flags(ビットフィールド)を表す場合は複数形とします。
コーディング規則
レイアウト規則
- 1つの行には1つの宣言のみ記述します。
- 1つの行には1つのステートメントのみを記述します。
- メソッドとプロパティの各定義の間には1つ以上の空白行を追加します。
- タブ文字は使わず、スペース4つとします。
- 継続行には1タブ分(スペース4つ)のインデントを行います。
- 式(expression)に句(clause)を含めるときは括弧でくくります。
コメント規則
- コード行の末尾ではなく別の行に記述します。
- 文章となるように記述します。(先頭大文字、ピリオドで終わる)
- コメント記号(
//)と文字の間には空白をひとつ挿入します。
var a = 0; // ダメなコメントのパターンです。
// コメントは別の行に記述します。
var a = 0;
長い名前
1行に収まらない場合は.(ピリオド)の後で改行を行うことができます。
var currentPerformanceCounterCategory = new System.
Diagnostics.PerformanceCounterCategory();
多い引数
引数が多く、1行が長くなる場合は((括弧)や,(カンマ)の後で改行することができます。
引数の数が多くなる場合はBuilderパターンの適用も検討しましょう。
メソッドチェーンを記述する場合はピリオドを前置とします。
var ipAddr = String.Format(
"{0}.{1}.{2}.{3}",
0x7F, 0x00, 0x00, 0x01);
var connStr = new StringBuilder()
.AppendFormat("Server={0};", "localhost")
.AppendFormat("Database={0};", "master")
.AppendFormat("User Id={0};", "sa")
.AppendFormat("Password={0};", "sa")
.ToString();
多い演算子
演算子が多く、1行が長くなる場合は演算子の前後で改行することができます。
bool isEmpty = ((data == null)
|| (data == DBNull.Value)
|| (String.IsNullOrEmpty(data.ToString())));
if ((data == null) ||
(data == DBNull.Value) ||
(String.IsNullOrEmpty(data.ToString())))
{
// 処理
}
自動プロパティ(※変更)
自動プロパティは1行で記述します。
// prop -> tab -> tabで補完できます。
public int MyProperty { get; set; } = 0;
空のコンストラクタ(※追加)
空のコンストラクタは1行で記述します。
// ctor -> tab -> tabで補完できます。
public int MyConstructor { }
イベント処理(※変更)
MSDNには、後で削除する必要のないイベントハンドラーを定義する場合はラムダ式を使うと書かれていますが、ここではその制限を行いません。
イベントの数が多いと見通しが悪くなりますし、Tab補完やpartial(コードビハインド)の記述に合いません。
public SampleForm()
{
// 推奨する記法(C#2.0)
// Event名 += -> tab -> tabで補完できます。
this.Click += this.SampleForm_OnClick;
// 古い記法(C#1.0)
this.Click += new EventHandler(this.SampleForm_OnClick);
// 匿名メソッド(C#2.0)
this.Click += delegate(object s, EventArgs e) =>
{
// TODO: 処理
};
// ラムダ式(C#3.0)
this.Click += (s, e) =>
{
// TODO: 処理
};
}
private void SampleForm_OnClick(Object sender, EventArgs e)
{
// TODO: クリックされたときの処理を実装します。
}
イディオム
タブとスペースではスペースを使う
タブとスペースのどちらを使うかという論争があります。
どちらを使ってもよいですが、混在だけは避けましょう。
MSDNの規約にもあり、VC#でデフォルトとなっているスペース4つがおすすめです。
ifの中括弧の省略はしない
ifの{ }(中括弧)は省略することができます。
returnのみだったり、同じような処理が並ぶ場合は、省略したくなります。
しかし、省略することで、括弧によらず、インデントのみで区別される行ができてしまいます。
バグの元になりやすいので中括弧の省略はおすすめできません。
否定形の名前は避ける
メソッド名および変数名は肯定形で記述します。
条件式で否定が続くと読みにくくなります。
二重否定を避けるという理由もあります。
// 二重否定
if (!isNotFound)
if (!IsNotNull())
// 肯定形
if (Exists())
if (Contains())
マジックナンバーは使わない
冗長なようでもマジックナンバーの代わりに正しい名前を持つ変数を用意します。
また、その値を設定した理由をコメントに残しておくこともコードの理解を助けます。
ただし、文脈から意味が明確な場合は例外とします。
public static class Constants
{
public static readonly int DaysInWeek = 7;
public static readonly int HoursInDay = 24;
}
// 文脈から意味が読み取れる場合
var mean = (a + b) / 2;
var milliSec = sec * 1000;
変数は一度だけ設定する
変数には値を一度だけ設定する方が見通しがよくなります。
変数の値が変わる頻度が多いと読みにくくなります。
フィールドのreadonly、ローカル変数のconstは積極的に使います。
ガード節は積極的に使う
メソッドの早い段階でのreturnは積極的に行います。
ネストを減らすことができます。
最後にただひとつだけreturnをするという方針は無意味です。
必ず実行したい処理がある場合にはtry-finallyの適用を検討します。
プロパティはステートレスにする
プロパティを設定する順番によって差があってはなりません。
例えば、DataSourceとDataMemberのプロパティは、
どちらを先に設定しても同じ結果になります。
i++ と ++i
どちらを使っても問題ありません。
速度に与える影響は微々たるものですし、コンパイラによっても変わります。
ただし、式の評価順が結果に影響を与える場合は注意が必要です。
i++を式に埋め込むより、別の行にできないか検討します。
for の比較演算子
forに用いる比較演算子は<を使うことが多いです。
これは配列がゼロベースのインデックスで用いられるためです。
for (var i = 0; i < length; i++)
比較演算子の向き
比較演算子は、左を主とするのが一般的です。
範囲を扱う場合は、比較演算子の左を小、右を大として、<, <=のみ使用する方がわかりやすくなります。
// 比較演算子の左を主とした場合
if ((a >= 90 && a <= 180) ||
(a >= 270 && a <= 360))
// 比較演算子の向きを揃えた場合
if ((90 <= a && a <= 180) ||
(270 <= a && a <= 360))
nullではなく空の配列を返す
メソッドでリストや配列を返すときは、nullではなく空のインスタンスを返すようにします。
nullチェックの煩雑さがなくなり、処理が簡潔になります。
ループよりLINQを使う
LINQを使うとループより意味を明確に記述できます。
クエリ構文は内部的にメソッド構文に変換して実行されます。
LINQのフル機能にアクセスするためにはメソッド構文を使用する必要があります。
// 通常のループ
var evenMax = 0;
foreach (var d in decimals)
{
if (d % 2 == 0)
{
if (d > evenMax)
{
evenMax = d;
}
}
}
// LINQ(メソッド構文 + ラムダ式)
var oddMin = decimals
.Where(x => (x % 2) == 1)
.Min();
// LINQ(クエリ構文)
var averageAgeOfMan = (
from person in persons
where (person.Sex == Sex.Man)
select person.Age).Average();
キャストは as および is を使う
C#7.0以降はis式のパターンマッチングを使用できます。
以前は参照型ではas演算子によるキャストが推奨されていました。
// 推奨する記法(C#7.0)
if (obj?.Value is int i)
{
}
// Null条件演算子(C#6.0)
if (obj?.Value is int)
{
int i = (int)obj.Value;
}
// 古い記法(C#1.0)
if (obj != null &&
obj.Value is int)
{
int i = (int)obj.Value;
}
// as演算子
var instance = obj as IDisposable;
if (instance != null)
{
instance.Dispose();
}
変わった書き方は避ける
広く使われている書き方の方が読みやすいです。
コードは書きやすさより読みやすさを重視します。
// 特異な記述
// while ((len--) > 0)と等価
var len = 100;
while (len --> 0)
{
// 99 .. 0 のループ
}
// 一般的な記述
for (var i = 99; i >= 0; i--)
{
// 99 .. 0 のループ
}
private なフィールドには _-(アンダーバー) を付ける
MSDNではprivateなフィールドはCamel形式で記述されるので、このイディオムを嫌う人も多いです。
しかし、アンダーバーを付けることにより、this.を省略してもprivateなフィールドだと判別できます。
また、引数とフィールドが同名となることを避けることができます。
ReSharperで推奨されています。
ReSharperを導入しない場合は、MSDNのCamel形式に従う方がよいでしょう。
this. および ClassName. の推奨
this.は現在のインスタンスを参照します。
フィールドやメソッドを呼び出す場合、this.をつけることによって明示することができます。
メソッド内でthis.がついていたらローカル変数ではなくフィールドだと判別できます。
base.はoverrideなどで隠匿されたメンバへ明示的にアクセスする場合にのみ使います。
staticなメンバにアクセスする場合にはClassName.(クラス名)を付けます。
冗長な場合は省略されることもありますが、static classでない場合は明示した方がよいでしょう。
ReSharperでは省略が推奨されていますが、明示した方が間違いを減らせます。
var の使いどころ
厳密な型が重要でない場合にはvarを積極的に使います。
変数名からvarの型が推測できない場合は名前を修正してください。
var n = 10m;はDecimal n = 10m;と明示した方が間違いを減らせます。
ReSharperではvarの使用を推奨されていますが、適用箇所は考えた方がよいでしょう。
const と readonly の使い分け
constはコンパイル時定数ですが、readonlyは実行時定数(変数)です。
constを使用するとデバッグ時のエディットコンティニュで変更できません。
アセンブリ内への定数の埋込が発生するので再コンパイルが必要になります。
属性(attribute)や列挙型(enum)の定義など、コンパイル時に定数が必要な箇所のみconstを使用します。
ReSharperではconstの使用が推奨されていますが、代わりにreadonlyを使ってください。
メソッドとプロパティの使い分け
次の場合はプロパティを使用することを検討してください。
- Get/Setメソッドの代わりに変数にアクセスする場合
- 値がクラスの属性を表す場合(
Name,Opened,BorderStyleなど)
次の場合は、プロパティではなくメソッドを使用してください。
- 処理に時間がかかる場合
- パラメータの変更なしに異なる結果が返る場合
-
ToString()などの変換を行う場合 -
ToArray()などの配列が返る場合 - 内部状態のコピーが返される場合
クラスと構造体の使い分け
次の場合は、構造体を使用することを検討してください。
- プリミティブ型のように単一で何かをあらわす場合
- インスタンスのサイズが16Byte未満
- ボックス化が必要ない場合
- 値が変更されない場合
解放が必要なオブジェクトには using を使う
IDisposableインターフェースを実装し、Dispose()メソッドが実装されているオブジェクトにはusingステートメントを使います。
ファイル、画像、メモリ、ネットワーク、リソースを扱うオブジェクトは解放が必要となります。
try
{
string path = "test.txt";
using (var stream = new FileStream(path, FileMode.Read))
using (var reader = new StreamReader(stream))
{
// 処理
}
}
Dispose() 後は null を設定する
オブジェクトはDispose()後すぐにメモリから開放されるわけではありません。
nullを設定することにより危険な参照を避けることができます。
実行環境によってはnullを設定しないとガベージコレクタで解放されない場合があります。
catch 時の throw の使い分け
throw ex;でリスローとするとスタックトレースが上書きされます。
その場で処理するべきか、呼び出し元へ伝えるべきかで使い分けます。
catch (Exception1)
{
// 原因を呼び出し元に伝える場合
db.Rollback();
throw;
}
catch (Exception2 ex)
{
// 原因の詳細がそれ以上必要ない場合
Debug.WriteLine(ex.ToString());
throw ex;
}
引数でコレクションを受けるときはできるだけ抽象度の高いものを使う
メソッドの引数にIEnumerable<T>を指定することによって、引数の内容が変化しないことを明示できます。
IEnumerable<T>は"列挙可能"なことを示しますが、IList<T>は"追加・削除"が可能です。
.NET 4.5以降はIReadOnlyList<T>もありますが、IEnumerable<T>の方が最初から順番にアクセスしていく意味が強いです。
public void DoAnything(IEnumerable<T> list)
Array, ArrayList, List の使い分け
-
System.Array
すべての配列の基本クラスです。[]で宣言します。
固定長なのでパフォーマンスを考慮する場合や画像やデータを保持する場合などに使います。 -
System.Collections.ArrayList
サイズが動的に変動する配列です。
型指定できないのでArrayListよりList<T>を使います。 -
System.Collections.Generics.List<T>
型指定されたArrayListです。
// Array
byte[] bytes1 = new byte[2] { 0x00, 0xFF };
byte[] bytes2 = new byte[] { 0x00, 0xFF };
byte[] bytes3 = { 0x00, 0xFF };
var array = new[] { 0x00, 0xFF };
// List<T>
List<string> words = new List<string>();
words.Add("Hello");
words.Add("World");
words.Insert(1, ", ");
string[] array = words.ToArray();
Hashtable, Dictionary の使い分け
-
System.Collections.Hashtable
キー/値のペアの配列です。連想配列やハッシュ(ハッシュテーブル)などとも言います。
型指定できないHashtableよりDictionary<TKey, TValue>を使います。 -
System.Collections.Generic.Dictionary<TKey, TValue>
型指定されたHashtableです。
// Dictionary<TKey, TValue>
var dic = new Dictionary<int, string>();
int key = 0;
dic.Add(key, "value");
if (dic.ContainsKey(key))
{
string data = dic[key];
}
一定時間スリープさせる方法
スリープさせるにはいくつかの方法があります。
Wait()はawaitと組み合わせるとデッドロックが発生するので注意が必要です。
int ms = 1000;
// 古い書き方
// スレッドごと停止します。
Thread.Sleep(ms);
// .NET4以降
Task.Delay(ms).Wait();
// asyncメソッド内であればawaitを使います。
await Task.Delay(ms);
アクセス修飾子について
アクセス修飾子は省略することが可能ですが、明示した方が間違いを減らせます。
protectedとinternalは依存関係を複雑にするため、あえて使用しない場合もあります。
アクセシビリティレベルの制限は下記の通りです。
public: 制限なし
private: 自身(クラス内)のみ許可する
internal: 同一プロジェクト内(同一アセンブリ)に許可する
protected internal: 同一プロジェクト内 または 派生クラスに許可する
protected: 派生クラスに許可する
private protected: 同一プロジェクト内の派生クラスに許可する
アクセス修飾子を省略した場合のアクセシビリティレベルは、下記の通りです。
トップレベルの型: internal(同一アセンブリ内のみ)
class, structのメンバー: private(クラス内のみ)
interfaceのメンバー: public
enumのメンバー: public
修飾子の順番
修飾子は、下記の順番が推奨されています。
public protected internal private new abstract virtual override sealed static readonly extern unsafe volatile async
ReSharperで推奨されています。
プリミティブ型(エイリアス)とCLS型の使い分け
型を宣言する場合はプリミティブ型(object, int, stringなど)を使います。
静的メンバーを参照する場合はCLS型(Object, Int32, Stringなど)を使います。
MSDNではエイリアスを活用するよう推奨されますが、クラスの意味を考えるとCLS型を使ったほうがスマートです。
if (String.IsNullOrEmpty(str))
if (Decimal.TryParse(txt, out decimal n))
unsigned 型はなるべく使わない
uintすなわちSystem.UInt32はCLSに準拠していません。
uintではなく、intを使用します。
intを使用すると他のライブラリと対話しやすくなります。
interface と abstract class の使い分け
interfaceにメンバを追加すると、既存のコードを破壊します。
将来にわたって変更がない場合や、多重継承が必要な場合にのみinterfaceを使用します。
interfaceに共通のメソッドが必要な場合は拡張メソッドの使用を検討します。
それ以外はabstract classを使用しましょう。
拡張メソッドについて
拡張メソッドはenum, interfaceなどメソッドが追加できないものに使います。
それ以外はインスタンスメソッドや静的メソッドで解決できないか検討します。
public static class FooExtensions
{
public static void Method(this IFoo foo) { }
}
System.LinqにはIEnumerableに対しての拡張メソッドが用意されています。
コードの依存関係
コードを共有化(ライブラリ化)することで、再利用性を高めることは重要です。
しかし、共有化するということは、ライブラリへの依存度を高めます。
ライブラリの変更が広範囲にわたる場合は、保守のコストが増大します。
共有化する場合は、ライブラリが肥大化しないよう、最小限の機能となるよう検討します。
ユーティリティクラスの是非
重複を避けるため共通の処理をユーティリティクラスとし、コードを再利用するのは便利です。
しかし、オブジェクト指向的ではなく、手続き型的であり、クラスの再利用性に問題があります。
多くのクラスがユーティリティクラスを呼び出し、依存してしまうことが予想されます。
ユーティリティクラスを作りたくなったら、本当に必要かどうか検討します。
例えばアプリケーション全体で、デバッグ用のログを出力したくなったとします。
汎用ユーティリティクラスの中の一部にログ出力がある場合は、専用のLogクラスまたはLoggingパッケージにできないか検討します。
例えばString.IsNullOrEmpty()というメソッドがあり、他のクラスでも似た機能を利用したくなったとします。
汎用ユーティリティクラスの中にIsNullOrZero()があるのはオブジェクト指向的ではありません。
可能であれば、対象のクラスを継承し、必要なメソッドを追加しましょう。
不可能であれば、専用ユーティリティクラスにできないか検討します。
多数に追加する必要があるならば、インターフェイスと拡張メソッドの組み合わせを検討します。
コメント
正しい名前と構造を持ったコードは、コメントがなくても読みやすいですが、コードの要約(summary)を読む方が、簡単で間違いがありません。
必要な箇所に必要なコメントを記述することはコードの品質を高めます。
コードを復唱するコメントはいりません。
コードを抽象レベルで説明するコメントを書いてください。
TODO(実装予定), UNDONE(実装中), HACK(改良予定)などトークンも併せて使うとよりわかりやすくなります。
コメント タグ
C# ドキュメンテーション コメント形式で記述すればインテリセンス(IntelliSense)へ反映されます。
Sandcastleなどのツールを利用すればAPIドキュメントを作成することも可能です。
VC#で///と入力すれば自動的に補完されます。
- summary
要約です。何をするコードか1行で簡潔に記述してください。インテリセンスで表示されます。 - remarks
解説です。要約を補足します。 - param
コンストラクタやメソッドのパラメータの説明を記述します。nameプロパティによりパラメータ名を指定できます。 - returns
メソッドの戻り値の説明を記述します。結果がboolなどで説明の必要がなければ省略してもかまいません。
/// <summary>
/// MyMethodの要約です。1行で記述します。
/// </summary>
/// <remarks>
/// MyMethodの補足です。
/// 細かい内容はこちらに記述します。
/// </remarks>
/// <param name="x">xの説明</param>
/// <returns>戻り値の説明</returns>
public bool MyMethod(int x)
- para
summaryやremarks内で段落に分けたい場合に使用します。
/// <summary>
/// <para>MyMethodの要約です。</para>
/// <para>paraを使えば複数行も記述できます。</para>
/// </summary>
/// <remarks>
/// <para>MyMethodの補足です。</para>
/// <para>改行や段落を表現したいときはparaを使います。</para>
/// </remarks>
public bool MyMethod(int x)
- value
プロパティが表す値の説明を記述します。
/// <summary>
/// Nameプロパティです。
/// </summary>
/// <value>Nameプロパティの値の説明</value>
public bool Name { get; }
- exception
例外の説明を記述します。crefプロパティによりスローできる例外を指定できます。
メソッド、プロパティ、イベントに対応します。
/// <exception cref="System.ArgumentException">例外の説明です。</exception>
/// <exception cref="System.Exception">例外の説明です。</exception>
public void ThrowException(params object[] args)
- see
文中にリンクを追加できます。crefプロパティによりクラスまたはメンバを指定できます。
/// <summary>
/// 内部で<see cref="MyClass.MyMethod(System.String)"/>を呼び出しています。
/// </summary>
- seealso
参照項目として追加できます。crefプロパティでメンバを指定できます。
hrefプロパティはSandcastleでAPIドキュメントを作成する際に有効です。
/// <seealso cref="MyClass"/>
/// <seealso cref="MyClass2.MyMethod(int)"/>
/// <seealso href="http://www.google.com/"/>
- example
使用例を記述します。コード部分はcまたはcodeタグを使用します。 - c
1行のみのコードを記述します。説明内でコードが必要な場合に使用します。 - code
複数行のコードを記述します。
/// <example>
/// 使い方:
/// <c>int x = await ReadAsync(...);</c>
/// </example>
インテリセンスの表示例
VSでは下記のように表示されます。
/// <summary>
/// ログにDebugメッセージを出力します。
/// </summary>
/// <param name="format">出力メッセージ(複合書式指定可)</param>
/// <param name="args">パラメータ(省略可)</param>
public static void d(string format, params object[] args)
付録
逐語的リテラル文字列
@を付けるとエスケープを省略でき、ヒアドキュメントとして扱えます。
Verbatim String Literalと呼ばれます。
//var path = "C:\\Users\\UserName\\Documents";
var path = @"C:\Users\UserName\Documents";
var sql = @"
SELECT
u.user_name
, o.order_date
FROM
users u
INNER JOIN orders o
ON o.user_id = u.user_id
WHERE
u.user_id = @user_id
AND o.order_id = @order_id'
;";
複合書式指定
書式を指定して文字列を作成できます。
書式は下記の通りです。
alignmentは-(マイナス)で左寄せとなります。
{index[,alignment][:format]}
C#6以降は文字列挿入(Interpolated Strings)の書式が推奨されます。
//var coord = String.Format("({0}, {1})", x, y);
var coord = $"({x}, {y})";
数値の場合(標準)
Cn: 通貨(nは小数桁)
Dn: 10進数(nは桁数)
En: 指数(nは小数桁)
Fn: 小数(nは小数桁)
Gn: 数値(nは有効桁)
Nn: 数値(nは小数桁)
Pn: パーセンテージ(1で100%, nは小数桁)
R: ラウンドトリップ(Single, Double, BigIntegerのみ)
Xn: 16進数(FF, nは桁数)
※nは省略できます。
※大文字でも小文字でも適用されます。
float f = 0.1F;
int yen = 1000;
byte b = 0xFF;
// f=10.00%
var percent = $"f={f:P2}";
// yen= \1,000
var currency = $"yen={yen,10:C}";
// b=0x00FF
var hex = $"b=0x{b,-5:X4}";
数値の場合(カスタム)
0: ゼロ プレースホルダー
#: 桁プレースホルダー
%: パーセンテージ
.: 小数点
,: 桁区切り記号
;: セクション区切り記号(正,負,ゼロ)
var percent = $"{f:0.00%}";
var phone = $"{tel:(###)###-####}";
var section = $"{n:#;(#);Zero}";
日付の場合
d: 短い形式の日付(yyyy/MM/dd)
D: 長い形式の日付(yyyy年M月d日)
g: 短い形式の日時(yyyy/MM/dd hh:mm)
※カルチャに依存します。
g, gg: 年号(西暦)
y, yy, yyy, yyyy: 年
M, MM: 月
MMM, MMMM: 月名(M, M月)
d, dd: 日にち
ddd, dddd: 曜日(日, 日曜)
f, ff, fff, ...: 秒以下の値
h, hh: 時間(12時間形式)
H, HH: 時間(24時間形式)
m, mm: 分
s, ss: 秒
tt: AM/PM(午前/午後)
K: タイムゾーン(+09:00)
:: 時刻の区切り記号
/: 日付の区切り記号
※TimeSpan型の場合は、区切り記号が使えないのでエスケープする必要があります。
//var date = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff");
//var date = String.Format("{0:yyyy/MM/dd HH:mm:ss.fff}", DateTime.Now);
var date = $"{DateTime.Now:yyyy/MM/dd HH:mm:ss.fff}";
//var date = TimeSpan.MaxValue.ToString(@"yyyy\/MM\/dd HH\:mm\:ss\.fff")
var date = $@"{TimeSpan.MaxValue:yyyy\/MM\/dd HH\:mm\:ss\.fff}";
列挙型の場合
G, F: 文字列
D: 整数
X: 16進数(0xFF)
※大文字でも小文字でも適用されます。
プログラミング原則、格言
-
DRY
DRY(Don't repeat yourself)とは、アプリケーションに必要な情報は、重複を避けるべきという考え方です。
重複があると変更漏れや作業量、コード量の増大につながります。
DRY原則に反しているシステムをWET(Write Every Time)なシステムと呼びます。 -
OAOO
OAOO(Once and only once)とは、コードの機能、ふるまいの重複を避けるべきという考え方です。 -
KISS
KISS(Keep it simple, stupid)とは、簡潔にせよという考え方です。
不必要な複雑性は避けるべきです。 -
YAGNI
YAGNI(You aren't gonna need it)とは、無駄をなくすための考え方です。
シンプルに作成し、必要になったときに実装するべきです。 -
SDP
安定依存の原則(SDP: Stable-dependencies principle)とは、パッケージ間の依存関係の指針です。
変更が少なく被参照が多い安定したパッケージが、変更が多い不安定なパッケージに依存してはいけません。
OCPやDIPに従い抽象化することによりSDPに則した構造となります。 -
SOLID
SRP, OCP, LSP, ISP, DIPの頭文字からSOLIDとまとめて呼ばれます。 -
SRP
単一責任の原則(SRP: Single responsibility principle)とは、クラスの変更理由はひとつでなければならないという考え方です。
責任という観点で見ることが重要です。 -
OCP
オープン・クローズドの原則(OCP: Open/closed principle)とは、拡張に対して開いていて、修正に対して閉じていなければならないという考え方です。
抽象化を行い、継承していくことで機能の拡張ができます。
抽象化しておくと、実装を差し替えるだけでインターフェイスには影響を与えません。
抽象化は柔軟性と複雑性のトレードオフとなります。 -
LSP
リスコフの置換原則(LSP: Liskov substitution principle)とは、スーパークラスとサブクラスは置換できなければならないという考え方です。
サブクラスでオーバーライドされた結果、動作が意図しないものに変わってしまってはいけません。 -
ISP
インターフェイス分離の原則(ISP: Interface segregation principle)とは、インターフェイスをシンプルに保つための指針です。
複数のクライアントにおいて、片方のクライアントから参照しないメソッドがある場合は、インターフェースを分離します。 -
DIP
依存関係逆転の原則(DIP: Dependency inversion principle)とは、依存関係を切り離す抽象化の手法です。
上位のクラスは下位のクラスに依存するべきではなく、どちらもインターフェイスに依存させます。 -
PIE
PIE(Program intently and expressively)とは、意図を明確に表現するコードを書くということです。
書きやすさより読みやすさを重視します。 -
SLA, SLAP
SLA(Single level of abstraction principle)とは、抽象化レベルを揃えるという考え方です。 -
PLS, PLA, POLA
驚き最小の原則(PLS: Principle of least surprise)とは、最も自然に思えるものを選択すべきだとする考え方です。 -
ループバックチェック
名前可逆性という命名に関する指針があります。
名前は、そのもととなった内容の説明文を復元できなければならないということです。 -
行ってはならない
パフォーマンスチューニング、最適化に関する2つの格言です。
行ってはならない(Don't do it.)
まだ行ってはならない(Don't do it yet.)
早すぎる最適化は、無駄になるだけでなく、コードを複雑にしてしまいます。 -
求めるな、命じよ
求めるな、命じよ(TdA: Tell, don't ask.)とは、オブジェクト指向の設計指針です。
手続き型では、情報を取得して自分で処理をします。
オブジェクト型では、オブジェクトに処理を命じます。 -
デメテルの法則
デメテルの法則(LoD: Law of Demeter)とは、依存関係を排除する指針となります。
最小知識の原則(PLK: Principle of least knowledge)や知らないヤツには話しかけない(Don't talk to strangers.)とも呼ばれます。
オブジェクト自身、オブジェクトに渡されたインスタンス、オブジェクト内部で生成したインスタンス以外には依存してはいけません。
例えば「あるインスタンス」から「別クラスのインスタンス」を取得した場合、「別クラス」への依存関係が発生してしまいます。 -
オペランドの原則
メソッドの引数にはオペランドのみを指定するべきという指針です。
この原則に従うとオプションを追加してもインターフェイスを変更する必要がありません。 -
割れた窓の法則
割れ窓理論をソフトウェア開発に当てはめた言葉です。
コードが清潔で美しく保たれている場合、開発者はソレを汚さないよう細心の注意を払うことになります。 -
ボーイスカウト・ルール
ボーイスカウトには「自分のいた場所は、そこを出ていくときは、来た時よりもキレイにしなければならない」というルールがあります。
小さな洗練を継続させることで、コードがよりよい方向へ向かっていきます。 -
UNIX哲学
UNIXの設計哲学です。
「1. Small is beautiful.」から始まる19の定理からなります。
拡張機能および外部ツール
-
ReSharper(年間¥15,000)
先進的なコード整形機能を提供します。
リファクタリング可能な部分をハイライトできます。
ダイアグラムでクラスの依存関係を可視化できます。 -
CodeMaid
コードを整形できます。
usingの整理やendregionタグの更新が便利です。
古いVisual Studioでもソリューションエクスプローラーのプロジェクトをすべて折りたためます。 -
StyleCop
コーディング規約にそった内容かチェックできます。
中括弧や空白の位置、改行といったコーディングのスタイルが対象です。
NuGetで取得できるStyleCopAnalyzersがおすすめです。 -
FxCop
ILを分析して、「クラスライブラリのデザインガイドライン」に沿っているかチェックできます。 -
Roslynator
非常に多くのチェックルールを追加できます。 -
Sandcastle Help File Builder(SHFB)
XMLコメントファイルからAPIドキュメントを作成できます。
NuGetで取得できるSHFBがおすすめです。 -
IntelliCode
IntelliSenseを強化したものとなります。 -
BuildVision
一つのソリューションに複数のプロジェクトがある場合に、ビルド結果が見やすくなります。 -
VSColorOutput
出力タブの結果が色付けされます。
参考資料
- MSDN .NET Framework3.5 名前に関するガイドライン
- MSDN C#のコーディング規則
- プログラマが知るべき97のこと
- 書籍: リーダブルコード -より良いコードを書くためのシンプルで実践的なテクニック-
- 書籍: Effective C# 6.0/7.0
- 書籍: More Effective c# 6.0/7.0
- 書籍: CODE COMPLETE 第2版 上・下 -完全なプログラミングを目指して-
- 書籍: 達人プログラマー 職人から名匠への道
- 書籍: プリンシプル オブ プログラミング -3年目までに身につけたい一生役立つ101の原理原則-
- 書籍: リファクタリング 既存のコードを安全に改善する
