xin9le.net

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

ラムダ形式メンバーの拡張

2016/09/24 (土) の朝、Expression-Bodied Everything (= どこでもラムダっぽく) に関する Pull-Request が Roslyn の master ブランチにマージされました!

C# 6 ではプロパティとメソッドの 2 箇所についてラムダ形式のメンバーを記述することができましたが、それをさらに拡張していろんなところで使えるようにするというものです。

C# 6 の頃から「ラムダ形式」と書いているのでその名残でタイトルを付けましたが、「式形式 / 式っぽい / 式の体をした」と言うのかもしれません。ここら辺は日本語訳が正式には決まってないと思うので結構テキトーです...。

f:id:xin9le:20160927231108p:plain

新たに使えるようになる場所

C# 7 から増えたもの (= 2016/09/27 時点で動作するもの) は以下の 4 つです。

  • コンストラクタ
  • デストラクタ
  • プロパティのアクセサ
  • インデクサ

例えば以下のように書けます。

class Program
{
    //--- プロパティのアクセサ
    //--- get だけとか set だけもできる
    public string Property
    {
        get => "Getter Property";
        set => Console.WriteLine(value);
    }

    //--- インデクサも同様に
    public string this[int index]
    {
        get => $"{index} : Getter Indexer";
        set => Console.WriteLine($"{index} : {value}");
    }

    //--- コンストラクタ / デストラクタもこの通り
    public Program(string text) => Console.WriteLine(text);
    ~Program() => Console.WriteLine("Destructor");

    static void Main()
    {
        var x = new Program("Constructor");
        x.Property = "Setter Property";
        Console.WriteLine(x.Property);
        x[123] = "Setter Indexer";
        Console.WriteLine(x[123]);
    }
}

/*
Constructor
Setter Property
Getter Property
123 : Setter Indexer
123 : Getter Indexer
Destructor
*/

逆コンパイル

先の例を逆コンパイルしてみると以下のようになります。お分かりの通り、これまでの書き方をより簡易にするための糖衣構文でしかありません。

class Program
{
    public string Property
    {
        get { return "Getter Property"; }
        set { Console.WriteLine(value); }
    }

    public string this[int index]
    {
        get { return string.Format("{0} : Getter Indexer", index); }
        set { Console.WriteLine(string.Format("{0} : {1}", index, value)); }
    }

    public Program(string text)
    {
        Console.WriteLine(text);
    }

    ~Program()
    {
        Console.WriteLine("Destructor");
    }

    private static void Main()
    {
        Program x = new Program("Constructor");
        x.Property = "Setter Property";
        Console.WriteLine(x.Property);
        x[123] = "Setter Indexer";
        Console.WriteLine(x[123]);
    }
}

[おまけ] イベントへの対応

プロパティの get/set ができるのなら、(滅多に使われない) イベントの add/remove もできるのでは?と思った方もいらっしゃるかもしれません。ただ、残念ながら執筆時点 (2016/09/27) では csc.exe がクラッシュして死にます。Roslyn の実装を見ると add/remove にも対応してそうな雰囲気を感じるところもあるのですが...。

private EventHandler click;
public event EventHandler Click
{
    //--- 残念ながらコンパイラがクラッシュする
    add => this.click += value;
    remove => this.click -= value;
}

イベントビューアで確認するとスタックトレースはこんな感じで吐かれていました。

