真・式の体を成して無い式を式の体を成すように式と式木で何とかする式

この投稿は「C# Advent Calendar 2015」の7日目の記事です。


朝、何となくスライドを読み直していたところ…

expression-tree-block昨日は「NGK2015B 名古屋合同懇親会2015忘年会」で式木のネタをLT登壇して発表してきました。この記事ではそれをブラッシュアップさせて公開しようと思っていたのです(そもそも5分LTでは、わかる人にしかわからない的なので)。で、改めてスライドを眺めていたところ…

あれ、 ( ゚д゚) … (つд⊂)ゴシゴシ

AndAlsoで繋げるとか言っておきながら、作る式木が「&&」で結合されてないじゃん? (;゚Д゚)

うーむ、間違ってる orz その辺りも含めてフォローしたいと思います…


式木とは

.NET 3.5で「Expression Tree」というライブラリが追加されました。C# 3.5ではこのライブラリを使用して、「式木」と呼ばれる機能をサポートしました。
式木とは、例えばC#で記述した式を実行時に解析出来るようなフレームワークです。とは言っても、C#のソースコード文字列を解析できるわけではなく、あくまで式の構造を表現するものです。例えば:

// 普通のラムダ式をデリゲートとして保持
Func<int, string> stringFunc = value => string.Format("Value = {0}", value);

// 例:デリゲートを実行
var result = stringFunc(123);
Debug.Assert(result == "Value = 123");

// ラムダ式を式木として保持
Expression<Func<int, string>> stringExpr = value => string.Format("Value = {0}", value);

// 例:ラムダ式の構造を調べる
Debug.Assert(stringExpr.Parameters[0].Name == "value");
Debug.Assert(stringExpr.Parameters[0].Type == typeof(int));
Debug.Assert(stringExpr.ReturnType == typeof(string));
var mcExpr = stringExpr.Body as MethodCallExpression;
Debug.Assert(mcExpr.Method.DeclaringType == typeof(string));
Debug.Assert(mcExpr.Method.Name == "Format");
Debug.Assert(mcExpr.Arguments[0].Type == typeof(string));

デリゲートと異なり、式木(Expressionクラス)としてラムダ式を受けるように書くことで、ラムダ式の構造を動的に探索することが可能となります。
また、以下のように、それを「コンパイル」して、実際に呼び出すことが出来ます。

// 式木を動的にコンパイルする
Func<int, string> compiledFunc = stringExpr.Compile();

// 出来上がったデリゲートを実行する
var compiledFuncResult = compiledFunc(123);
Debug.Assert(compiledFuncResult == "Value = 123");

わざわざ式木で受けておいて、動的にコンパイルするぐらいなら、初めからデリゲートで良いじゃないか!と思うかもしれませんが、逆なのです。式木を手で構築すれば、同じようにコンパイルが可能となり、高速実行可能なコードを動的に扱うことが出来ます。

// 式木を動的に構築する

// ラムダ式のパラメータに相当する式木
var pValueExpr = Expression.Parameter(typeof(int), "value");

// string.FormatメソッドのMethodInfo
var stringFormatMethod = typeof(string).
	GetMethod("Format", new[] { typeof(string), typeof(object) });

// ラムダ式の本体に相当する式木 「string.Format("Value = {0}", value)」
// Convertが必要なのは、引数の型はobjectなのでintから変換が必要なため(暗黙変換が行われない)
var bodyExpr = Expression.Call(
	stringFormatMethod,
	Expression.Constant("Value = {0}"),
	Expression.Convert(pValueExpr, typeof(object)));

// ラムダ式に相当する式木 「value => string.Format("Value = {0}", value)」
Expression<Func<int, string>> dynamicStringExpr =
	Expression.Lambda<Func<int, string>>(
		bodyExpr,
		pValueExpr);

// 式木を動的にコンパイルする
Func<int, string> compiledFunc = dynamicStringExpr.Compile();

// 出来上がったデリゲートを実行する
var compiledFuncResult = compiledFunc(123);
Debug.Assert(compiledFuncResult == "Value = 123");

そもそもの動機

前置きはこのぐらいで、要するに動的コード生成技術の一つの道具として、式木を使うことが出来ます。インタプリタ的に処理すると動作速度に難があるような場合、コンパイルして実行出来るというのは、非常に大きなアドバンテージです。内部ではILが生成されるので、C#で普通にソースコードを書いてコンパイルして実行したのとそん色ない速度を達成出来ます。

動的にコードを実行する手段として、Emitライブラリを使用してILで書くこともできますが、個人的なメリットとデメリットを挙げると:

式木(Expression Tree)

  • メリット:使用可能なCLRが多い。.NET 3.5以降・Silverlight・Silverlight for Windows Phone・Store App・Universal Windows Platform
  • デメリット:式木の論理構造について色々知っておく必要がある。.NET 3.5と.NET 4.0以降で機能面に差異がある。

IL by Emit (System.Reflection.Emit)

  • メリット:ILで許されるどんなコードでも書ける。ILのルールは比較的単純なので、ある意味習得は容易。新たなクラスやインターフェイス等も自由に定義できる。また、C#では書けないようなコードでも書ける。
  • デメリット:Emitが使えない環境が多い。特にStore AppとUniversal Windows Platformで使えないのが大きい。

Emitが使えないのは、本当に深刻な問題だと個人的には思っているのですが、こればっかりは仕方がない。こういうわけで、式木をメインに考えるのですが…

「.NET 3.5と.NET 4.0以降で機能面に差異がある」(本当はSliverlight for WP辺りにも同じような差がある)

という問題があり、特に.NET 3.5を切り捨てるかどうかで悩む局面がまだあります。3.5なんて古代文明だと言い切れれば無問題なんですが。それで、どうやって.NET 3.5と.NET 4.0の差を埋めるかと言う事を考えるわけです。


