using System;
using System.Dynamic;
using System.Reflection;
namespace MoroMoro.Dynamic
{
public sealed class MemberAdapter : DynamicObject
{
private static BindingFlags GetBindingFlags(bool ignoreCase)
{
var baseFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy;
return (ignoreCase ? (baseFlags | BindingFlags.IgnoreCase) : baseFlags);
}
public MemberAdapter(object target)
{
if (target == null)
{
throw new ArgumentNullException("target");
}
this._target = target;
}
private readonly object _target;
private Type TargetType
{
get
{
return this._target.GetType();
}
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var flags = GetBindingFlags(binder.IgnoreCase);
var field = TargetType.GetField(binder.Name, flags);
var property = TargetType.GetProperty(binder.Name, flags);
if (field != null)
{
result = field.GetValue(this._target);
return true;
}
else if ((property != null) && (property.CanRead))
{
result = property.GetValue(this._target, null);
return true;
}
else
{
result = null;
return false;
}
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
var flags = GetBindingFlags(binder.IgnoreCase);
var field = TargetType.GetField(binder.Name, flags);
var property = TargetType.GetProperty(binder.Name, flags);
if (field != null)
{
field.SetValue(this._target, value);
return true;
}
else if ((property != null) && (property.CanRead))
{
property.SetValue(this._target, value, null);
return true;
}
else
{
return false;
}
}
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
var flags = GetBindingFlags(binder.IgnoreCase);
var filter = (MemberFilter)((member, c) =>
{
var memberAsMethod = member as MethodInfo;
if (memberAsMethod == null)
{
return false;
}
if (!string.Equals(memberAsMethod.Name, binder.Name, (binder.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)))
{
return false;
}
var parameters = memberAsMethod.GetParameters();
if (args.Length != parameters.Length)
{
return false;
}
for (var i = 0; i < parameters.Length; i++)
{
var parameter = parameters[i];
var arg = args[i];
if (arg == null)
{
continue;
}
if (parameter.ParameterType.IsAssignableFrom(arg.GetType()))
{
continue;
}
return false;
}
return true;
});
var methods = TargetType.FindMembers(MemberTypes.Method, flags, filter, null);
if ((methods == null) || (methods.Length == 0))
{
result = null;
return false;
}
var method = (MethodInfo)methods[0];
result = method.Invoke(this._target, args);
return true;
}
}
}
サンプル
class Program
{
class Sample
{
public Sample()
{
this._i = 0;
}
private readonly int _i;
private void Method1()
{
Console.WriteLine(_i);
}
}
static void Main()
{
var s = new Sample();
dynamic m = new MemberAdapter(s);
m._i++;
m.Method1();
Console.ReadKey();
}
}
事実上、.NET 4.0 からは構文ツリーに進化したらしい。
例えば、こんな感じのプログラムを式ツリーで構築したいとする。
for (int i = 1; i <= 10; i++)
{
Console.WriteLine(i);
}
式ツリーでループを組むことはできるが for 文を直接組むことはできないので、こんな感じに変形する。
int i = 1;
while (true)
{
if (i <= 10)
{
Console.WriteLine(i);
}
else
{
break;
}
i++;
}
これを式ツリーで組むとこんな感じになる。
var breakLabel = Expression.Label();
var iParameter = Expression.Variable(typeof(int), "i");
var writeLineMethod = typeof(Console).GetMethod("WriteLine", new[] { typeof(int) });
var lambda = Expression.Lambda
(
Expression.Block
(
new[]
{
iParameter
},
Expression.Assign(iParameter, Expression.Constant(1)),
Expression.Loop
(
Expression.Block
(
Expression.IfThenElse
(
Expression.LessThanOrEqual(iParameter, Expression.Constant(10)),
Expression.Call(writeLineMethod, iParameter),
Expression.Break(breakLabel)
),
Expression.PostIncrementAssign(iParameter)
),
breakLabel
)
)
);
var @delegate = lambda.Compile();
@delegate.DynamicInvoke();
式ツリーを Compile すると DynamicMethod が内部で作られるわけだけど、式ツリーを使わず自分で直接 DynamicMethod を構築するとなると IL を組み立てていくことになるので一苦労。IL だから for 文どころかループもないので、ラベルと goto で…。
ちなみに、CompileToMethod を使用すれば動的クラスの静的メソッドも構築できる。残念ながら動的クラスのインスタンスメソッドを構築することはできない。
最後に、AST (IronPython 同梱の Microsoft.Dynamic.dll に含まれている) を使用すると for 文が簡単に作れるので結構すっきりする。
var writeLineMethod = typeof(Console).GetMethod("WriteLine", new[] { typeof(int) });
var builder = Utils.Lambda(typeof(void), string.Empty);
var iParameter = builder.Variable(typeof(int), "i");
builder.Body = Expression.Block
(
Expression.Assign(iParameter, Expression.Constant(1)),
Utils.Loop
(
Expression.LessThanOrEqual(iParameter, Expression.Constant(10)),
Expression.PostIncrementAssign(iParameter),
Expression.Call(writeLineMethod, iParameter),
null
)
);
var lambda = builder.MakeLambda();
var @delegate = lambda.Compile();
@delegate.DynamicInvoke();
ByRef parameter and C# - 猫とC#について書くmatarilloの日記
MethodInfo.Invoke で動的に実行したメソッド内で例外が発生した場合 out 引数が書き換えられない、という話。
Reflection.Emit でゴリゴリ自前実装してみたものをコメントに投稿しましたが、インスタンスメソッドの動的実行に対応できてなかったので、対応したコードをこちらで晒しときます。あと、NyaRuRu さんからの指摘事項含め、リファクタリングもしときました。
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using Invoker = System.Func<object, object[], object>;
public static class MethodInfoExtension
{
public static object InvokeStrictly(this MethodInfo source, object obj, params object[] parameters)
{
if (source == null)
{
throw new NullReferenceException();
}
int parametersLength = (parameters != null) ? parameters.Length : 0;
if (source.GetParameters().Length != parametersLength)
{
throw new ArgumentException("引数 parameters の要素数とメソッドのパラメータ数が一致しません。");
}
var invokerBuilder = new DynamicMethod("DynamicMethod", typeof(object), new[] { typeof(object), typeof(object[]) }, true);
GenerateCode(source, invokerBuilder);
var invoker = (Invoker)invokerBuilder.CreateDelegate(typeof(Invoker));
return invoker(obj, parameters);
}
private static void GenerateCode(MethodInfo source, DynamicMethod invokerBuilder)
{
ILGenerator ilGenerator = invokerBuilder.GetILGenerator();
List<LocalBuilder> paramLocalBuilderList = GenerateCodeForLocalVariableDeclarationsForParameters(source, ilGenerator);
LocalBuilder resultLocal = GenerateCodeForLocalVariableDeclarationForResult(ilGenerator);
GenerateCodeForSubstitutingsParameterToLocalVariable(paramLocalBuilderList, ilGenerator);
/* Begin try block. */
Label exBlockLabel = ilGenerator.BeginExceptionBlock();
GenerateCodeForCallingSourceMethod(source, paramLocalBuilderList, resultLocal, ilGenerator);
ilGenerator.Emit(OpCodes.Leave, exBlockLabel);
/* End try block. */
/* Begin finally block. */
ilGenerator.BeginFinallyBlock();
GenerateCodeForRestorationParameters(paramLocalBuilderList, ilGenerator);
ilGenerator.EndExceptionBlock();
/* End finally block. */
ilGenerator.Emit(OpCodes.Ldloc_S, resultLocal);
ilGenerator.Emit(OpCodes.Ret);
}
private static List<LocalBuilder> GenerateCodeForLocalVariableDeclarationsForParameters(MethodInfo source, ILGenerator ilGenerator)
{
List<LocalBuilder> localBuilderList = new List<LocalBuilder>();
ParameterInfo[] paramInfos = source.GetParameters();
for (int i = 0; i < paramInfos.Length; i++)
{
var paramInfo = paramInfos[i];
var paramType = paramInfo.ParameterType;
var localType = paramType.IsByRef ? paramType.GetElementType() : paramType;
localBuilderList.Add(ilGenerator.DeclareLocal(localType));
}
return localBuilderList;
}
private static LocalBuilder GenerateCodeForLocalVariableDeclarationForResult(ILGenerator ilGenerator)
{
return ilGenerator.DeclareLocal(typeof(object), false);
}
private static void GenerateCodeForSubstitutingsParameterToLocalVariable(List<LocalBuilder> paramLocalBuilderList, ILGenerator ilGenerator)
{
for (int i = 0; i < paramLocalBuilderList.Count; i++)
{
var paramLocalBuilder = paramLocalBuilderList[i];
ilGenerator.Emit(OpCodes.Ldarg_1);
ilGenerator.Emit(OpCodes.Ldc_I4, i);
ilGenerator.Emit(OpCodes.Ldelem_Ref);
Label passLabel = ilGenerator.DefineLabel();
ilGenerator.Emit(OpCodes.Brfalse, passLabel);
ilGenerator.Emit(OpCodes.Ldarg_1);
ilGenerator.Emit(OpCodes.Ldc_I4, i);
ilGenerator.Emit(OpCodes.Ldelem_Ref);
ilGenerator.Emit(OpCodes.Unbox_Any, paramLocalBuilder.LocalType);
ilGenerator.Emit(OpCodes.Stloc_S, paramLocalBuilder);
ilGenerator.MarkLabel(passLabel);
}
}
private static void GenerateCodeForCallingSourceMethod(MethodInfo source, List<LocalBuilder> paramLocalBuilderList, LocalBuilder resultLocal, ILGenerator ilGenerator)
{
if (!source.IsStatic)
{
GenerateCodeForPushingContextObject(source, ilGenerator);
}
GenerateCodeForPushingParameters(source, paramLocalBuilderList, ilGenerator);
ilGenerator.Emit(OpCodes.Call, source);
if (source.ReturnType == typeof(void))
{
ilGenerator.Emit(OpCodes.Ldnull);
}
ilGenerator.Emit(OpCodes.Stloc_S, resultLocal);
}
private static void GenerateCodeForPushingContextObject(MethodInfo source, ILGenerator ilGenerator)
{
ilGenerator.Emit(OpCodes.Ldarg_0);
if (source.DeclaringType.IsValueType)
{
ilGenerator.Emit(OpCodes.Unbox, source.DeclaringType);
}
else
{
ilGenerator.Emit(OpCodes.Castclass, source.DeclaringType);
}
}
private static void GenerateCodeForPushingParameters(MethodInfo source, List<LocalBuilder> paramLocalBuilderList, ILGenerator ilGenerator)
{
ParameterInfo[] paramInfos = source.GetParameters();
for (int i = 0; i < paramInfos.Length; i++)
{
var paramInfo = paramInfos[i];
var paramLocalBuilder = paramLocalBuilderList[i];
bool paramIsByRef = paramInfo.ParameterType.IsByRef;
if (paramIsByRef)
{
ilGenerator.Emit(OpCodes.Ldloca_S, paramLocalBuilder);
}
else
{
ilGenerator.Emit(OpCodes.Ldloc_S, paramLocalBuilder);
}
}
}
private static void GenerateCodeForRestorationParameters(List<LocalBuilder> paramLocalBuilderList, ILGenerator ilGenerator)
{
for (int i = 0; i < paramLocalBuilderList.Count; i++)
{
var paramLocalBuilder = paramLocalBuilderList[i];
var localType = paramLocalBuilder.LocalType;
ilGenerator.Emit(OpCodes.Ldarg_1);
ilGenerator.Emit(OpCodes.Ldc_I4, i);
ilGenerator.Emit(OpCodes.Ldloc_S, paramLocalBuilder);
if (localType.IsValueType)
{
ilGenerator.Emit(OpCodes.Box, localType);
}
ilGenerator.Emit(OpCodes.Stelem, typeof(object));
}
}
}
using System;
using System.Reflection;
class P
{
public static void Method(out string arg)
{
arg = "out";
throw new Exception();
}
static void Main()
{
object[] args = new object[1];
try
{
MethodInfo targetMethod = typeof(P).GetMethod("Method");
targetMethod.InvokeStrictly(null, args);
}
catch (Exception ex)
{
Console.WriteLine(ex);
Console.WriteLine();
}
Console.WriteLine(args[0]);
Console.ReadLine();
}
}
こーゆー IL を動的に構築するコードって、もしかしてメソッド分割しない方が見やすい?
メタデータの取得に用いられるクラスは、いくつかあります。これらは、アセンブリや型やメンバー ( フィールド、プロパティ、イベント、コンストラクタ、メソッド ) などに対応して用意されているので、わかりやすくなっています。
Assembly クラス (System.Reflection)
アセンブリを表します。
Module クラス (System.Reflection)
モジュールを表します。
Type クラス (System)
型を表します。型とはクラスとインターフェイスです。構造体,列挙型,デリゲートなどもクラスの一種であり、Typeクラスで表されます。
MemberInfo クラス (System.Reflection)
ク ラスのメンバ ( フィールド、プロパティ、コンストラクタ、メソッド、イベント ) を表します。このクラスは、FieldInfoクラス、PropertyInfoクラス、EventInfoクラス、 MethodBaseクラスの基本クラスです。また、Typeクラスの基本クラスでもあることに注意します。
FieldInfo クラス (System.Reflection)
フィールドを表します。
PropertyInfo クラス (System.Reflection)
プロパティを表します。
EventInfo クラス (System.Reflection)
イベントを表します。
MethodBase クラス (System.Reflection)
コンストラクタとメソッドを表します。このクラスは、ConstructorInfoクラス、MethodInfoクラスの基本クラスです。
ConstructorInfo クラス (System.Reflection)
コンストラクタを表します。
MethodInfo クラス (System.Reflection)
メソッドを表します。
それぞれの継承関係は以下のようになります。
□ System.Object
├─□ System.Reflection.Assembly
├─□ System.Reflection.Module
└─□ System.Reflection.MemberInfo
├─□ System.Type
├─□ System.Reflection.FieldInfo
├─□ System.Reflection.PropertyInfo
├─□ System.Reflection.EventInfo
└─□ System.Reflection.MethodBase
├─□ System.Reflection.ConstructorInfo
└─□ System.Reflection.MethodInfo
今回はここまでです。次回は、型情報の取得について解説します。
【 リフレクションとは何か? 】
一言で言うと、メタデータ ( データについてのデータ ) を扱う仕組みです。
アセンブリ,モジュール,型 ( クラス,構造体,インターフェイス,デリゲート,列挙型 ) ,メンバ ( フィールド,プロパティ,イベント,コンストラクタ,メソッド ) の情報を、動的に取得・使用・生成することができます。
要するに、プログラムコードでプログラムコードを扱うというようなイメージです。
まだイマイチわかりづらいと思いますので、例を挙げて説明していきます。
例えば、 パブリックなメソッド " Method1 " と、プライベートなメソッド " Method2 " を持つ、" Class1 " というクラスがあります。
このクラスに対してリフレクションを利用すると、
「Class1クラスのMethod1メソッドのパラメータリストを、動的に調べる」とか、
「Class1クラスのプライベートメソッドであるMethod2メソッドを、外部から呼び出す」とか、
「Class1クラスに、新しく " Method3 " というメソッドを、動的に追加する」
といったことが可能になります。もちろん、これらは一例であり、他にもいろいろなことができます。
また、アセンブリに埋め込まれたリソースの取得や、アセンブリの動的ロード、属性の取得などにも、リフレクションを利用します。
【 リフレクションを使う場面 】
「リフレクションなんて必要あるの?」と思う人もいるかもしれませんが、例えば以下に挙げるようなアプリケーションやクラスライブラリでは、しばしばリフレクションを必要とします。
代表例 : Visual Studio、Reflector、NDoc、FxCop、etc...
・.NET 開発のサポート系クラスライブラリ
代表例 : Enterprise Library、etc...
・他者が機能を拡張するための機構 ( プラグイン ) を設けるアプリケーション
代表例 : Visual Studio 2005、etc...
どれも、リフレクションを使用している機能を利用するのは.NET開発者です。 ( " プラグイン機能を利用するのはプラグイン開発者 " として。 )
他に、IronPytonやPowerShellも、リフレクションを利用しています。これらは、コンソールに入力されたコード ( コマンド ) を、リフレクションを利用して処理しています。
【 名前空間 】
リフレクションの主要なクラスは、以下の2つの名前空間に含まれています。
メタデータを動的に取得・使用するためのクラス群が含まれています。
System.Reflection.Emit 名前空間
メタデータを動的に生成するためのクラス群が含まれています。
なお、System.Reflection 名前空間には含まれてはいませんが、Type クラス (System) や、AppDomain クラス (System) も、リフレクションにおける重要なクラスの一つです。
今回はここまでです。次回は、リフレクションを利用してメタデータを取得する方法について解説します。