アプリケーション:csc.exe
フレームワークのバージョン:v4.0.30319
説明: ハンドルされない例外のため、プロセスが中止されました。
例外情報:System.InvalidOperationException
   場所 Microsoft.Cci.ReferenceIndexer.ProcessMethodBody(Microsoft.Cci.IMethodDefinition)
   場所 Microsoft.Cci.MetadataVisitor.Visit(Microsoft.Cci.ITypeDefinitionMember)
   場所 Microsoft.Cci.MetadataVisitor.Visit(System.Collections.Generic.IEnumerable`1<Microsoft.Cci.IMethodDefinition>)
   場所 Microsoft.Cci.ReferenceIndexerBase.Visit(Microsoft.Cci.ITypeDefinition)
   場所 Microsoft.Cci.MetadataVisitor.Visit(System.Collections.Generic.IEnumerable`1<Microsoft.Cci.INamedTypeDefinition>)
   場所 Microsoft.Cci.ReferenceIndexer.Visit(Microsoft.CodeAnalysis.Emit.CommonPEModuleBuilder)
   場所 Microsoft.Cci.MetadataWriter.CreateIndices()
   場所 Microsoft.Cci.MetadataWriter.BuildMetadataAndIL(Microsoft.Cci.PdbWriter, System.Reflection.Metadata.BlobBuilder, System.Reflection.Metadata.BlobBuilder, System.Reflection.Metadata.BlobBuilder, System.Reflection.Metadata.Blob ByRef, System.Reflection.Metadata.Blob ByRef)
   場所 Microsoft.Cci.PeWriter.WritePeToStream(Microsoft.CodeAnalysis.Emit.EmitContext, Microsoft.CodeAnalysis.CommonMessageProvider, System.Func`1<System.IO.Stream>, System.Func`1<System.IO.Stream>, Microsoft.Cci.PdbWriter, System.String, Boolean, Boolean, System.Threading.CancellationToken)
   場所 Microsoft.CodeAnalysis.Compilation.SerializeToPeStream(Microsoft.CodeAnalysis.Emit.CommonPEModuleBuilder, EmitStreamProvider, EmitStreamProvider, System.Func`1<System.Object>, Microsoft.CodeAnalysis.DiagnosticBag, Boolean, System.Threading.CancellationToken)
   場所 Microsoft.CodeAnalysis.CommonCompiler.RunCore(System.IO.TextWriter, Microsoft.CodeAnalysis.ErrorLogger, System.Threading.CancellationToken)
   場所 Microsoft.CodeAnalysis.CommonCompiler.Run(System.IO.TextWriter, System.Threading.CancellationToken)
   場所 Microsoft.CodeAnalysis.CSharp.CommandLine.Csc+<>c__DisplayClass1_0.<Run>b__0(System.IO.TextWriter)
   場所 Microsoft.CodeAnalysis.CommandLine.ConsoleUtil.RunWithUtf8Output[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]](System.Func`2<System.IO.TextWriter,Int32>)
   場所 Microsoft.CodeAnalysis.CSharp.CommandLine.Csc.Run(System.String[], Microsoft.CodeAnalysis.CommandLine.BuildPaths, System.IO.TextWriter, Microsoft.CodeAnalysis.IAnalyzerAssemblyLoader)
   場所 Microsoft.CodeAnalysis.CommandLine.DesktopBuildClient.RunLocalCompilation(System.String[], Microsoft.CodeAnalysis.CommandLine.BuildPaths, System.IO.TextWriter)
   場所 Microsoft.CodeAnalysis.CommandLine.BuildClient.RunCompilation(System.Collections.Generic.IEnumerable`1<System.String>, Microsoft.CodeAnalysis.CommandLine.BuildPaths, System.IO.TextWriter)
   場所 Microsoft.CodeAnalysis.CommandLine.DesktopBuildClient.Run(System.Collections.Generic.IEnumerable`1<System.String>, System.Collections.Generic.IEnumerable`1<System.String>, Microsoft.CodeAnalysis.CommandLine.RequestLanguage, Microsoft.CodeAnalysis.CommandLine.CompileFunc, Microsoft.CodeAnalysis.IAnalyzerAssemblyLoader)
   場所 Microsoft.CodeAnalysis.CSharp.CommandLine.Program.Main(System.String[], System.String[])
   場所 Microsoft.CodeAnalysis.CSharp.CommandLine.Program.Main(System.String[])

Expression-Bodied なイベントへの対応も提案としては出ているので、今後 (どういった書き味になるかはさておき) 対応が入るかもしれませんね。