例1: Expression.Assignがない

問題点

Expression.Assignとは、代入式を示す式木の事です。

// 代入させたいフィールド
static int _fieldValue;

// ...

// 代入式の概念(C#ソースコードとしてコンパイルは不可)
Expression<Func<int, int>> assignExpr = value => _fieldValue = value;

var assignmentExpr = assignExpr.Body as BinaryExpression;
Debug.Assert(assignmentExpr.Type == typeof(int));
var fieldExpr = assignmentExpr.Left as MemberExpression;
Debug.Assert(fieldExpr.Member.Name == "_fieldValue");
Debug.Assert(fieldExpr.Type == typeof(int));
var valueExpr = assignmentExpr.Right as ParameterExpression;
Debug.Assert(valueExpr.Name == "value");
Debug.Assert(valueExpr.Type == typeof(int));

assignExprの行はコンパイルエラーになる(代入式を含むラムダ式はNGのため)ので、上記のコードは実行出来ませんが、仮に上記コードが成立した場合はこのように式木を探索できます。
C#のラムダ式としてコンパイル出来ないため、.NET 3.5でこの式木を表現可能にすることを見送ったのかもしれません。しかし、結局.NET 4.0では上記のようなコードをExpression.Assignを使って実現出来るようになりました。

// 動的に式木を構築すれば可能

// パラメータの式木
var pValueExpr = Expression.Parameter(typeof(int), "value");

// フィールドを示す式木
var fieldValueExpr = Expression.Field(null, typeof(Program).GetField("_fieldValue"));

// Expression.Assignを使って代入式を示す式木を作り、それをbodyにラムダ式木を生成
Expression<Func<int, int>> assignExpr =
	Expression.Lambda<Func<int, int>>(
		Expression.Assign(fieldValueExpr, pValueExpr),
		pValueExpr);

さて、これをどうやって.NET 3.5で実現するかという話ですが、Assignがない以上、どうやっても素で代入する式木は実現できません。また別の問題として、代入式には2種類あり、代入対象が「フィールド」なのか「プロパティ」なのかによって、生成されるはずのコードが大きく異なる事です。

フィールドへの代入は上記に示したような式で、最終的にはILのstfld命令に落ちます。しかし、プロパティの場合は(見えない)setterメソッドがあり、代入式と言いつつ実はsetterメソッドの呼び出しコードが生成されるのです。したがって、これらを分けて考えることにします。

フィールド代入のシミュレート

このような、.NET 3.5の式木で表現できないコードは、表現できるところでアウトソース的に実行させるようにすれば良いのです。フィールドの場合、「指定されたフィールドに値を代入させる」ようなメソッドがあったとして、そのメソッドを呼び出す式木を構築すれば実現できます。

このような戦略で考えたヘルパーメソッドが、以下のようなものです。

// フィールドに値を代入させるためのヘルパーメソッド
public static TValue FieldSetter<TValue>(out TValue field, TValue newValue)
{
	field = newValue;	// outなので、対象に代入される
	return newValue;	// ちゃんと値を返すことで、式の体を成す
}

このようなメソッドがあれば、次のようなコードで「間接的」にフィールドに値が代入できます。

// _fieldValue = 123; のシミュレート
var result = FieldSetter<int>(out _fieldValue, 123);

あとはこれを式木で表現します。

// FieldSetterのクローズジェネリックメソッド
var fieldSetterMethod = typeof(Program).
	GetMethod("FieldSetter").
	MakeGenericMethod(fieldValueExpr.Type);

// FieldSetterを呼び出す式木から、ラムダ式木を生成
var assignExpr =
	Expression.Lambda<Func<int, int>>(
		Expression.Call(fieldSetterMethod,
			fieldValueExpr, 	// outでもうまいことやってくれる
			pValueExpr),
		pValueExpr);

面白いのは、メソッド引数がoutパラメータであっても、式木上は特に何もしなくてもライブラリ内でよしなにやってくれることです。頭の中で考えていた時は、ここをどうするかが問題だなと思っていましたが、無問題でした。

プロパティ代入のシミュレート

プロパティへの代入が、実際にはsetterメソッドの呼び出しである事は前に述べたとおりです。では、それをそのままFieldSetterのように呼び出してしまえば目的が達成出来そうですが、厄介な問題があります。

// 代入させたいプロパティ
public static int TestProperty { get; set; }

// ...

// resultの値は?
var result = TestProperty = 123;
Debug.Assert(result == 123);

// プロパティのsetterメソッドの戻り値はvoidなので、以下はNG(疑似コード)
// (プロパティのsetterメソッド名は実際には異なる可能性があります)
var result = set_TestProperty(123);

上記のように、代入式の結果は、代入された値となるはずです。なので、Expression.Assignの結果はintの123になります。しかし、単にsetterメソッドを呼び出した場合、そのメソッドの戻り値はvoidであるため、異常な式木となってしまいます。

結局、ここでもヘルパーメソッドの登場となるわけです。

// プロパティに値を代入させるためのヘルパーメソッド
public static TValue PropertySetter<TValue>(Action<TValue> setter, TValue newValue)
{
	setter(newValue);	// デリゲートに設定させる
	return newValue;	// ちゃんと値を返すことで、式の体を成す
}

このヘルパーメソッドは、指定されたデリゲートを実行して、値を返します。フィールドの時のようにoutパラメータが使えれば良いのですが、プロパティメンバに対してoutパラメータを適用することは出来ないので、同じ方法では実現できません。

そこで、「プロパティに代入するラムダ式」を指定させ、デリゲートとして実行することで、間接的にvoid(を返す式木)の問題を回避しようというわけです。つまり:

// TestProperty = 123; のシミュレート
var result = PropertySetter<int>(innerValue => TestProperty = innerValue, 123);

