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

miso_soup3 Blog

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

Markdig Markdown processor for .NET

Markdown C#

Implementing a Markdown Engine for .NET | xoofx

GitHub - lunet-io/markdig: A fast, powerful, CommonMark compliant, extensible Markdown processor for .NET


.NET 用の Markdown パーサー「Markdig」が良さげな感じです。

背景

今まで、.NET での Markdown パーサーとして MarkdownSharp や MarkdownDeep を使っていました。
ほぼ標準の Markdown の仕様( こことか)しか実装されておらず、
また保守も最近されておらず、GitHub や Codeplex 等のようなテーブルやコード等の表現は、 .NET では難しいものがありました。

Markdig

Markdig は、CommonMark の仕様を実装し、下の図の通り拡張機能が多数用意されています。
PHP Markdown Extra や、Pandoc、GitHub などの表現が取り込まれています。

f:id:miso_soup3:20160928143834p:plain
(この図は冒頭 GitHub の Readme ページのスクショです。)

使ってみる

下記のように記述すれば上のような拡張機能が使えます。

using System;
using Markdig;
//...
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
var result = Markdown.ToHtml("This is a text with some *emphasis*", pipeline);

サンプルコード Gist:https://gist.github.com/hhyyg/56325059768cc4a0997bfa049d410508

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