xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

nameof演算子

これまでC# vNextの新機能についていくつか紹介してきましたが、個人的にラムダ形式プロパティよりも、null伝搬演算子よりも、catch/finally句で利用できるawait演算子よりもずっと ×2 興奮したのが今回取り上げるnameof演算子 (nameof operator) です。簡単に言うと、変数名や関数名などをサクッと取得するためのキーワードです。

とりあえず使ってみる

nameof演算子の利用方法は以下のような感じです。このように、非常にカジュアルに利用することができます。

var n1 = nameof(value);
var n2 = nameof(Console);
var n3 = nameof(Console.Title);
var n4 = nameof(Console.WriteLine);

//--- それぞれの変数の値
/*
n1 = value
n2 = Console
n3 = Title
n4 = WriteLine
*/

この機能により、これまで文字列ベタ書きに甘んじていた多くのコードが安全に記述できるようになります!PropertyInfoなどのリフレクションを駆使したコードや、Expressionを使ったコードがいくらか減る予感。超ハッピー!

以下にいくつかの例を載せてみました。他にもJSONの作成 (もはや自力の生成はほぼないとは思うけど) やデバッグメッセージの生成など、有効に使えるシチュエーション結構多いのではないかと思います。Visual Studio 14 CTP 3から利用できますので、ぜひ試してみてください!

例 1 : 引数チェック時の例外文字列

コンストラクタやメソッドでの引数チェックは頻繁に行われます。このとき、検証エラーが発生した場合にはエラーが出た引数名を文字列として例外クラスに渡すのが基本でした。引数名を変更する際に処理を忘れるというのはたまにやってしまうミスですが、これからはそんな問題も起こらなくなるでしょう。

class Person
{
    public string Name { get; } = name;
    public Person(string name)
    {
        if (name == null)
            throw new ArgumentNullException(nameof(name));  //--- 変数名の変更に追従可能 = コンパイル時にエラー検知可能!
          //throw new ArgumentNullException("name");        //--- これまでは文字列ベース
    }
}

例 2 : INotifyPropertyChanged

WPFやWindows Phone、WindowsストアアプリなどのXAMLアプリケーションを開発する際の重要な機構のひとつにデータバインディングがあります。特にINotifyPropertyChangedはその機構を支える重要なインターフェースですが、その実装は「変更されたプロパティの名前をイベント引数としつつ、イベントを発行する」なので、プロパティ名を文字列でベタ書きするか、ExpressionやCallerMemberName属性などを利用して取得していました。今回nameof演算子のおかげで少しだけ直観的になった気がします。

class PersonViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private string familyName = null;
    private string firstName = null;

    public string FamilyName
    {
        get { return this.familyName; }
        set
        {
            if (this.familyName == value)
                return;
            this.familyName = value;
            this.RaiseNotifyPropertyChanged(nameof(FamilyName));
            this.RaiseNotifyPropertyChanged(nameof(FullName));   //--- 連動するプロパティもこんな感じで!
        }
    }

    public string FirstName
    {
        get { return this.firstName; }
        set
        {
            if (this.firstName == value)
                return;
            this.firstName = value;
            this.RaiseNotifyPropertyChanged(nameof(FirstName));
            this.RaiseNotifyPropertyChanged(nameof(FullName));   //--- こんな感じ
        }
    }

    public string FullName{ get{ return string.Format("{0} {1}", this.FamilyName, this.FirstName); } }

    protected void RaiseNotifyPropertyChanged(string propertyName)
    {
        var handler = this.PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

例 3 : 依存関係プロパティ

その他、同じくXAMLアプリケーションで重要な依存関係プロパティにもプロパティ名を文字列で指定する箇所があります。こういったところでも変更に対する耐久性を高めることができるようになります。

class Person : DependencyObject
{
    public static readonly DependencyProperty NameProperty =
        DependencyProperty.Register
        (
            nameof(Name),  //--- もちろんコレも!
            typeof(string),
            typeof(Person),
            new PropertyMetadata("no name")
        );

    public string Name
    {
        get { return (string)this.GetValue(NameProperty); }
        set { this.SetValue(NameProperty, value); }
    }
}

逆コンパイル

nameof演算子がどのように展開されるのか逆コンパイルして確認してみます。

static void Main()
{
    var name = nameof(Console.WriteLine);
    Console.WriteLine(name);
}
static void Main()
{
    string name = "WriteLine";  //--- そのまま文字列リテラルに
    Console.WriteLine(name);
}

普通の文字列リテラルにそのまま置き換わっています。実行時解釈ではなくコンパイル時解釈のようです。