// 更に生っぽく
var result = PropertySetter<int>(innerValue => set_TestProperty(innerValue), 123);

実はExpression.Lambda<T>は、FuncだけではなくActionデリゲートでも定義できます。つまり、値を返さない(void)なラムダ式を示す式木は作ることが出来ます。上記のように、PropertySetterの引数に指定するラムダ式の式木は作成できるのです。

// プロパティを示す式木
var testProperty = typeof(Program).GetProperty("TestProperty");
var testPropertyExpr = Expression.Property(null, testProperty);

// PropertySetterの引数に渡すラムダ式
// innerValue => set_TestProperty(innerValue);
var pInnerValueExpr = Expression.Parameter(typeof(int), "innerValue");
var setterExpr =
	Expression.Lambda(
		typeof(Action<>).MakeGenericType(testPropertyExpr.Type),
		Expression.Call(
			null,
			testProperty.GetSetMethod(false),	// プロパティのsetterメソッドを取得する
			pInnerValueExpr),
		pInnerValueExpr);

// PropertySetterのクローズジェネリックメソッド
var propertySetterMethod = typeof(Program).
	GetMethod("PropertySetter").
	MakeGenericMethod(testPropertyExpr.Type);

// PropertySetterを呼び出す式木から、ラムダ式木を生成
var assignExpr =
	Expression.Lambda<Func<int, int>>(
		Expression.Call(propertySetterMethod,
			setterExpr,
			pValueExpr),
		pValueExpr);

ちょっと式木として大きくなってしまいましたね。.NET 3.5ではパフォーマンスが低下する要因となるかもしれませんが、同じインフラが使える妥協点としては良いんじゃないでしょうか。


例2: Expression.Blockがない

.NET 4.0以降では、Assign同様、以下のようなブロックを含む式を「Expression.Block」を使って作ることが出来ます。

// ブロックを含む式の概念(C#ソースコードとしてコンパイルは不可)
Expression<Action<int, int>> blockedExpr = (a, b) =>
{
	Console.WriteLine("{0}+{1}", a, b);
	Console.WriteLine("{0}*{1}", a, b);
};

// 動的に式木を構築すれば可能

// パラメータの式木
var paExpr = Expression.Parameter(typeof(int), "a");
var pbExpr = Expression.Parameter(typeof(int), "b");

// Console.WriteLineのメソッド
var consoleWriteLineMethod = typeof(Console).
	GetMethod("WriteLine", new[] {typeof(string), typeof(object), typeof(object)});

// ブロック内のそれぞれの式
var innerExpr1 = Expression.Call(consoleWriteLineMethod,
	Expression.Constant("{0}+{1}"),
	Expression.Convert(paExpr, typeof(object)),
	Expression.Convert(pbExpr, typeof(object)));
var innerExpr2 = Expression.Call(consoleWriteLineMethod,
	Expression.Constant("{0}*{1}"),
	Expression.Convert(paExpr, typeof(object)),
	Expression.Convert(pbExpr, typeof(object)));

var blockedExpr = Expression.Lambda<Action<int, int>>(
	Expression.Block(
		innerExpr1,
		innerExpr2),
	paExpr,
	pbExpr);

さて、Blockがない.NET 3.5でこれをどう調理するか。

Expression.Blockがやる事

Blockは、n個の任意の式を順番に実行します。NGKのスライドでは、n個の式を逐次実行する方法として、それぞれの式が常にtrueを返す式と見なせたとすれば、Expression.AndAlsoを使って数珠つなぎにすることで、逐次実行が出来ることを示しました。

// それぞれの式が常にtrueを返すと仮定すれば、以下の式で逐次実行できる
var dummy = ((true && expr0) && expr1) && expr2;

