オプションのパラメーターを指定したC#4.0を使用して別の問題に遭遇しました。
パラメータを必要としないことがわかっている関数(またはコンストラクタ、ConstructorInfo
オブジェクトを持っている)を呼び出すにはどうすればよいですか?
これが私が今使っているコードです:
type.GetParameterlessConstructor()
.Invoke(BindingFlags.OptionalParamBinding |
BindingFlags.InvokeMethod |
BindingFlags.CreateInstance,
null,
new object[0],
CultureInfo.InvariantCulture);
(私は別のBindingFlags
を試したところです)。
GetParameterlessConstructor
は、私がType
用に作成したカスタム拡張メソッドです。
[〜#〜] msdn [〜#〜] によると、デフォルトのパラメーターを使用するには、Type.Missing
を渡す必要があります。
コンストラクターに3つのオプションの引数がある場合、空のオブジェクト配列を渡す代わりに、各要素の値がType.Missing
である3つの要素のオブジェクト配列を渡します。
type.GetParameterlessConstructor()
.Invoke(BindingFlags.OptionalParamBinding |
BindingFlags.InvokeMethod |
BindingFlags.CreateInstance,
null,
new object[] { Type.Missing, Type.Missing, Type.Missing },
CultureInfo.InvariantCulture);
オプションのパラメーターは通常の属性で示され、コンパイラーによって処理されます。
それらはILに影響を与えず(メタデータフラグ以外)、リフレクションで直接サポートされていません(IsOptional
およびDefaultValue
プロパティを除く)。
リフレクションでオプションのパラメーターを使用する場合は、デフォルト値を手動で渡す必要があります。
コードを追加するだけです...コードは満足できるものではない、と私は同意しますが、コードはかなり単純です。うまくいけば、これがこれにつまずく人を助けるでしょう。これはテストされていますが、本番環境で望んでいるほどではありません。
引数argsを使用してオブジェクトobjのメソッドmethodNameを呼び出しています:
public Tuple<bool, object> Evaluate(IScopeContext c, object obj, string methodName, object[] args)
{
// Get the type of the object
var t = obj.GetType();
var argListTypes = args.Select(a => a.GetType()).ToArray();
var funcs = (from m in t.GetMethods()
where m.Name == methodName
where m.ArgumentListMatches(argListTypes)
select m).ToArray();
if (funcs.Length != 1)
return new Tuple<bool, object>(false, null);
// And invoke the method and see what we can get back.
// Optional arguments means we have to fill things in.
var method = funcs[0];
object[] allArgs = args;
if (method.GetParameters().Length != args.Length)
{
var defaultArgs = method.GetParameters().Skip(args.Length)
.Select(a => a.HasDefaultValue ? a.DefaultValue : null);
allArgs = args.Concat(defaultArgs).ToArray();
}
var r = funcs[0].Invoke(obj, allArgs);
return new Tuple<bool, object>(true, r);
}
また、関数ArgumentListMatchesは以下のとおりです。これは基本的に、おそらくGetMethodにあるロジックの代わりになります。
public static bool ArgumentListMatches(this MethodInfo m, Type[] args)
{
// If there are less arguments, then it just doesn't matter.
var pInfo = m.GetParameters();
if (pInfo.Length < args.Length)
return false;
// Now, check compatibility of the first set of arguments.
var commonArgs = args.Zip(pInfo, (margs, pinfo) => Tuple.Create(margs, pinfo.ParameterType));
if (commonArgs.Where(t => !t.Item1.IsAssignableFrom(t.Item2)).Any())
return false;
// And make sure the last set of arguments are actually default!
return pInfo.Skip(args.Length).All(p => p.IsOptional);
}
LINQがたくさんあり、これはパフォーマンステストされていません!
また、これは一般的な関数またはメソッドの呼び出しを処理しません。これにより、(GetMethod呼び出しが繰り返される場合のように)これはかなり醜くなります。
コードが逆コンパイルされたことを確認すると、すべての質問が消えます。
c#:
public MyClass([Optional, DefaultParameterValue("")]string myOptArg)
msil:
.method public hidebysig specialname rtspecialname instance void .ctor([opt]string myOptArg) cil managed
ご覧のように、オプションのパラメータは、特定の属性で装飾された実際の独立したエンティティであり、前述のように、リフレクションを介して呼び出す場合はそれに応じて尊重する必要があります。
オープンソースフレームワーク ImpromptuInterface バージョン4以降では、C#4.0のDLRを使用して very late bound way でコンストラクターを呼び出すことができ、named/optionalを持つコンストラクターを完全に認識しています引数、これはActivator.CreateInstance(Type type, params object[] args)
より4倍速く実行され、デフォルト値を反映する必要はありません。
using ImpromptuInterface;
using ImpromptuInterface.InvokeExt;
...
//if all optional and you don't want to call any
Impromptu.InvokeConstructor(type)
または
//If you want to call one parameter and need to name it
Impromptu.InvokeConstructor(type, CultureInfo.InvariantCulture.WithArgumentName("culture"))