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();