ここの部分をTruenizeメソッドを呼び出すように書けばよい的に紹介したんですが、AndAlsoがどこにもない (;´Д`) 実際はこんな感じです。

// 結果を受け取るが、常にtrueを返すヘルパーメソッド
public static bool Truenize<TValue>(TValue result)
{
	return true;
}

// Truenizeを使って式の結果を常にtrue化し、それをAndAlsoで連結する
var dummy = ((true && Truenize(expr0)) && Truenize(expr1)) && Truenize(expr2);

しかし、スライドで示したTruenizeでも連結実行出来てますね… あれも方法としてはアリでしょう。多分風邪にやられてトチ狂った結果ってことで勘弁。

もう一つ、式が値を返さない(void)場合は、上記TruenizeではTValueがダメなので、PropertySetter同様、ラムダ式を使って間接的に実行させるTruenizeが必要になります。以下のような感じです:

// 結果を受け取るが、常にtrueを返すヘルパーメソッド
public static bool Truenize(Action action)
{
	// デリゲートを実行
	action();
	return true;
}

// Truenizeを使って式の結果を常にtrue化し、それをAndAlsoで連結する
var dummy = ((true && Truenize(() => expr0)) && Truenize(() => expr1)) && Truenize(() => expr2);

非ジェネリックTruenizeでデリゲートを実行させて、voidな式木をラムダ式として実行させるようにします。実際の実装はPropertySetterと同様なので省略。

スコープセパレーション

もう一つの重要な特徴も忘れてました(やばい、これかなり大きなトピックだった)。それは、「スコープを狭める役割」もある事です。ブロック内で宣言された変数はブロック外では使えません。以下は疑似コード:

// 外側のスコープ
var localVariable = 123;

{
	// 内側のスコープ(実際にはコンパイル不可)
	var localVariable = 456;
	Debug.Assert(localVariable == 456);
}

// 影響ない
Debug.Assert(localVariable == 123);

スコープの問題は、式木全体としてちゃんと考えれば影響ないかもしれませんが、式木がどのように組み立てられるか分からない場合は、相互に影響を与えなくてすむ方法があれば、それに越したことはありません。

これのシミュレートはかなり悩みました。色々考えた結果、同じようにスコープを制限しているという特徴を利用したらどうか、と言うものです。それは何か? ラムダ式ですよ。

// ラムダ式の「引数」は、スコープ内のローカル変数と見なせる(疑似コード・実際にはコンパイル不可)
Action<int> localBlockExpr = localVariable =>
{
	// 外側に影響を与えない
	localVariable = 456;
	Debug.Assert(localVariable == 456);
};

// 外側のスコープ
var localVariable = 123;

// デリゲートを実行
localBlockExpr(default(int));

// 影響ない
Debug.Assert(localVariable == 123);

ラムダ式を使ってラップすることで、引数をローカル変数としてみなし、スコープを強制することが出来ます。これと同じことを動的に式木を組み立てて実現すれば良いわけです。但し、まだ問題もあります。上記のままだと、スコープ内のローカル変数が増えると、Actionデリゲートのジェネリック引数を増やす必要があり、実装上も上限的にも辛い香りが漂います。代替案として思いつく方法は:

// ローカル変数としての受け皿を保持するクラスを動的に生成する(疑似コード)
public sealed class LocalVariableClosure
{
	public int localVariable1;
	public int localVariable2;
	public int localVariable3;
	// ...
}

// これを引数にする
Action<LocalVariableClosure> localBlockExpr = localVariableClosure =>
{
	// LocalVariableClosureをクロージャ的に扱う
	localVariableClosure.localVariable1 = 111;
	localVariableClosure.localVariable2 = 222;
	localVariableClosure.localVariable3 = 333;
	// ...
};

// LocalVariableClosureを生成して渡す
localBlockExpr(new LocalVariableClosure());

アイデアとしては良いんですが、お手付きですね。LocalVariableClosureクラスを「動的」に作るには、Emitが必要です。そんなわけで、Emitしなくても同じような事が出来ないか頭をひねるとこうなります:

// object配列の引数を渡し
Action<object[]> localBlockExpr = localVariables =>
{
	// 配列の各要素をローカル変数と見なす
	localVariables[0] = 111;
	localVariables[1] = 222;
	localVariables[2] = 333;
	// ...
};

// object配列を生成して渡す(要素数が分かる必要がある)
localBlockExpr(new object[3]);

しかし、まだ足りない。上記コードのように配列に代入したり参照したりするためには、objectとの型の変換が常に必要になります。式木として扱う場合、式木の各要素は基底クラスの「Expressionクラス」から継承しているわけですが、この代入を成立させるには型変換(Convert)も式木に盛り込まなければなりません。また、その変換は左辺側の式木内で吸収出来る必要があります。段々気絶級になってきた…

// object配列の引数を渡し
Action<object[]> localBlockExpr = localVariables =>
{
	// 型変換(Expression.Convert)が必要。しかも本当は左辺での実現が必要
	localVariables[0] = (object)111;
	otherVariable = (int)localVariables[0];
};

# C++なら参照使って左辺で表現出来るんですが…

結論の前に、ローカルスコープ内の式木を、例1のフィールドへの代入式の例で表現するとこうなります:

// ローカル変数パラメータ
var localVariablesExpr = Expression.Parameter(typeof(object[]), "localVariables");

// 代入式
var assignerExpr = Expression.Call(fieldSetterMethod,
	fieldValueExpr,
	Expression.Convert(	// どう頑張っても右辺でしか表現できない
		Expression.ArrayIndex(
			localVariablesExpr,
			Expression.Constant(0)),
		typeof(int)));

// ラムダ式にしてテスト
var testExpr = Expression.Lambda<Action<object[]>>(
	assignerExpr,
	localVariablesExpr);
var compiledAction = testExpr.Compile();

var testLocalVariables = new object[] {123};
compiledAction(testLocalVariables);
Debug.Assert(_fieldValue == 123);

Blockのシミュレートアイデアとしてはこれで全てです。が、これを使って.NET 3.5でも.NET 4.0でも同じように記述可能にするには、Expressionクラスをラップするエミュレーションレイヤーが必要になります。特に左辺をローカル変数式木としてだまして(.NET 4.0ではExpression.Variable)置きながら、実際に生成される式木では配列参照とConvertを挟む必要があるため、ユーティリティメソッドレベルの対応ではたぶん無理だろうと考えました。

そんなわけで、上記の全てを組み合わせたコードはあまりにも大きいため、ここでは示しません。エミュレーションレイヤーとしてExpressionクラスを再定義した例を以下のコードに示します。

.NET 3.5での実装:
CenterCLR.ExaSerializers/ExpressionCompatibilityLayer_Strict.cs

.NET 4.0での実装:
CenterCLR.ExaSerializers/ExpressionCompatibilityLayer_Extended.cs

文中のサンプルコードはここ:
CenterCLR.ExpressionTreeCompatibility


まとめ

.NET 3.5の式木サポートで不足しているものは他にも色々あると思います。ループとかもそうですね。.NET 3.5に欠けているものは、C# 3.0の式木として受け入れられないもの、のように思います。そこから慎重にドリルダウンすることで、シミュレートするアイデアが得られるのではないかなと思います。とはいえ、やってみて分かったのは、想像以上に沼地だったと言う事です ( ´Д`)=3 フゥ

最後にサンプルで紹介したコードは、高速バイナリシリアライザです。テストはかなり通してあるので、興味があればいじってみて下さい。特に逆シリアル化でストリーミング出来るようにしたのはイケてると、勝手に思ってます。

GitHub: CenterCLR.ExaSerializers

次はvarmilさん、よろしくお願いします!

