読者です 読者をやめる 読者になる 読者になる

miso_soup3 Blog

主に ASP.NET 関連について書いています。

null 条件演算子を式ツリーデータに変換することができない

C#

C# 6 の機能である null 条件演算子を式ツリーに入れようとしたら、図のようにコンパイルエラーが発生しました。メッセージは、「式ツリーのラムダに null 伝搬演算子を含めることはできません。」です。


f:id:miso_soup3:20160823162044p:plain


「ラムダ式から式ツリーの変換って何か制約あったけ?」
「どうして三項演算子での書き方(図の後者の記述)だとOKなのにこれはだめなんだろう?」
といろいろ分からなかったので整理しました。

調べた結果、このコンパイルエラーの原因は次のことが原因でした。

  • null 条件演算子は式ツリーデータに変換できない
  • null 条件演算子と例の三項演算子は、同じことではない
  • ラムダ式のすべてが式ツリーデータに変換できるわけではない
    • また、null 条件演算子を式ツリーデータに変換するように対応すれば幸せ、という話でもない

言葉について

ここまで画像と文章にいろいろな言葉がでてきますが、参考リンクを置いておきます。このページには、後述するエラーとなる原因について説明があります。

ラムダ式から式ツリーの変換って何か制約あったけ?

ラムダ式から式ツリーへの代入には制約があります。null 伝搬演算子もその制約の1つでした。

式ツリーにできるのは前者(1文だけのラムダ式)だけです。

そうなると、結構強い制約がかかってきます。 例えば、for, while, switch などの制御構文や、x = 0 といったような代入式は式ツリーにできません。 あと、インクリメント・デクリメントも、実質的には加減算+代入なので、式ツリーにできません。 また、ラムダ式内でローカル変数を定義できません。

ラムダ式 http://ufcpp.net/study/csharp/sp3_lambda.html より

制約がある、というのは、式ツリーデータを表す Expression 型のクラス(厳密には、Expression 型の派生クラス)が用意されていなければいけない、ということです。対応する Expression 型の派生クラスがないと、変換できません。また、Expression が登場した.NET Framework 3.5 の時点で存在する機能しか、ラムダ式から Expression 型への変換はできません。.NET 3.5 以降にも Expression の派生クラスは追加されていますが、“ラムダ式からExpression 型への変換”は対応していないようです。

では、どれが使えてどれが使えないか、を判断するにはどこを見ればいいかというと、○×表みたいなのは見つからず、式ツリー(Expression Trees) - C# によるプログラミング入門 | ++C++; // 未確認飛行 C このページ等を見るのが良いかなと思います。

null 条件演算子と例の三項演算子は、同じことではない

最初に、「どうして三項演算子での書き方だとOKなのにこれはだめなんだろう?」と書きました。これは分解すると、「三項演算子が式ツリーデータに変換できるなら、null条件演算子も式ツリーデータとして書けていいのでは?だって同じようなことしてるでしょ」ということになります(何て傲慢な)。

これに対する答えは、「同じじゃない」になります。三項演算子は、プロパティに2回アクセスしていますが、null条件演算子の方は、プロパティへのアクセスは1回のみです。

三項演算子:

hanako.Status == null ? false : hanako.Status.HasShield;

hanako.Status に2回アクセスする可能性がある。

null 条件演算子:

hanako.Status?.HasShield

コンパイル後のコードをC#で書くと次のようになる。

var status = hanako.Status;
if (status == null) { return null; }
return status.HasShield;

hanako.Status に1回アクセスし、変数に代入している。

このように処理の内容が違うため、「三項演算子での書き方だとOKならnull 条件演算子でもOK?」という意見は通りません。

また、ここでは式ツリーに変換できない理由も確認できます。ラムダ式から式ツリーへの変換の制約の1つに、複数行で書くようなブロックタイプは式ツリーに変換できない、というのがあります。null 条件演算子はこのブロックのうちに該当するため、式ツリーへ変換できません。

null 条件演算子を式ツリーデータに変換するように対応すれば幸せ、という話でもない

じゃあ、null条件演算子を Expression 型派生クラスに変換できるようにすればいいのでは?結構使いそう。 .NET 4 以降に追加されたという Expression 型派生クラスも、ラムダ式から式ツリーへの変換に対応すればいいのでは? と思います。が、現状はいろいろな理由があって採用されていないようです。

要望は既に出されていました。

CodePlexなとき:Null-propagating operator ?. in Expression Trees: spec v1
案としては、演算子用のExpression型派生クラスを作成する・既存のExpressionで組み立てられるようにする、があるようです。

Roslyn:Proposal: extend the set of things allowed in expression trees · Issue #2060 · dotnet/roslyn · GitHub

roslyn/2015-04-14 C# Design Meeting.md at master · dotnet/roslyn


問題としては、上記のページに

This operator could not be used in expression in .Net 3.5/4.0/4.5/4.5.1/4.5.1 because the class will not exist.
LINQ providers will need to be updated to take advantage of this operator.

とあります。Linq to SQL や既存ライブラリのことを考えると、簡単な話ではないよう。
null 条件演算子以外でも、ある機能を式ツリーサポートするか否かの議論がでています。

