Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9322

【C#】式木を使った動的なデリゲート生成

$
0
0

式木とは

C#の構文を動的に生成できる機能です。

メソッドでの生成とラムダ式での生成がサポートされています。

Expression<Func<int,int,int>>exp1=(a,b)=>a+b;varparamA=Expression.Parameter(typeof(int),"a");varparamB=Expression.Parameter(typeof(int),"b");Expression<Func<int,int,int>>exp2=Expression.Lambda<Func<int,int,int>>(Expression.Add(paramA,paramB),paramA,paramB);// exp1とexp2は同等varf1=exp1.Compile();varf2=exp2.Compile();Console.WriteLine(f1(3,4));// -> 7Console.WriteLine(f2(3,4));// -> 7

式木から別の式木を作ってみる

T型の引数からIComparable<K>であるKを返すようなラムダ式を元にIComparer<T>を下記のように作れます。

例えば、コンストラクタに(string s) => -s.Lengthを渡すと文字列の長さの降順となるような結果が得られます。

publicclassExpComparer<T,K>:IComparer<T>whereK:IComparable<K>{privateclassParameterReplaceVisitor:ExpressionVisitor{privatereadonlyParameterExpressionfrom;privatereadonlyParameterExpressionto;publicParameterReplaceVisitor(ParameterExpressionfrom,ParameterExpressionto){this.from=from;this.to=to;}protectedoverrideExpressionVisitParameter(ParameterExpressionnode)=>node==from?to:base.VisitParameter(node);}privatereadonlyComparison<T>func;publicExpComparer(Expression<Func<T,K>>expression){varparamA=expression.Parameters[0];varparamB=Expression.Parameter(typeof(T));varexp2=(Expression<Func<T,K>>)newParameterReplaceVisitor(paramA,paramB).Visit(expression);varcompExp=Expression.Lambda<Comparison<T>>(Expression.Call(expression.Body,typeof(K).GetMethod(nameof(IComparable<K>.CompareTo),new[]{typeof(K)}),exp2.Body),paramA,paramB);this.func=compExp.Compile();}publicintCompare(Tx,Ty)=>func(x,y);publicoverrideboolEquals(objectobj)=>obj!=null&&GetType()==obj.GetType();publicoverrideintGetHashCode()=>GetType().GetHashCode();}

以下、ポイントごとに解説します

paramA, paramB

Comparison<T>は引数を2つ持つので、それに対応するparamAparamBを用意します。

片方は元のParameterExpressionをそのまま流用でOKです。

ExpressionVisitor

引数の式木のパラメータを新たに生成したparamBで置き換える役割です。

Expression.Lambda<Comparison<T>>

ここでCompareToの呼び出しを構築します。

expressionをp => -pだったとき

// expressionを p => -pとする(p1,p2)=>-p1.CompareTo(-p2)

というようになります。

expression.Bodyexp2.Bodyの順番を間違えると

// expressionを p => -pとする(p1,p2)=>-p2.CompareTo(-p1)

になるので注意

Compile

CompileメソッドでLambdaExpressionをdelegateに変換します。

あとは普通のdelegateとして扱えます。

補足:ParameterExpressionについて

Expression<Func<string,int>>expression=a=>a.Length;varparamA=expression.Parameters[0];varparamB=Expression.Parameter(typeof(string),"a");varexp2=(Expression<Func<string,int>>)newParameterReplaceVisitor(paramA,paramB).Visit(expression);varcompExp=Expression.Lambda<Comparison<string>>(Expression.Call(expression.Body,typeof(int).GetMethod(nameof(IComparable<int>.CompareTo),new[]{typeof(int)}),exp2.Body),paramA,paramB);varfunc=compExp.Compile();Console.WriteLine(compExp);// -> (a,a) = a.Length.CompareTo(a.Length)

上記のようにexpressionのパラメータがaとなっている場合に、生成される式木が(a,a) = a.Length.CompareTo(a.Length)となりますが問題ありません。

ParameterExpressionNameプロパティが同一でもインスタンスが別(object.ReferenceEqualsでの比較がfalse)の場合は別の変数として扱われるためです。

逆にいうと、

Expression<Func<string,int>>expression=Expression.Lambda<Func<string,int>>(Expression.Property(Expression.Parameter(typeof(string),"a"),nameof(string.Length)),Expression.Parameter(typeof(string),"a"));

のような式木はa => a.Lengthとなりますが不正です。

Console.WriteLine(expression);// -> a => a.LengthConsole.WriteLine(expression.Compile()("f42"));// Unhandled exception. System.InvalidOperationException: variable 'a' of type 'System.String' referenced from scope '', but it is not defined

ラムダの引数のaとa.Lengthのaが別の変数として扱われるためです。


Viewing all articles
Browse latest Browse all 9322

Latest Images

Trending Articles