11. DynamicObject + リフレクション
テストコードの方ではC#のコードをデータとして取り込んでいる。
そして、それを対象プロジェクト側に送って
リフレクションで実行させている
//var form = Application.OpenForms[0]
var form = app.Type<Application>().OpenForms[0];
form.BackColor = Color.Green;
var type = FindType("System.Windows.Forms.Application");
var propOpenForms = type.GetProperty("OpenForms", flgs);
var openForms = propOpenForms.GetValue(null);
var method = openForms.GetType().GetMethod("get_Item", flgs);
var form = method.Invoke(openForms, new object[] { 0 });
var propBackColor = form.GetType().GetProperty("BackColor");
propBackColor.SetValue(form, Color.Green);
12. dynamic
ダックタイプができるようになる機能
でも、それだけではない!
public class X
{
public void Func() { }
}
public class Y
{
public void Func() { }
}
public class Test
{
public void Main()
{
DuckType(new X());
DuckType(new Y());
}
public void DuckType(dynamic target)
=> target.Func();
}
13. DynamicOjbect
立派なメタプロツール
DynamicObjectを継承したクラスをdynamicにすると
C#の構文を動的に利用可能なデータに変換してくれる
public class DynamicObject {
//キャスト
public virtual bool TryConvert(ConvertBinder binder, out object result);
//インデクサ (object this[int index])
public virtual bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result);
public virtual bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value);
//プロパティ、フィールド
public virtual bool TryGetMember(GetMemberBinder binder, out object result);
public virtual bool TrySetMember(SetMemberBinder binder, object value);
//メソッド
public virtual bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result);
//delegate
public virtual bool TryInvoke(InvokeBinder binder, object[] args, out object result);
//いくつか端折ります
…
}
使うものだけオーバーライドしたらいいよ
14. DynamicOjbect
var application = app.Type<Application>();
var openForms = application.OpenForms;
//class DynamicAppType : DynamicObject
public DynamicAppType(WindowsAppFriend app, string typeFullName)
=> _typeFullName = typeFullName;
dynamic Type<T>(this app) =>
new DynamicAppType(app, typeof(T).FullName);
public override bool TryGetMember
(GetMemberBinder binder, out object result)
{
//プロパティー名がわかる "OpenForms"
var propOrFieldName = binder.Name;
//タイプ名称とメンバ名称を送る
//その情報があれば、相手プロセスでリフレクションを実行可能
result = SendGetProperty(_typeFullName, propOrFieldName);
return true;
}
15. public class DynamicAppType : DynamicObject
public class DynamicAppVar : DynamicObject
DynamicOjbect
var application = app.Type<Application>();
var openForms = application.OpenForms;
Friendlyでは二種類実装してます
型に対するstaticな操作
オブジェクトに対する操作
18. 【コラム】 タイプの探し方
//現在実行中のアセンブリまたは
//Mscorlib.dll 内にある場合でないと無理
var type = Type.GetType("MyLib.MyClass");
//お、おう・・・ AssemblyQualifiedName
var type = Type.GetType(
"MyLib.MyClass, FullDotNetDll, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
//現実的にはこんなところ
//フルネームが同じのが二つあると正しく取れないけど
//まあ、それは良いでしょう。
var type = AppDomain.CurrentDomain.GetAssemblies().
Select(x => x.GetType(“MyLib.MyClass”)).
Where(x => x != null).
FirstOrDefault();
19. //Genericはちょっと面倒
//A<B>
var a = AppDomain.CurrentDomain.GetAssemblies().
Select(x => x.GetType("MetaTest.A`1")).
Where(x => x != null).FirstOrDefault();
var b = AppDomain.CurrentDomain.GetAssemblies().
Select(x => x.GetType("MetaTest.B")).
Where(x => x != null).FirstOrDefault();
var generic = a.MakeGenericType(new[] { b });
【コラム】 タイプの探し方
20. 【コラム】 メソッドの探し方
//普通はこれでいいんだけど・・・
var binding = BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Static | BindingFlags.Instance;
var method = type.GetMethod(
"Func", binding, null, new [] { typeof(int) }, null);
//こういうの実装したいとき
static object Execute(Type type, object target, string func, params object[] args);
class Q { }
class QQ: Q { }
class WWW
{
public void Func(Q q) { }
public void Func(string s) { }
}
Execute(typeof(WWW), new WWW(), "Func", new QQ());
public MethodInfo GetMethod(string name, BindingFlags bindingAttr,
Binder binder, Type[] types, ParameterModifier[] modifiers);
21. static object Execute(Type type, object target, string func, params object[] args)
{
var binding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance;
MethodInfo matchMethod = null;
var maybeMethods = new List<MethodInfo>();
while (type != null && matchMethod == null)
{
foreach (var x in type.GetMethods(binding))
{
switch (CheckMatch(func, args, x))
{
case MethodMatch.Match:
matchMethod = x;
break;
case MethodMatch.Maybe:
maybeMethods.Add(x);
break;
default:
break;
}
}
type = type.BaseType
}
//完全一致
if (matchMethod != null) return matchMethod.Invoke(target, args);
//一つに絞れてること
return maybeMethods.Single().Invoke(target, args);
}
オーバーロードの解決
【コラム】 メソッドの探し方
22. static MethodMatch CheckMatch(string func, object[] args, MethodInfo methodInfo)
{
//名前や引数の数が違うと不一致
if (methodInfo.Name != func) return MethodMatch.Diff;
var parameters = methodInfo.GetParameters();
if (parameters.Length != args.Length) return MethodMatch.Diff;
var methodMatch = MethodMatch.Match;
for (int i = 0; i < args.Length && methodMatch != MethodMatch.Diff; i++)
{
var paramType = parameters[i].ParameterType;
//nullは値型でなければ一致してるかもしれない
if (args[i] == null)
{
methodMatch = MethodMatch.Maybe;
if (paramType.IsValueType) methodMatch = MethodMatch.Diff;
}
//一致
else if (paramType == args[i].GetType()) { }
//代入できるなら一致してるかもしれない
else if (paramType.IsAssignableFrom(args[i].GetType())) methodMatch = MethodMatch.Maybe;
//不一致
else methodMatch = MethodMatch.Diff;
}
return methodMatch;
}
【コラム】 メソッドの探し方
35. Code Dom
C#スクリプト利用するための元祖
大昔から存在する
新しい構文は使えない
標準で使える
実はかなり暗黒なことも可能
Roslynより高速
var code = @"
public class Abc
{
public int GetValue()=>100;
};";
var codeProvider = new CSharpCodeProvider(
new Dictionary<string, string> { { "CompilerVersion", "v3.5" } });
var param = new CompilerParameters { GenerateInMemory = true };
var compilerResults = codeProvider.CompileAssemblyFromSource(param, code);
//アセンブリからリフレクションで必要な型を取り出す
var asm = compilerResults.CompiledAssembly;
37. 共通の型を定義しておくと使いやすい
var code = @"
public class Abc : MetaTest.ITest
{
public int GetValue()=>100;
}
return new Abc();
";
//インターフェイスの定義されているアセンブリを参照
var option = ScriptOptions.Default.AddReferences(GetType().Assembly);
//スクリプト実行
var script = CSharpScript.Create<ITest>(code, option);
var scriptReult = script.RunAsync();
scriptReult.Wait();
//ITest型の戻り値を取得
var obj = scriptReult.Result.ReturnValue;
var val = obj.GetValue();
public interface ITest
{
int GetValue();
}
38. 豆知識 複数回やると同じ名前のがどんどんできるよ。
実行プロセスは
アプリと分けておいた方が
無難
for (int i = 0; i < 2; i++)
{
var code = $@"
public class Abc
{{
public int GetValue()=>{i};
}}
return new Abc();
";
var script = CSharpScript.Create<object>(code);
var scriptReult = script.RunAsync();
}
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
Type[] types = assemblies.SelectMany(e =>
{
//Roslyn系のDLLを使ってると例外が発生する・・・
try
{
return e.GetTypes();
}
catch { }
return new Type[0];
}).ToArray();
//二個できている
var count = types.Where(e => e.Name == "Abc").Count();
46. ICode Convert(Expression exp)
{
var method = exp as MethodCallExpression;
if (method != null) return Convert(method);
var constant = exp as ConstantExpression;
if (constant != null) return Convert(constant);
var binary = exp as BinaryExpression;
if (binary != null) return Convert(binary);
var unary = exp as UnaryExpression;
if (unary != null) return Convert(unary);
var member = exp as MemberExpression;
if (member != null) return Convert(member);
var newExp = exp as NewExpression;
if (newExp != null) return Convert(newExp);
var array = exp as NewArrayExpression;
if (array != null) return Convert(array);
var memberInit = exp as MemberInitExpression;
if (memberInit != null) return Convert(memberInit);
throw new NotSupportedException("Its way of writing is not supported by LambdicSql.");
}
LambdicSqlでは以下の式をサポートしてます
55. 取得用のオブジェクトを一回コンパイルしてそれを使う
Func<object, object> getter =
arg => ((ObjClass)arg).target;
これを作ってキャッシュしたい
//arg =>
var arg = Expression.Parameter(typeof(object), "arg");
//arg => ((ObjClass)arg)
var target = Expression.Convert(arg, memberExp.Expression.Type);
//arg => ((ObjClass)arg).target
var value = Expression.PropertyOrField(target, memberExp.Member.Name);
//arg => (ojbect)((ObjClass)arg).target
var converted = Expression.Convert(value, typeof(object));
//コンパイル
var getter = Expression.Lambda<Func<object, object>>(converted, arg).Compile();
【コラム】 オブジェクトの値取り出し
56. プロパティの元のオブジェクトは?
var constant = memberExp.Expression as ConstantExpression;
var method = memberExp.Expression as MethodCallExpression;
var newExp = memberExp.Expression as NewExpression;
var memberExp2 = memberExp.Expression as MemberExpression;
var constant = memberExp.Expression as ConstantExpression;
var obj = constant.Value;
LambdicSqlでは以下の4種類を想定
ConstantExpressionなら値が取れる
それ以外なら再帰的にオブジェクトを取得する
MethodCall,NewExpressionは別途実装。
興味があれば、こちらを参照お願いします。
https://github.com/Codeer-Software/LambdicSql/blob/master/Project/LambdicSql.Shared/ConverterServices/Inside/ExpressionToObject.cs