式の体を成して無い式を式の体を成すように式と式木で何とかする式 – NGK2015B 名古屋合同懇親会2015忘年会

WP_20151205_14_03_21_Pro_LI年末ラッシュの季節です。NGK2015B 名古屋合同懇親会2015忘年会で、「式の体を成して無い式を式の体を成すように式と式木で何とかする式」の題目で登壇してきました。

NGKは忘年会なので、ネタが多くて面白いです。全セッション完全5分LT形式です。

「高校生がプログラミングを三年間で学んだ軌跡」とか「リアル波動拳をつくってみました」とか、爆笑しました (*´Д`)

今年も気の利いたネタを披露したかったのですが、秋口から体調が優れず、普通ネタを高速展開するという、いつか見たアレになってしまった…

.NET 3.5環境で、.NET 4以降で使えるExpression Treeのシミュレートをしようという内容です。
パズルっぽい感じで何とかなった感あります。ただし、すべてのExpression Tree Nodeがシミュレートできるのかどうかはわかりません。

オリジナルスライドはこちら:式の体を成して無い式を式の体を成すように式と式木で何とかする式.pptx


この記事には、まとめなおした完全版があります。参考にどうぞ:「真・式の体を成して無い式を式の体を成すように式と式木で何とかする式」

Final LINQ Extensions III – 第四回 Center CLR 勉強会

finallinqextensions3第四回Center CLR勉強会で、Final LINQ Extensions IIIのタイトルで登壇してきました。

ご静聴ありがとうございます。
式木と並列化の話をしました。ようやく、ここに完結です!!
初回から参加していない方は、是非初回スライドから見て下さい。入門的な内容から網羅しています。

Final LINQ Extensions – 第二回Center CLR勉強会
Final LINQ Extensions II – 第三回Center CLR勉強会


オリジナルスライドはこちら: Final LINQ Extensions III.pptx
GitHub: CenterCLR.CustomLINQProviderDemo


11875075_746804792098116_7791419725665778286_o今回のCenter CLRは参加人数も20名を超え、何だか大所帯を感じました。ディスカッションをメインにしているので、あんまり多いとアレなんですが、それはそれとして嬉しい限り。

がりっちさんのUWPの細部の話・可知さんのVB14の知らなかった機能・松岡さんの.NET MicroFrameworkとWindows 10 IoTの選択の話・そして、平内さんの


女子高生とLINEでお友達になるにはとんでもなく敷居が高い件

など、主催しておきながら凄く面白いネタで楽しかったです。

また次回もよろしくお願いします。

Final LINQ Extensions II – 第三回 Center CLR 勉強会

finallinqextensions2第三回Center CLR勉強会で、Final LINQ Extensions IIのタイトルで登壇してきました。

ご静聴ありがとうございます。
前回網羅できなかった、細かい話の落穂広いと、標準の演算子を一通り説明しました。
標準演算子が使いこなせるようになると、LINQでの実装がはかどる事間違いなしですよ。

にしても、あーあ、やっちまいました (;´Д`) 次回もよろしくお願いします!!


オリジナルスライドはこちら: Final LINQ Extensions II.pptx

Final LINQ Extensions – 第二回 Center CLR 勉強会

第二回 Center CLR 勉強会にて、「Final LINQ Extensions」というタイトルで、登壇してきました。

Center CLR二回目で、一回目から参加された方も、今回初めて参加された方も、ありがとうございました。

FinalLINQExtensions
LINQ集大成ってことで、LINQ to Object限定ですが、入門的内容から発展ネタまで展開しようと思いました。が、最初にアジェンダを書いて、それを何とか詰め込もうとしたのですが、どうしても時間が足りない! orz という事で、次回に続編やります。

  • ループと分岐処理からの脱却
  • 構造的な値への適用
  • 初歩的な演算子
  • 実践的なforeachの置き換え
  • 列挙子とは
  • 演算子を作る
  • パフォーマンスの改善

次回もよろしくお願いします!

オリジナルスライドFinal LINQ Extensions.pptx

列挙可能から完全なるモノまで – IEnumerableの探索 – C# Advent Calendar 2014

この投稿は、C# Advent Calendar 2014 の14日目の記事です。

IEnumerableにまつわるアレやアレ

こんにちは! いつもはAsyncとLINQの事をしゃべったり書いたりしています。「列挙可能から完全なるモノまで – IEnumerableの探索」というお題で書いてみようと思います。

.NETでは、LINQにまつわるインターフェイスがかなり多く含まれています。その中でも、IEnumerableインターフェイスの継承グラフに存在する様々なインターフェイスを、どのように使い分けるべきか、と言うのが分かりにくいかも知れません。沢山のインターフェイスが定義されているのは、歴史的な事情もあります。

LINQ to ObjectやLINQ to Entitiesなど、一般的に使用されているLINQの背景には、「列挙可能である」という性質があります。この事は、以前にLINQの勉強会でも取り上げましたが、もっと分かりやすく掘り下げたいと思います。


Back to the IEnumerable – .NET 1.0

LINQにおいて「列挙可能」であるとは、つまり「IEnumerable<T>」インターフェイスを実装している事、という事です。例えば、次のような簡単なLINQを考えてみます。

// データを格納するintの配列
int[] sourceDatas = new int[] { 123, 456, 789 };

// データ配列から、偶数の値を抽出する
IEnumerable<int> evens = sourceDatas.Where(data => (data % 2) == 0);

.NETの配列は、System.Arrayクラスを暗黙に継承しています。このリファレンスを見ると、以下のようなインターフェイスを実装していることが分かります。

// (無関係なインターフェイスは除外)
public abstract class Array : IList, ICollection, IEnumerable

これを図示すると、こういう事です。

