Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

メタな感じのプログラミング(プロ生 + わんくま 071118)

527 vues

Publié le

2017/11/18 の プロ生+わんくま勉強会 で発表したスライドです。

Publié dans : Logiciels
  • Hello! Get Your Professional Job-Winning Resume Here - Check our website! https://vk.cc/818RFv
       Répondre 
    Voulez-vous vraiment ?  Oui  Non
    Votre message apparaîtra ici

メタな感じのプログラミング(プロ生 + わんくま 071118)

  1. 1. Dawn Huczek メタな感じのプログラミング
  2. 2. 自己紹介 ・石川達也 ・(株)Codeer 代表取締役 ・Microsoft MVP ・ささいなことですが(ブログ) ・OSS Friendly Selenium拡張 LambdicSql Visual Studio and Development Technologies http://ishikawa-tatsuya.hatenablog.com/ https://www.nuget.org/profiles/ishikawa-tatsuya 趣味はギターとライブラリ作成
  3. 3. Codeer Ltd. こんなメンバーでやってます! ソフトウェア開発でお悩みの方は、 いつでもご相談ください
  4. 4. イントロダクション
  5. 5. そもそも、メタプログラミングとは データ→プログラム プログラム→データ プログラム→プログラム このスライドにまとまってました。 https://www.slideshare.net/kmizushima/ss-6031153
  6. 6. つまり、身近なものとして使っている ・コンパイラ ・コード生成ウィザード ・デザイナ ・インテリセンス IDE系に多い
  7. 7. 今日話すリフレクションツール ・DynamicObject ・リフレクション ・CodeDom ・Roslyn ・Expression 実例と一緒に話します
  8. 8. DynamicObject + リフレクション
  9. 9. Friendly 別プロセスの内部APIを呼び出せるライブラリ
  10. 10. Friendlyのデモ
  11. 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. 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. 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. 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. 15. public class DynamicAppType : DynamicObject public class DynamicAppVar : DynamicObject DynamicOjbect var application = app.Type<Application>(); var openForms = application.OpenForms; Friendlyでは二種類実装してます 型に対するstaticな操作 オブジェクトに対する操作
  16. 16. DynamicOjbect DynamicAccessor https://github.com/neuecc/ChainingAssertion/blob/master/ChainingAssertion/ChainingAssertion.MSTest.cs Friendlyのはちょっと一般的でないので サンプルコードとしてはこちらが分かりやすいと思います。 privateな操作を可能にする実装 dynamic objX = obj.AsDynamic(); var value = objX.Value;
  17. 17. 何はともあれ、リフレクション 型情報を取り出す機能。 C#でのメタプロの基本といっても過言ではない。 型情報はプログラムからはメタデータと呼ばれる。 文字列から目的のデータを取得できるので 動的な処理が可能となる。 一番なじみ深いメタプロツール ・ Assembly ・ Type ・ MethodInfo ・ PropertyInfo ・ FieldInfo
  18. 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. 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. 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. 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. 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; } 【コラム】 メソッドの探し方
  23. 23. StandardではType以下の情報取得が面倒・・・ TypeInfo typeInfo = type.GetTypeInfo(); TypeInfoの方に情報があって、わざわざGetTypeInfo()って 呼ばないとダメだった。なんでこんな改悪したんだよ・・・・ 1.2のコード
  24. 24. StandardではType以下の情報取得が面倒・・・結構改善! てか、Typeの方に戻ってきた。 やっぱ評判悪かったんじゃんw 2.0
  25. 25. まず、知らないDLLをロードできない →deps.json 全タイプ探すことができない →現在読み込まれているアセンブリの一覧が取れない どうすんだこれ・・・ DotNetCoreではやりづらくなった なんだか知らんけど これもTypeInfo同様 改善されることを望む・・・
  26. 26. 暗黒な使い方だけでなく 普通のリフレクション的な簡単な使い方もある。 (怖くない アプリのライフサイクル中に 再コンパイルされる可能性のある アセンブリを読み込むときは こっちを使おう。 こっちにもちょっと書いたよ https://www.slideshare.net/tatsuyaishikawa7334/dot-netconf2017-vs Mono.Cecil //アセンブリ情報取得 var asm = AssemblyDefinition.ReadAssembly(assemblyPath); //タイプ var type = asm.Modules.SelectMany(e => e.Types). Where(e => e.FullName == typeFullName).FirstOrDefault();
  27. 27. C#スクリプト
  28. 28. Quick shot 関数単体で実行するVS拡張(無料)
  29. 29. Quick shot デモ https://marketplace.visualstudio.com/items?itemName=ishikawa- tatsuya.Quickshot
  30. 30. 先日話したVS拡張の話はこちら https://www.slideshare.net/tatsuyaishikawa7334/dot -netconf2017-vs VS拡張はメタプロの宝庫 ・コード解析 ・アセンブリ解析 ・コンパイル ・コード生成 ・Etc まあIDEなので当たり前
  31. 31. 設定をC#で書く
  32. 32. DBへの接続設定をどするかで迷った まずプロバイダ選ばないと・・・ でも、再配布できないものもあるし・・・ Nugetで取ったの選ばせるの? そんなUI嫌すぎる! どうすりゃいいねん・・・
  33. 33. コードで表現した設定してもらう方が分かりやすいよね! コネクション取得のプロパティを書いておくと、 DBへの接続が必要な時にはそれを使います。
  34. 34. 特定のコンテキストでは GUIでの設定より、 XMLでの設定より、 C#で設定する方が分かりやすい! そして、C#のスクリプトをコンパイル手段があれば それが選択肢に入る! 昔から複雑なXMLの設定見るたびに これならコードで書かせてくれよって 思ってました。 C#で設定させるのも選択肢の一つ
  35. 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;
  36. 36. Roslyn コンパイラプラットフォーム コード解析から コンパイルまで 最新の構文でも対応している 多機能だけど、それぞれ使いやすい var code = @" return 1 + 2 + 3; "; var script = CSharpScript.Create<int>(code); var scriptReult = script.RunAsync(); scriptReult.Wait(); var val = scriptReult.Result.ReturnValue;
  37. 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. 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();
  39. 39. Expression
  40. 40. λ sql 仲間募集中!
  41. 41. LambdicSqlデモ https://github.com/Codeer-Software/LambdicSql
  42. 42. Expression ・構文の解析 C#の式をデータとして取り込める これにより、C#の構文を別の言語に変換できる ・構文作成 キャッシュすることにより リフレクションより高速な動的処理が可能 超便利! 若干とっつきにくい・・・
  43. 43. Expression解析 int a = 100; Analyze(() => a == 100); static void Analyze(Expression<Func<bool>> exp) Func<bool> ではなく Expression<Func<bool>> で受けているところがポイント EFもこれで受けてます。
  44. 44. C#の構文をDSLとして使える Db<DB>.Sql(db => Select(Asterisk()). From(db.tbl_staff). Where(db.tbl_staff.name == "ishikawa") ); SELECT * FROM tbl_staff WHERE tbl_staff.name = @p_0 efModel.tbl_staff.Where(e => e.name == "ishikawa"); SELECT [Extent1].[id] AS [id], [Extent1].[name] AS [name] FROM [dbo].[tbl_staff] AS [Extent1] WHERE 'Jackson' = [Extent1].[name] Entity Framewrok LambdicSql SQLに変換!
  45. 45. ちなみに・・・ これは静的には決まらない情報です。 つまり呼び出しコストはタダではない。 それが安いか否かは状況によりけり。 (通常のSQL呼び出しでは無視できる場面は多い) そのため、渡すExpressionが複雑になれば それだけコストは増していきます。 //呼び出した瞬間に //Expression<Func<bool>> //が生成される Analyze(() => a == 100);
  46. 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では以下の式をサポートしてます
  47. 47. クラス 種別 例 ConstantExpression 定数 true BinaryExpression 二項演算 1 == a UnaryExpression 単項演算 !value, (boo)obj MemberExpression メンバ A.B MethodCallExpression メソッド呼び出し Method(1,2) NewExpression 生成 new A() MemberInitExpression 生成時の初期化 new A{ X = 1} NewArrayExpression param付配列 1, 2, 3 ParameterExpression 引数 ラムダの引数 ConditionalExpression LambdaExpression は未対応。 それからExpressionで受けれるものだ けなので、そもそも制限はある。 LambdicSqlでは以下の式をサポートしてます
  48. 48. 木構造なので、再帰的に解析したらOK ICode Convert(BinaryExpression binary) { //子要素を再帰的に解析していきます。 //二項演算式の場合は左右の項目を先に解析する var left = Convert(binary.Left); var right = Convert(binary.Right); ・・・ }
  49. 49. イメージ図 Db<DB>.Sql(db => Select(Asterisk()). From(db.tbl_staff). Where(db.tbl_staff.name == "Jackson") ); MethodCallExpression (Where) MethodCallExpression (From) BinaryExpression (==) MemberExpression (name) ConstExpression (“Jackson”) MemberExpression (tbl_staff) ParameterExpression (db) MethodCallExpression (Select) MemberExpression (tbl_staff) ParameterExpression (db) MethodCallExpression (Asterisk) 解析の概要です
  50. 50. 【コラム】 括弧がなくなる Analyze(() => a - (b + c) == d); a - + d == b c 明示的につけた括弧はなくなり 最適化されたツリー状態になる のだけど・・・ LambdicSqlでどうやっって 文字列に戻す?
  51. 51. 一番簡単なのは、ツリーを戻すときに両方に括弧を付ける 実動作上は問題ないし、 バグることもない でも項目数が増えてくると 果てしなくダサい・・・ Analyze(() => a - (b + c) == d); ((@a) – ((@b)+(@c)) = (@d) 【コラム】 括弧がなくなる
  52. 52. static readonly Dictionary<ExpressionType, int> Priority = new Dictionary<ExpressionType, int> { { ExpressionType.Or , 0}, { ExpressionType.OrElse , 0}, { ExpressionType.And , 1}, { ExpressionType.AndAlso , 1}, { ExpressionType.LessThan , 2}, { ExpressionType.LessThanOrEqual , 2}, { ExpressionType.GreaterThan , 2}, { ExpressionType.GreaterThanOrEqual , 2}, { ExpressionType.Equal , 3}, { ExpressionType.NotEqual , 3}, { ExpressionType.Add , 4}, { ExpressionType.Subtract , 4}, { ExpressionType.Multiply , 5}, { ExpressionType.Divide , 5}, { ExpressionType.Modulo , 5}, }; static AddingBlankets CheckAddingBlanckets(BinaryExpression binary) { var leftBinary = binary.Left as BinaryExpression; var rightBinary = binary.Right as BinaryExpression; return new AddingBlankets { Left = (leftBinary != null && Priority[leftBinary.NodeType] < Priority[binary.NodeType]), Right = (rightBinary != null && Priority[rightBinary.NodeType] <= Priority[binary.NodeType]) }; } 演算子の優先順位を 考える 【コラム】 括弧がなくなる
  53. 53. カッコよくなった! Analyze(() => a - (b + c) == d); @a - (@b + @c) = @d 【コラム】 括弧がなくなる
  54. 54. 【コラム】 オブジェクトの値取り出し Db<DB>.Sql(db => Select(Asterisk()). From(db.tbl_staff). Where(db.tbl_staff.name == target) ); SELECT * FROM tbl_staff WHERE tbl_staff.name = @target Expressionから値を取り出す必要がある。 以下の方法が簡単だけど、毎回ビルドはさすがに重い・・・ static object GetObject(MemberExpression exp) => Expression.Lambda(exp).Compile().DynamicInvoke();
  55. 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. 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
  57. 57. まとめ メタプロのチャンスは色々あるので 目的に応じて、用量を守り使ってみましょう!

×