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

57
Dawn Huczek メタな感じのプログラミング

Upload: tatsuya-ishikawa

Post on 21-Jan-2018

224 views

Category:

Software


4 download

TRANSCRIPT

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

Dawn Huczek

メタな感じのプログラミング

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

自己紹介

・石川達也

・(株)Codeer 代表取締役

・Microsoft MVP

・ささいなことですが(ブログ)

・OSS

FriendlySelenium拡張

LambdicSql

Visual Studio and Development Technologies

http://ishikawa-tatsuya.hatenablog.com/

https://www.nuget.org/profiles/ishikawa-tatsuya

趣味はギターとライブラリ作成

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

Codeer Ltd.

こんなメンバーでやってます!

ソフトウェア開発でお悩みの方は、いつでもご相談ください

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

イントロダクション

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

そもそも、メタプログラミングとは

データ→プログラムプログラム→データプログラム→プログラム

このスライドにまとまってました。

https://www.slideshare.net/kmizushima/ss-6031153

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

つまり、身近なものとして使っている

・コンパイラ・コード生成ウィザード・デザイナ・インテリセンス

IDE系に多い

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

今日話すリフレクションツール

・DynamicObject

・リフレクション

・CodeDom

・Roslyn

・Expression

実例と一緒に話します

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

DynamicObject+

リフレクション

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

Friendly

別プロセスの内部APIを呼び出せるライブラリ

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

Friendlyのデモ

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

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

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

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

}

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

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);//delegatepublic virtual bool TryInvoke(InvokeBinder binder, object[] args, out object result);

//いくつか端折ります…

}

使うものだけオーバーライドしたらいいよ

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

DynamicOjbect

var application = app.Type<Application>();

var openForms = application.OpenForms;

//class DynamicAppType : DynamicObjectpublic 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;

}

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

public class DynamicAppType : DynamicObject

public class DynamicAppVar : DynamicObject

DynamicOjbect

var application = app.Type<Application>();

var openForms = application.OpenForms;

Friendlyでは二種類実装してます

型に対するstaticな操作

オブジェクトに対する操作

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

DynamicOjbect

DynamicAccessorhttps://github.com/neuecc/ChainingAssertion/blob/master/ChainingAssertion/ChainingAssertion.MSTest.cs

Friendlyのはちょっと一般的でないのでサンプルコードとしてはこちらが分かりやすいと思います。

privateな操作を可能にする実装

dynamic objX = obj.AsDynamic();var value = objX.Value;

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

何はともあれ、リフレクション

型情報を取り出す機能。C#でのメタプロの基本といっても過言ではない。型情報はプログラムからはメタデータと呼ばれる。文字列から目的のデータを取得できるので動的な処理が可能となる。

一番なじみ深いメタプロツール

・ Assembly・ Type・ MethodInfo・ PropertyInfo・ FieldInfo

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

【コラム】 タイプの探し方

//現在実行中のアセンブリまたは//Mscorlib.dll 内にある場合でないと無理var type = Type.GetType("MyLib.MyClass");

//お、おう・・・ AssemblyQualifiedNamevar 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();

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

//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 });

【コラム】 タイプの探し方

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

【コラム】メソッドの探し方

//普通はこれでいいんだけど・・・

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

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

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

}

オーバーロードの解決

【コラム】メソッドの探し方

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

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;}

【コラム】メソッドの探し方

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

StandardではType以下の情報取得が面倒・・・

TypeInfo typeInfo = type.GetTypeInfo();

TypeInfoの方に情報があって、わざわざGetTypeInfo()って呼ばないとダメだった。なんでこんな改悪したんだよ・・・・

1.2のコード

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

StandardではType以下の情報取得が面倒・・・結構改善!

てか、Typeの方に戻ってきた。やっぱ評判悪かったんじゃんw

2.0

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

まず、知らないDLLをロードできない→deps.json

全タイプ探すことができない→現在読み込まれているアセンブリの一覧が取れない

どうすんだこれ・・・

DotNetCoreではやりづらくなった

なんだか知らんけどこれもTypeInfo同様改善されることを望む・・・

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

