xin9le.net

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

ラムダ形式プロパティ

みなさんもGetterだけのプロパティを作った経験があるかと思います。例えば以下のようなものです。

using System.Math;
using System.Windows;

namespace CSharpVNext
{
    class Circle
    {
        public double Radius{ get; };
        public Point Origin{ get{ return new Point(0, 0); } }
        public double Area{ get{ return Radius * Radius * PI; } }
        public Circle(double radius)
        {
            this.Radius = radius;
        }
    }
}

しかし、上記のように特に1行で終わるようなシンプルな演算の場合にイチイチ「get」や「return」などと書くのは煩わしいですし、まして「{}」で2回も括らなければならないのはとても面倒と思うでしょう。そこに颯爽と登場するラムダ形式のプロパティ!

ラムダ形式プロパティ

ラムダ形式プロパティは以下のように記述します。定数値を返すこともできますし、他のプロパティを参照して値を算出した結果を返すこともできます。「get」も「return」も「{}」も何もない!たったこれだけでGetterプロパティを表現できるなんて超クール!

using System.Math;
using System.Windows;

namespace CSharpVNext
{
    class Circle
    {
        public double Radius{ get; };
        public Point Origin => new Point(0, 0);      //--- ラムダ式っぽく表現!
        public double Area => Radius * Radius * PI;  //--- 他のプロパティの参照もOK!
        public Circle(double radius)
        {
            this.Radius = radius;
        }
    }
}

また、ラムダ形式で記述できると言っても「式形式のラムダ式」のみが利用できます。「ステートメント形式のラムダ式」は利用できません。つまり、1行で記述できるものでなければならないという制約が付くということです。ステートメント形式を許可してしまうと戻り値がない (void型の) プロパティを許可することになってしまうので、式形式しか使えないというのは妥当ですね。

class Circle
{
    public double Radius{ get; };
    public Point Origin => new Point(0, 0);                 //--- OK
    public double Area => { return Radius * Radius * PI; }  //--- コンパイルエラー!
    public Circle(double radius)
    {
        this.Radius = radius;
    }
}

ラムダ式 (C# プログラミング ガイド)

ちなみに、この新機能は英語では「Expression-bodied properties」と呼ばれています。そのまま訳すると「式の体 (てい) をしたプロパティ」なのですが、散々悩んだ挙句勝手に「ラムダ形式プロパティ」と表現しました。この辺りはいつか日本語が決まるのではないかと思うので、それを待ちたいと思います。

コンパイル

先の例ですでに大体想像がついていると思いますが、例によって逆コンパイルもしてみました。予想通りですね。

using System;
using System.Runtime.CompilerServices;
using System.Windows;

namespace CSharpVNext
{
    class Circle
    {
        public double Radius
        {
            [CompilerGenerated]
            get{ return this.<Radius>k__BackingField; }
        }
        public Point Origin{ get{ return new Point(0.0, 0.0) } }
        public double Area{ get{ return this.Radius * this.Radius * 3.1415926535897931; } }
        public Circle(double radius)
        {
            this.<Radius>k__BackingField = radius;
            base..ctor();
        }
    }
}

ReactivePropertyが更に短く記述可能に (けどオススメしない)

そして我らが ReactivePropertyコンストラクタが膨らむことで有名なこの子ですが、以前に @okazuki さんが以下のような記事を投稿していました。

C# vNextで、ReactivePropertyはすっきり書けるのか?

自動実装プロパティ初期化子 では別のプロパティを参照することができないのでエラーになるという内容ですね。その通りです。しかし今回のラムダ形式プロパティを利用すれば以下のように書けます!

class Person
{
    public ReactiveProperty<string> Name{ get; } = new ReactiveProperty<string>("xin9le");
    public ReactiveProperty<string> UpperCaseName => Name
                                                    .Select(x => x.ToUpper())
                                                    .ToReactiveProperty();
}

ReactivePropertyはまたひとつ、新しい楽さを手に入れました。しかし先に説明した通り、ラムダ形式プロパティはGetter-onlyプロパティとして展開されるので、上記の実装はUpperCaseNameプロパティを呼び出す度に別インスタンスが取得されることになります。この点はコンストラクタで記述する既存の書き方と比べると良くない部分です。

class Person
{
    public ReactiveProperty<string> Name{ get; private set; }
    public ReactiveProperty<string> UpperCaseName{ get; private set; }
    public Person()
    {
        this.Name           = new ReactiveProperty<string>("xin9le");
        this.UpperCaseName  = this.Name
                            .Select(x => x.ToUpper())
                            .ToReactiveProperty();
    }
}

確かに良くないのはですが、ReactivePropertyは主にXAMLBinding と連携して使うもので、かつBindingはプロパティを1度だけ呼び出してインスタンスをキャッシュして利用する挙動をするので、ラムダ形式プロパティを利用してもこれまで通りの挙動をします。

とは言え、複数のBindingからこのプロパティにアクセスする場合や、Binding以外でもプロパティにアクセスする必要がある場合は無理せずこれまで通りに書くのが無難です。「できるようになった」というだけで実用的でないことは覚えておいてください