enumeration1

  • IEnumerableインターフェイスは、「列挙可能」である事を示します。「示す」と言っても、ただ表明しているわけではありません。列挙可能にするための「GetEnumerator」メソッドを実装しています。ここではその詳細に踏み込みませんが、C#の「foreach」を使用する時、C#のコンパイラは暗黙に「GetEnumerator」メソッドを呼び出して、要素の列挙を実行します。そのため、「IEnumerable」を実装している=列挙可能、と見なせるわけです。
    だから、配列に対して、直接foreachが使えます。
    (厳密には異なるのですが、このように解釈していて問題ありません)
  • ICollectionインターフェイスを実装しているクラスは、要素数を把握可能である事を示します(他にも機能はあるのですが、省略)。要素数は、皆さんおなじみの「Count」プロパティで取得できます。
    ICollectionはIEnumerableを継承しています。そのため、ICollectionにキャスト出来た場合は、勿論「列挙が可能」と言う事になります。
  • IList インターフェイスは、リスト形状のコレクションに対する、追加・変更・削除も含めた、全ての操作が可能である事を示します。このインターフェイスは、ICollectionを継承しているので、要素数の把握も可能(つまり、Countプロパティが使える)で、更にIEnumerableも継承していることになるので、列挙も可能です。

さて、これらのインターフェイスが登場したのは、.NET 1.0、つまり最初から存在するインターフェイスです。気づいたかもしれませんが、これらのインターフェイスには、要素の型が含まれていません。例えば、IEnumerableインターフェイスで列挙しようとすると、System.Objectが要素の型となります。これでは列挙した後、適切な型へのキャストを行わなければなりません。

実際、「foreach」を使用して列挙する場合、ループ変数の型を明示的に指定する事で、暗黙にキャストしていました。意味的には以下のようになります。

// .NET 1.0世代での列挙

// 明示的にIEnumerable型の変数に代入
IEnumerable sourceDatas = new int[] { 123, 456, 789 };

// foreachでループ(一般的な記述)
foreach (int value in sourceDatas)
{
  // ...
}

// 実際にはこういう事
foreach (object untypedValue in sourceDatas)
{
  int value = (int)untypedValue;

  // ...
}

ところで、配列の基底型であるSystem.Arrayが、IListインターフェイスを実装しているのはおかしいと思うかもしれません。配列の要素数は、一度生成されたら「不変」な筈です。そのため、ICollectionインターフェイスの実装までが妥当と思われますが、そうなっていません。実際、配列のインスタンスをIListインターフェイスにキャストし、Addメソッドを呼び出して要素を追加しようとしても、例外がスローされます。

このような設計になっている理由は公式には明らかになっていないと思いますが、IListインターフェイスには「インデクサ」が定義されています。このインデクサを使用可能にするため、敢えてIListインターフェイスを実装しているのではないかと推測しています。

(しかし、結局これは誤った設計だったと個人的には思っています。理由については後述)


ジェネリック引数で要素の型を伝搬 – .NET 2.0

上図では、Arrayを継承してintの配列を生成しています。Arrayは列挙可能であったり、要素数の把握が可能と言う事になっています。しかし、その要素の型はSystem.Objectであり、intへのキャストが必要である事は説明した通りです。では、配列を対象とした場合、IEnumerable<int>のような、要素型が明示されたインターフェイスでは使用出来ないという事でしょうか?

enumeration2

そんな事はありません。.NET 2.0でジェネリック型がサポートされ、このように配列の実装インターフェイスが拡張されました。「Array<T>」クラスが導入されたわけではなく、配列を定義すると自動的に「IEnumerable<T>」インターフェイス「ICollection<T>」インターフェイス「IList<T>」インターフェイスが、直接配列の型に実装されます。

「IEnumerable<T>」は「IEnumerable」も継承しているので、根本的な「列挙可能」という性質は受け継いでいると言えます。しかし、「ICollection<T>」は「ICollection」を継承しておらず、「IList<T>」も「IList」を継承していません。

この理由についても推測するしかありませんが、「ICollection<T>」は設計上、ICollectionのジェネリックバージョンではないと考えられます。ICollectionには存在しない「Add」等の要素数の変化を伴う操作メソッドが追加されています。「IList」があまりに多くの役割を担い過ぎている事への反省でしょうか。

しかし、これが原因で、ICollectionやIListの対比が分かりにくくなっています。IList<T>を実装するクラスを見た場合、暗黙的にIListも実装しているのではないか?と考えてしまいますが、そうなっていません(ここで示した配列や、List<T>クラスは、わざわざIList「も」実装する事で、この問題に対処しています)。

それにしても、普段コードを書いていると、「ICollection<T>」ほど「触らない」インターフェイスも珍しいです。一番の問題はやはり「インデクサ」が定義されていない事で、わざわざICollection<T>を操作する機会がほとんどありません。対照的に、「IList<T>」は良く使いました。


LINQの登場 – .NET 3.5

さて、このようなコレクション操作の抽象化を行う下積みがあり、とうとうLINQが登場します。LINQは、IList<T>のような高機能なインターフェイスではなく、List<T>クラスのような個々のコレクションクラスへの直接拡張でもなく、「IEnumerable<T>」インターフェイスだけを要求してきました。

ここに「拡張メソッド」のサポートが加わり、「列挙可能な要素群」を包括的に操作可能となったのです。LINQが.NETやC#と切り離せない関係にあるのは、ここで改めて説明するまでも無いでしょう。

IEnumerable<T>しか要求しないので、ここまでに解説したほとんどのインターフェイスは、実装さえしていれば即LINQで応用が可能と言う事になります。IEnumerable・ICollection・IListの「非ジェネリック」版インターフェイスについては、残念ながらLINQでそのまま使う事が出来ません。大きな問題は、列挙可能な要素の「型」が分からない事です。

しかし、型を明示的に与えれば、LINQ演算子を適用する事が出来ます。