暗黒な使い方だけでなく普通のリフレクション的な簡単な使い方もある。(怖くない

アプリのライフサイクル中に再コンパイルされる可能性のあるアセンブリを読み込むときはこっちを使おう。

こっちにもちょっと書いたよ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();

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

C#スクリプト

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

Quick shot

関数単体で実行するVS拡張(無料)

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

Quick shot デモ

https://marketplace.visualstudio.com/items?itemName=ishikawa-tatsuya.Quickshot

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

先日話したVS拡張の話はこちら

https://www.slideshare.net/tatsuyaishikawa7334/dot-netconf2017-vs

VS拡張はメタプロの宝庫

・コード解析・アセンブリ解析・コンパイル・コード生成・Etc

まあIDEなので当たり前

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

設定をC#で書く

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

DBへの接続設定をどするかで迷った

まずプロバイダ選ばないと・・・

でも、再配布できないものもあるし・・・

Nugetで取ったの選ばせるの?

そんなUI嫌すぎる!

どうすりゃいいねん・・・

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

コードで表現した設定してもらう方が分かりやすいよね!

コネクション取得のプロパティを書いておくと、DBへの接続が必要な時にはそれを使います。

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

特定のコンテキストではGUIでの設定より、XMLでの設定より、C#で設定する方が分かりやすい!

そして、C#のスクリプトをコンパイル手段があればそれが選択肢に入る!

昔から複雑なXMLの設定見るたびにこれならコードで書かせてくれよって思ってました。

C#で設定させるのも選択肢の一つ

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

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;

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

Roslyn

コンパイラプラットフォームコード解析からコンパイルまで最新の構文でも対応している

多機能だけど、それぞれ使いやすい

var code = @"return 1 + 2 + 3;";

var script = CSharpScript.Create<int>(code);var scriptReult = script.RunAsync();scriptReult.Wait();var val = scriptReult.Result.ReturnValue;

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

共通の型を定義しておくと使いやすい

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

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

豆知識 複数回やると同じ名前のがどんどんできるよ。

実行プロセスはアプリと分けておいた方が無難

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

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

Expression

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

λ sql

仲間募集中!

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

LambdicSqlデモ

https://github.com/Codeer-Software/LambdicSql

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

Expression

・構文の解析C#の式をデータとして取り込めるこれにより、C#の構文を別の言語に変換できる

・構文作成キャッシュすることによりリフレクションより高速な動的処理が可能

超便利!若干とっつきにくい・・・

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

Expression解析

int a = 100;Analyze(() => a == 100);

static void Analyze(Expression<Func<bool>> exp)

Func<bool> ではなくExpression<Func<bool>> で受けているところがポイント

EFもこれで受けてます。

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

C#の構文をDSLとして使える

Db<DB>.Sql(db =>Select(Asterisk()).From(db.tbl_staff).Where(db.tbl_staff.name == "ishikawa"));

SELECT *FROM tbl_staffWHERE 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に変換!

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

ちなみに・・・これは静的には決まらない情報です。

つまり呼び出しコストはタダではない。それが安いか否かは状況によりけり。(通常のSQL呼び出しでは無視できる場面は多い)そのため、渡すExpressionが複雑になればそれだけコストは増していきます。

//呼び出した瞬間に//Expression<Func<bool>>//が生成されるAnalyze(() => a == 100);

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

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では以下の式をサポートしてます

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

クラス 種別 例

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 引数 ラムダの引数

ConditionalExpressionLambdaExpressionは未対応。それからExpressionで受けれるものだけなので、そもそも制限はある。

LambdicSqlでは以下の式をサポートしてます

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

木構造なので、再帰的に解析したらOK

ICode Convert(BinaryExpression binary){

//子要素を再帰的に解析していきます。//二項演算式の場合は左右の項目を先に解析する

var left = Convert(binary.Left);var right = Convert(binary.Right);

・・・}

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

イメージ図

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)

解析の概要です

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

【コラム】 括弧がなくなる

Analyze(() => a - (b + c) == d);

a

-

+ d

==

b c

明示的につけた括弧はなくなり最適化されたツリー状態になるのだけど・・・LambdicSqlでどうやっって文字列に戻す?

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

一番簡単なのは、ツリーを戻すときに両方に括弧を付ける

実動作上は問題ないし、バグることもないでも項目数が増えてくると果てしなくダサい・・・

Analyze(() => a - (b + c) == d);

((@a) – ((@b)+(@c)) = (@d)

【コラム】 括弧がなくなる

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

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])

};}

演算子の優先順位を考える

【コラム】 括弧がなくなる

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

カッコよくなった!

Analyze(() => a - (b + c) == d);

@a - (@b + @c) = @d

【コラム】 括弧がなくなる

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

【コラム】 オブジェクトの値取り出し

Db<DB>.Sql(db =>Select(Asterisk()).From(db.tbl_staff).Where(db.tbl_staff.name == target));

SELECT *FROM tbl_staffWHERE tbl_staff.name = @target

Expressionから値を取り出す必要がある。以下の方法が簡単だけど、毎回ビルドはさすがに重い・・・

static object GetObject(MemberExpression exp)=> Expression.Lambda(exp).Compile().DynamicInvoke();

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

取得用のオブジェクトを一回コンパイルしてそれを使う

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).targetvar value = Expression.PropertyOrField(target, memberExp.Member.Name);

//arg => (ojbect)((ObjClass)arg).targetvar converted = Expression.Convert(value, typeof(object));

//コンパイルvar getter = Expression.Lambda<Func<object, object>>(converted, arg).Compile();

【コラム】 オブジェクトの値取り出し

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

プロパティの元のオブジェクトは?

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

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

まとめ

メタプロのチャンスは色々あるので目的に応じて、用量を守り使ってみましょう!