反射から明示的な操作キャスティングを実行するにはどうすればよいですか?
-
28-10-2019 - |
質問
反射を使用して、反射を使用して暗黙的または明示的なカバーを実行したいと思います。
このようにFooを定義したことを考えると
public class Foo
{
public static explicit operator decimal(Foo foo)
{
return foo.Value;
}
public static explicit operator Foo(decimal number)
{
return new Foo(number);
}
public Foo() { }
public Foo(decimal number)
{
Value = number;
}
public decimal Value { get; set; }
public override string ToString()
{
return Value.ToString();
}
}
このコードを実行するとき
decimal someNumber = 42.42m;
var test = (Foo)someNumber;
Console.WriteLine(test); // Writes 42.42 No problems
Fooを使用してクラスをメンバータイプとして定義し、リフレクションを使用して設定しようとすると。次の例外があります。
Error : Object of type 'System.Decimal' cannot be converted to type 'Foo'.
StackTrace: at System.RuntimeType.TryChangeType(Object value, Binder binder, CultureInfo culture, Boolean needsSpecialCast)
at System.RuntimeType.CheckValue(Object value, Binder binder, CultureInfo culture, BindingFlags invokeAttr)
at System.Reflection.MethodBase.CheckArguments(Object[] parameters, Binder binder, BindingFlags invokeAttr, CultureInfo culture, Signature sig)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index)
これが私がリフレクションでプロパティを設定するために使用するコードです
public class FooComposite
{
public Foo Bar { get; set; }
}
var properties = typeof(FooComposite).GetProperties();
var testFoo = new FooComposite();
foreach(var propertyInfo in properties)
{
propertyInfo.SetValue(testFoo, 17.17m, null); // Exception generated on this line
}
Console.WriteLine(testFoo.Bar); // Never gets here
どうすればこの変換を行うことができますか?
解決
まあ、それはあなたの非反射コードと実際に違いはありませんが、あなたはまだ数字をに明示的にキャストする必要があります Foo
:
propertyInfo.SetValue(testFoo,(Foo)17.17m, null);
ライブ例: http://rextester.com/rundotnet?code=bpq74480
興味深いことに、私はいくつかの選択肢を試しました。
他のヒント
私は今日、オブジェクトに名前でフィールドをコピーしようとしながら、この質問を見ました。選択された答えが「明示的に明示的なオペレーターを呼び出すことができる」ということを見て、私は非常に失望しました。結局のところ、他のことは反射によって行うことができます。
私の問題は、複雑なタイプのために2つのクラスの間に深いコピーを作成しようとする反射方法でした。私は定義しようとしました explicit operator
変換ですが、それは呼ばれるようには見えなかったので、私は反射によってそれを取得する方法を見つけました。静的メソッドの呼び出しに関する他の研究を使用して、Psourceに保存されている複雑なタイプをプロパティPDestの別のタイプにコピーするときに、これが私のために機能することがわかりました。 PDESTのタイプには、Psourceのタイプからの変換があります。
MethodInfo[] static_methods = pDest.PropertyType.GetMethods(System.Reflection.BindingFlags.Static | BindingFlags.Public);
if (static_methods != null)
{
foreach (MethodInfo method in static_methods)
{
if(method.Name== "op_Explicit") // this is a constant
{ // for explicit operators
ParameterInfo[] paramSet = method.GetParameters();
if ((paramSet != null) && (paramSet.Length == 1))
{
if (paramSet[0].ParameterType == pSource.PropertyType) // match the types!
{
pDest.SetValue( // Destination prop
dstVar, // Destination instance
method.Invoke( // converter method
null, // static has no 'this'
new object[] { // value to convert from
pSource.GetValue(source, null)
} // source property on
// source instance
)
); // SetValue(...)
}
}
}
}
}
DSTVARは私の目的地インスタンスです。 PDESTは、宛先インスタンスの現在のPropertyInfoです。
ソースは私のソースインスタンスです。 Psourceは、ソースインスタンスの現在のPropertyInfoです。
私の宛先プロパティに使用されるタイプは、ソースプロパティタイプからの明示的な変換があり、これは何も必要なく動作します
Ted Hのような機能が必要でしたが、このように実装しました。
var cast = typeof(dest).GetMethod("op_Explicit", new Type[] { typeof(source) });
var result = cast.Invoke(null, new object[] {value});
編集: 最近、より進化したバージョンが必要でしたが、これが私が思いついたものです。利用可能なすべての変換をカバーするわけではないことに注意してください。
private static object DynamicCast(object source, Type destType) {
Type srcType = source.GetType();
if (srcType == destType) return source;
var paramTypes = new Type[] { srcType };
MethodInfo cast = destType.GetMethod("op_Implicit", paramTypes);
if (cast == null) {
cast = destType.GetMethod("op_Explicit", paramTypes);
}
if (cast != null) return cast.Invoke(null, new object[] { source });
if (destType.IsEnum) return Enum.ToObject(destType, source);
throw new InvalidCastException();
}
建物 ハーマンの答え...ソースクラスと宛先クラスの両方が変換演算子を定義できることに気付きました。これが私のバージョンです:
private static bool DynamicCast(object source, Type destType, out object result)
{
Type srcType = source.GetType();
if (srcType == destType) { result = source; return true; }
result = null;
BindingFlags bf = BindingFlags.Static | BindingFlags.Public;
MethodInfo castOperator = destType.GetMethods(bf)
.Union(srcType.GetMethods(bf))
.Where(mi => mi.Name == "op_Explicit" || mi.Name == "op_Implicit")
.Where(mi =>
{
var pars = mi.GetParameters();
return pars.Length == 1 && pars[0].ParameterType == srcType;
})
.Where(mi => mi.ReturnType == destType)
.FirstOrDefault();
if (castOperator != null) result = castOperator.Invoke(null, new object[] { source });
else return false;
return true;
}
典型的な使用法:
object a = new A();
object o;
if (DynamicCast(a, typeof(B), out o))
{
B b = (B)o;
...
}
次のことに注意してください:
- コンバージョンがソースと宛先の両方で定義されている場合、宛先変換演算子方法に優先順位が与えられます
- 関数は、成功/失敗を示すブール、およびの実際の変換値を示します
out
変数(TryParseメソッドと同様)
上記のすべてに感謝します。私は借りて追加しました。私の状況では、上記のすべてが必要であり、ソースタイプと宛先タイプの両方のすべての祖先ベースタイプを検索して、それらのいずれかが私の宛先タイプへの暗黙的または明示的な変換が含まれているかどうかを確認する必要がありました。この追加要件を追加して、以下を作成しました。
private static bool TryCast(object source, Type destType, out object result)
{
Type srcType = source.GetType();
if (srcType == destType)
{
result = source;
return true;
}
MethodInfo cast = null;
while (cast == null && srcType != typeof(object))
{
cast = GetCastMethod(srcType, srcType, destType);
if (cast == null) cast = GetCastMethod(destType, srcType, destType);
srcType = srcType.BaseType;
}
if (cast != null)
{
result = cast.Invoke(null, new object[] { source });
return true;
}
if (destType.IsEnum)
{
result = Enum.ToObject(destType, source);
return true;
}
result = null;
return false;
}
private static MethodInfo GetCastMethod(Type typeWithMethod, Type srcType, Type destType)
{
while (typeWithMethod != typeof(object))
{
foreach (MethodInfo method in typeWithMethod.GetMethods(BindingFlags.Static | BindingFlags.Public))
{
if (method.ReturnType == destType && (method.Name == "op_Explicit" || method.Name == "op_Implicit"))
{
ParameterInfo[] parms = method.GetParameters();
if (parms != null && parms.Length == 1 && parms[0].ParameterType == srcType)
return method;
}
}
typeWithMethod = typeWithMethod.BaseType;
}
return null;
}