// 明示的にIEnumerable型の変数に代入
IEnumerable sourceDatas1 = new int[] { 123, 456, 789 };

// LINQで使うには、Cast演算子を使って型を明示すればよい
IEnumerable<int> evens1 = sourceDatas1.Cast<int>().Where(data => (data % 2) == 0);

// 様々なインスタンスが入っている状態
IEnumerable sourceDatas2 = new object[] { 123, "ABC", 456, Guid.NewGuid(), 789 };

// 何が入っているか不明な場合は、OfType演算子を使って型で絞り込みを行う事も可能
// (指定された型以外のインスタンスが入っていた場合は、そのインスタンスは無視される)
IEnumerable<int> evens2 = sourceDatas2.OfType<int>().Where(data => (data % 2) == 0);

IEnumerableは、列挙される要素の型が分からないため、必ず想定されるインスタンスが取得されるとは限りません。OfType演算子はそのような場合に使用することが出来ます。Cast演算子を使用しながら、異なる型が取得された場合は、例外がスローされます。

今でも、古いWindows Formsクラスのコレクションで、IEnumerableやIListだけが実装されているクラスがあります。それらのインスタンスに対しても、この演算子を使えばLINQで操作可能となります。

.NET 3.5の世代では、一連のインターフェイスに新たに付け加えられたインターフェイスはありませんでした。次の拡張は.NET 4.5を待つ事になります。


読み取り専用とインデクサ – .NET 4.5

enumeration3

.NET 4.5がリリースされ、突如として「列挙可能」インターフェイスに新顔が追加されました。それが「IReadOnlyCollection<T>」インターフェイス「IReadOnlyList<T>」インターフェイスです。

名前から想像出来るように、これらのインターフェイスは、要素の読み取りのためのインターフェイスで、IReadOnlyCollection<T>は要素数「だけ」が取得可能、IReadOnlyList<T>は読み取り専用インデクサが定義されています。

このインターフェイスを初見した時の率直な感想としては、「遅すぎた」でした。.NET 1.0世代で書きましたが、すでにこの時「読み取りしか行わないけど、インデクサが欲しい」という状態は露呈していました。世代が進んで初めて必要となったのであれば分かるのですが、.NET 1.0においても既に分かっていた事のように思います。理由として、「IsReadOnly」プロパティの存在が挙げられます。このプロパティを追加する際に、当然IReadOnlyCollectionやIReadOnlyListを検討する事も可能だった、と思うと残念なのです。

残念な出自もさることながら、残念な現実もあります(だから余計に残念)。今更、ICollection<T>やIList<T>インターフェイスの継承階層の適切な位置に、これらの新しいインターフェイスを潜り込ませる事は「出来ません」。そんな事をすれば、既存のICollection<T>やIList<T>等のインターフェイスを実装しているクラスが破綻してしまいます。インターフェイスは契約の一種です。後から変更されるとバイナリ互換性が失われます。

.NET内部ではこのインターフェイスをどう扱っているのでしょうか? 図に示したように、新しいインターフェイスは配列の具象型が「直接」実装する事になります。これまでのインターフェイスとは全く融合せず、唯一IEnumerable<T>を継承する事で「型明示で列挙可能」である事は表明しています。同様に、List<T>クラスも、クラスが直接新しいインターフェイスを実装する事で、互換性を失わないようにしています。


メモ:先人を擁護するなら、.NET 1.0の頃はまだJavaとの対比が強く、「マルチ言語」に強くこだわっていました。インデクサという機能は、様々な言語で再現出来ない可能性があり、C#やVB.net特有の機能とみなされており、IL上でも特別な扱いを受けていました。そのような機能を、mscorlib内のスタンダードなインターフェイスとして定義するのははばかられたのかも知れません。
ぶっちゃけ、素でインデクサを表現できないなら、「Item」メソッドで良いじゃん?と思わなくもないんですがw


インターフェイスバリエーションの物理的な側面

IEnumerableにまつわるインターフェイスの歴史的な側面を挙げてきました。物理的な側面についても触れておきます。

多くのインターフェイス定義の、どれを選択するのか?

単に列挙するだけならIEnumerable<T>、リストの参照や操作を行う場合はIList<T>型を指定させ、意図の違いをコード上に表現します。

// 与える引数は列挙しかしないので、int[]ではなく、IList<int>ではなく、IEnumerable<int>として意図を推測しやすくする
public static int SumEvens(IEnumerable<int> datas)
{
  // (わざとベタアルゴリズムです)
  int result = 0;
  foreach (var data in datas)
  {
    if ((data % 2) == 0)
    {
      result += data;
    }
  }
  return result;
}

// 配列の指定はOK
int[] sourceDatas = new int[] { 123, 456, 789 };
SumEvents(sourceDatas);

// List<int>の指定もOK
List<int> sourceDatas = new List<int> { 123, 456, 789 };
SumEvents(sourceDatas);

基底となるインターフェイスで引数を定義する、と言うのは意図の明確化でもあり、応用性を高める点でも重要です。上記の例では、引数にはあらゆる「列挙可能」なインスタンスを指定する事が出来ます。しかし、仮に具象クラスや具象クラスに近いインターフェイスを指定した場合はどうなるでしょうか。

// IList<int>を指定した場合
public static int SumEventsFromIList(IList<int> datas) { ... }

// 配列の指定はOK
int[] sourceDatas = new int[] { 123, 456, 789 };
SumEventsFromIList(sourceDatas);

// List<int>の指定もOK
List<int> sourceDatas = new List<int> { 123, 456, 789 };
SumEventsFromIList(sourceDatas);

// しかし、内部では列挙しているだけなのだから、IList<T>である必要はない。
// そして、IList<T>で渡していると、リストが書き換えられる可能性がある、ように見える。