ちまみにこのコンパイルエラーがでてきたケースは、AutoMapper ライブラリや ASP.NET MVC の .cshtml の HTML Helper を使ったときです。

f:id:miso_soup3:20160823155224p:plain

Application Insights で Azure Web Apps のパフォーマンスカウンターを監視したいが…

Azure ASP.NET

こちらの記事にて、Azure Web Apps でパフォーマンスカウンターの値を Application Insights に送信できるとありましたので、ASP.NETで試してみました。もろもろを記録しておきます。

azure.microsoft.com

現状の確認

まず、上記の方法を試す前に、現状、パフォーマンスカウンター関連の項目をどのように確認できるか、見てみます。
Standard 1のApp Service Plan上に、Web Appsを立ち上げます。

Web Appsでは、インスタンスあたりのメトリックスや、CPU Time、Average memory working set等の項目を見られます。

f:id:miso_soup3:20160819193235p:plain
↑インスタンスあたりのメトリックス
f:id:miso_soup3:20160819185435p:plain
↑グラフで選択できる項目

App Service Planだと、「CPU Percentage」「Memory Percentage」「Disk Queue Length」等が見られます。

f:id:miso_soup3:20160819193548p:plain
↑グラフで選択できる項目

グラフで選択できる項目は、アラートにも設定できます。「インスタンスあたりのメトリックス」で確認できるインスタンス毎の値については、アラートには設定できません。

Application Insights

Application Insights を入れた場合のパフォーマンスカウンター関連の項目はどうでしょうか?
Web Apps と Application Insights を作成した後、Visual Studio から ASP.NET プロジェクト作成、NuGet で Application Insights 2.1.0 を追加しデプロイしました。

このとき、Application Insights のグラフの項目には、パフォーマンスカウンターの欄がありますが、Web Apps の場合は実際に「Process CPU」や「ASP.NET request execution time」が取得できるわけではありません。

f:id:miso_soup3:20160819193909p:plain

このことは、実行中の IIS Web サイトのパフォーマンスの問題の診断 Microsoft Azure のドキュメントにも書いてあります。

システム パフォーマンス カウンター IIS または Azure Cloud Services (Azure Web アプリは除く)

それでは、冒頭のサイトで説明された機能を試し、Application Insights 上で Web Apps のパフォーマンスカウンターの値が取れるか確認してみます。

Application Insights Performance Counters

試した方法は以下の通りです。

・Azure 上に Application Insights と Web Apps を作成する。
・Visual Studio で適当な ASP.NET プロジェクトを作成する。
ApplicationInsights-SDK-Labs こちらにある手順を参照し、Application Insights SDK Labs の Package をインストールできるよう設定する。
・NuGetで「Microsoft.ApplicationInsights」(version 2.2.0-beta1)をインストールする。
・プロジェクトの中のApplicationInsights.configに、InstrumentationKey(Application Insightsのキー)を設定する。
・Azure Web Apps にデプロイする。
・Azure Web Apps の Kudu から拡張機能「Application Insights」を設定する。

f:id:miso_soup3:20160819195434p:plain
https://****.scm.azurewebsites.net にアクセスし、Site extensions から Application Insights をインストールします。)

そうすると、Application Insights メトリックスエクスプローラーにて、以下の項目の追加を確認できました。

f:id:miso_soup3:20160819195742p:plain

\.NET CLR Exceptions(??APP_CLR_PROC??)\# of Exceps Thrown / sec
\ASP.NET Applications(??APP_W3SVC_PROC??)\Request Execution Time 
\ASP.NET Applications(??APP_W3SVC_PROC??)\Requests In Application Queue 
\ASP.NET Applications(??APP_W3SVC_PROC??)\Requests/Sec 
\Process(??APP_WIN32_PROC??)\% Processor Time 
\Process(??APP_WIN32_PROC??)\IO Data Bytes/sec 
\Process(??APP_WIN32_PROC??)\Private Bytes 


f:id:miso_soup3:20160819195922p:plain

サイトの画像のように、Performance counters欄にある「Processor time」の項目名として取得できるのかと思いましたが、上記の項目名として取得しているようでした。

これらの項目はアラートとしても設定できますが、すべて単位が「カウント」となっており、また設定してもアラートが飛んでこないので若干怪しいです。
f:id:miso_soup3:20160819200112p:plain

Custom

Application Insights Aggregate Metrics SDK TelemetryModule こちらを参考にし、
applicationinsights.configにカスタムカウンターを定義してみました。

  <Counters> 
      <Add PerformanceCounter="\Process(??APP_WIN32_PROC??)\Handle Count" ReportAs="Process handle count" /> 
      <Add PerformanceCounter="\Process(??APP_WIN32_PROC??)\Thread Count" ReportAs="Process thread count" /> 
    </Counters> 

が、ポータル上からはこれらの項目を確認できず…。何か設定が間違っているのかもしれません。


以上、いろいろ試してみましたが、理解不足もあり中々難しいです。まだBetaですし、今後に期待です。