構文:文字列にクラス名などを間違えないようにコーディングするには?[C# 6.0]:.NET TIPS
C# 6で追加されたnameof演算子を使うことで、クラス名/変数名/プロパティ名などを安全に文字列化できる。名前にまつわるバグを減らしてくれるうれしい機能だ。
対象:Visual Studio 2015(C# 6.0)以降
コード内に、クラス名や変数名などを文字列リテラルとして書くことがある。エラーメッセージに変数名を埋め込んだり、イベント通知にプロパティ名を含めたりする場合だ。ところが文字列では、タイプミスしていてもコンパイラはチェックしてくれない。名前を変えるときには修正漏れを起こしやすい。何とかならないだろうか? C# 6.0で導入されたnameof演算子を使えば、そんな悩みも解決する。本稿ではその使い方を解説しよう。
なお、本稿はC#で説明するが、Visual Studio 2015のVisual Basic(VB 14)でも利用できる。
nameof演算子の基本
nameof演算子を使うと、名前空間/型/メソッド/プロパティ/変数などの単純な名前(=名前空間やクラス名などで修飾されていない名前)の文字列が取得できる。次のコードに簡単な例を示す。なお、nameof演算子は、コンパイル時に文字列リテラルに置き換えられるので、実行速度に影響しない。
using System;
using static System.Console;
namespace SampleNamespace
{
class Sample{ public void SampleMethod() { } }
class Program
{
static void Main(string[] args)
{
// クラス名
WriteLine($"Sampleクラス:{nameof(Sample)}");
// ⇒ Sampleクラス:Sample
// 参考:クラス名のフルネームを取得するにはtypeofを使う
WriteLine($"Sampleクラスのフルネーム:{typeof(Sample).FullName}");
// ⇒ Sampleクラスのフルネーム:SampleNamespace.Sample
// メソッド名
WriteLine($"SampleクラスのSampleMethod:{nameof(Sample.SampleMethod)}");
// ⇒ SampleクラスのSampleMethod:SampleMethod
// オブジェクトのメソッド名も取得できる
var s = new Sample();
WriteLine($"SampleオブジェクトのSampleMethod:{nameof(s.SampleMethod)}");
// ⇒ SampleオブジェクトのSampleMethod:SampleMethod
// 変数は、その変数名が取得できる(変数の型名ではない)
WriteLine($"Sampleオブジェクトを持つ変数:{nameof(s)}");
// ⇒ Sampleオブジェクトを持つ変数:s
Action a = () => s.SampleMethod();
WriteLine($"delegate変数:{nameof(a)}");
// ⇒ delegate変数:a
// 名前空間も取得できる。ただし、末尾のみ
WriteLine($"System.Text名前空間:{nameof(System.Text)}");
// ⇒ System.Text名前空間:Text
// 参考:名前空間のフルネームは、typeofを応用すれば取得可能
var sbName = typeof(System.Text.StringBuilder).FullName;
var systemText = sbName.Remove(sbName.LastIndexOf('.'));
WriteLine($"System.Text名前空間:{systemText}");
// ⇒ System.Text名前空間:System.Text
#if DEBUG
ReadKey();
#endif
}
}
}
クラス名/メソッド名/変数名/名前空間名をnameof演算子で取得する例である。
なお、型名なしでWriteLineメソッドを呼び出す書き方については、「.NET TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]」を、また、文字列の先頭に「$」記号を付ける書き方は、「.NET TIPS:数値を右詰めや0埋めで文字列化するには?[C#、VB]」をご覧いただきたい。
INotifyPropertyChangedインタフェースの実装例
実際の使い方を、INotifyPropertyChangedインタフェースを実装するクラスで見てみよう。ここでは、プロパティの名前を取得するためにnameof演算子を使う。
まず従来の書き方から見ていこう。次のコードの「SampleClass」クラスは、2つのプロパティ名を文字列リテラルで記述している。例外のコンストラクタに渡す引数と、イベントを起動するときの引数だ。
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class SampleClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// PropertyChangedイベントを発火させるメソッド
protected void OnPropertyChanged(
[CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(propertyName));
}
// メンバ変数を変更し、PropertyChangedイベントを発火させるための、
// ジェネリックなメソッド
protected bool SetProperty<T>(ref T storage, T value,
[CallerMemberName] string propertyName = null)
{
if (Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
// 値を保持しているプロパティ
private DateTimeOffset m_dt;
public DateTimeOffset Date
{
get { return m_dt; }
set
{
if (value == DateTimeOffset.MinValue)
{
// 従来の書き方
throw new ArgumentOutOfRangeException("Date");
}
if (SetProperty<DateTimeOffset>(ref m_dt, value))
{
// DateStringプロパティにも変更があったと通知する
// 従来の書き方
OnPropertyChanged("DateString");
}
}
}
// 他のプロパティに依存している、読み取り専用のプロパティ
// 「Date」プロパティが変わると、この「DateString」プロパティも変わる
public string DateString
{
get { return m_dt.ToString("yyyy/MM/dd"); }
}
}
XAMLでUIを構築するアプリ(WPFアプリやUWPアプリなど)では、データバインディングのためにINotifyPropertyChangedインタフェースを実装したデータクラスを頻繁に作成する。そこでは、この例に示すように、プロパティ名を文字列リテラルで記述する場面も多い。そして厄介なことに、「OnPropertyChanged」メソッドに渡すプロパティ名を間違えていても、ビルドエラーにならないばかりか、実行時にもエラーにならない。ただデータバインディングが期待通りには動作しないだけなのだ。
なお、実際の開発プロジェクトでは、PropertyChangedイベント/OnPropertyChangedメソッド/SetProperty<T>メソッドだけを実装した基本クラスを作り、それを継承してそれぞれのデータクラスを書くようにするとよい。
また、OnPropertyChangedメソッド内に登場する「?.」という記述については、「.NET TIPS:構文:nullチェックを簡潔に記述するには?[C# 6.0]」をご覧いただきたい。
上のコードで「OnPropertyChanged」メソッドに渡しているプロパティ名「DateString」を書き間違えても、ビルドエラーにもならず、実行時エラーも出ない。このうっかりミスは、発見しにくいのである。また、例外のエラーメッセージに使われるプロパティ名「Date」の間違いも、発見しにくい。例外を発生させるテストはあまり頻繁には行われないものだし、テストから漏れることさえちょくちょくあるからだ。
nameof演算子を使って、次のコードのように「Date」プロパティのsetterを書き換えれば、もう間違えることはない。
public DateTimeOffset Date
{
get { return m_dt; }
set
{
if (value == DateTimeOffset.MinValue)
{
// 従来の書き方
//throw new ArgumentOutOfRangeException("Date");
// nameof演算子を使う
throw new ArgumentOutOfRangeException(nameof(Date));
}
if (SetProperty<DateTimeOffset>(ref m_dt, value))
{
// DateStringプロパティにも変更があったと通知する
// 従来の書き方
//OnPropertyChanged("DateString");
// nameof演算子を使う
OnPropertyChanged(nameof(DateString));
}
}
}
「Date」プロパティのみを示す。
nameof演算子を使えば、「Date」や「DateString」を書き間違えても、その場でVisual Studioが警告してくれる。もちろんビルドエラーにもなる。
まとめ
クラス名や変数名などを文字列リテラルとしてコーディングしなければならない場面では、できるだけnameof演算子を使おう。つまらないバグに悩まされずに済む。
利用可能バージョン:Visual Studio 2015(C# 6.0)以降
カテゴリ:C# 処理対象:言語構文
関連TIPS:Visual Studioでコンソール・アプリケーションのデバッグ実行時にコマンド・プロンプトを閉じないようにするには?
関連TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]
関連TIPS:数値を右詰めや0埋めで文字列化するには?[C#、VB]
関連TIPS:構文:nullチェックを簡潔に記述するには?[C# 6.0]
Copyright© Digital Advantage Corp. All Rights Reserved.