//////////////////////////////////////

// 配列を指定した場合
public static int SumEventsFromArray(int[] datas) { ... }

// 配列の指定はOK
int[] sourceDatas = new int[] { 123, 456, 789 };
SumEventsFromArray(sourceDatas);

// List<int>の指定はNG
List<int> sourceDatas = new List<int> { 123, 456, 789 };
SumEventsFromArray(sourceDatas);   // 型不一致エラーでコンパイル出来ない

//////////////////////////////////////

// List<int>を指定した場合
public static int SumEventsFromList(List<int> datas) { ... }

// 配列の指定はNG
int[] sourceDatas = new int[] { 123, 456, 789 };
SumEventsFromList(sourceDatas);   // 型不一致エラーでコンパイル出来ない

// List<int>の指定はOK
List<int> sourceDatas = new List<int> { 123, 456, 789 };
SumEventsFromList(sourceDatas);

大したことではない、と思うかもしれません。一年後、コードのメンテナンスをしなければならない時に、内部実装を読む事無く、すばやくメソッドの「意図」を把握出来るのはどちらでしょうか? IEnumerable<T>のように、より制限された型を指定しておくと、そのような負担から解放され、少しでも楽になります。

.NET 4.5以上であれば、IReadOnlyList<T>も使い分けの対象に入れると良いでしょう。このインターフェイスはまだまだ実装クラスが少ないですが、見ると安心感が違います(少なくともIList<T>を引数に渡す時には、書き換えの可能性が頭をよぎります)。

共変性と読み取り専用インターフェイス

読み取り専用として定義されたインターフェイスが残念な事になっているのは説明した通りです。このインターフェイスがわざわざ遅れて導入された理由と思われるものとして、要素型の「共変性」があります。共変性については「ジェネリクスの共変性・反変性 – 未確認飛行 – ++C++;// 未確認飛行 C」が詳しいので参照してください。

読み取り専用であれば(或は、読み取り専用として定義する事により)、インスタンスの代入互換性が高まるという事が趣旨にあります。ちなみに、この共変性の性質は.NET 1.0からありました。配列についてだけは、要素型の暗黙的なキャストが成立する場合において、代入互換があることになっていました。

// stringの配列
string[] values = new string[] { "ABC", "DEF", "GHI" };

// objectの配列は、要素型がstring→objectへの暗黙のキャストが成立するので代入可能(暗黙の共変性・但し、値型は不可)
object[] objectValues = values;

実行時判定

今までの話は、全てコンパイル時に型を特定する「型安全」についてフォーカスしたものです。LINQのパフォーマンスについての疑念の話をしましたが、実際にはより最適なアルゴリズムを採用するために、実行時のインスタンスの型を調査しています。

去年書いた、Advent LINQ:Countや、Advent LINQ:ElementAtのように、実行時に様々なインターフェイスにキャスト可能かどうかを判定して、より効率の高いアルゴリズムを採用する、という手法です。

Countの例はIReadOnlyCollection<T>に対応していなかったので、書き直してみます。

public static int Count<T>(this IEnumerable<T> enumerable)
{
  // 列挙子がIReadOnlyCollection<T>を実装していれば
  var rocollection = enumerable as IReadOnlyCollection<T>;
  if (rocollection != null)
  {
    // 直接Countプロパティを参照する
    return rocollection.Count;
  }
 
  // 列挙子がICollectionを実装していれば
  var collection = enumerable as ICollection;
  if (collection != null)
  {
    // 直接Countプロパティを参照する
    return collection.Count;
  }
 
  // 仕方が無いので、列挙してカウントする
  var count = 0;
  foreach (var value in enumerable)
  {
    count++;
  }
  return count;
}

残念なインターフェイスの通り、IReadOnlyCollection<T>とICollectionは、互いに全く関係がありません。そのため、キャストが成立するかどうかを別々に確かめる必要があります。


あとがき

ちょっと、頭の中を整理したい事もあって、詳しく書き出してみたつもりだったのですが、要所でコード書いて確認してみないと自身が無い部分とかあり、良いリハビリになったなと思いました。

今年もあと少し、実際にはC# Advent Calendarは次週も担当です。進捗はダメです (;´Д`)
それではまた。

次は「a_taihei」さんです。よろしく!

プロ生ちゃんをひろっちゃう! – プログラミング生放送勉強会 第30回@名古屋ソフトウェアセンター

プログラミング生放送勉強会で「プロ生ちゃんをひろっちゃう!」というタイトルでセッションに登壇してきました。

WP_20141108_12_21_12_Pro

初めての参加で、まったりと聞こうと思っていたのに、何故か登壇席にいた… そういうセッションです (;´Д`) 一体どういうことなのか、良く分からない…

仕方が無いので、プロ生ちゃんベースに、いつもとは違う方向性でセッションしました。途中、スライドが見えづらいというリアルタイムなツッコミに動揺し、しかもデモしようとしたら、Azureのネットワーク障害で見せられないという、デモ失敗あるあるでガタガタな結果に orz

すいませんすいませんすいません(泣

精進出来るようにがんばります。

内容はざっくり、プロ生ちゃんサイトの壁紙ページをスクレイピングして、非同期で画像をダウンロードしながらWPFでデータバインディングして表示するという内容です。

スクレイピングするために、どうやってサイトのHTMLを調査するか、そして実際にスクレイピングする時に使えるライブラリや、HTMLの解析方法、ユーザーインターフェイスをスムーズにする為の非同期処理の概要をちりばめました。

以下にスライドを置いておきます。また、例によってソースコードはGitHubに上げてあるので、見る事が出来なかった方は参考にして下さい。

スライド: プロ生ちゃんをひろっちゃう!.pptx
ソースコード: GitHub kekyo/Pronama.ScrapingViewer