MemberExpressionからオブジェクトを取得しますか?
-
06-07-2019 - |
質問
では、C#に次の式があるとします。
Expression<Func<string>> expr = () => foo.Bar;
fooへの参照を引き出すにはどうすればよいですか?
解決
Expression<Func<string>> expr = () => foo.Bar;
var me = (MemberExpression)((MemberExpression)expr.Body).Expression;
var ce = (ConstantExpression)me.Expression;
var fieldInfo = ce.Value.GetType().GetField(me.Member.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
var value = (Foo)fieldInfo.GetValue(ce.Value);
他のヒント
同じ問題を抱えていましたが、やや複雑でした。ダリン・ディミトロフの答えは私にとって良いスタートでした。これは「古い」という事実にもかかわらず、ここに結果を投稿します。質問。
ケース1:ルートオブジェクトはオブジェクトメンバーです
this.textBox.Text // where 'this' has type 'Form'
...は、次の式ツリーと同等です。
. +====================+
. | MemberExpression |
. +====================+
# | |
# .Expression | | .Member
# v v
. +------------------+ +------------+
. | MemberExpression | | MemberInfo |
. +------------------+ +------------+
# | | .Name = "Text"
# .Expression | | .Member .MemberType = Property
# v v
. +--------------------+ +------------+
. | ConstantExpression | | MemberInfo |
. +--------------------+ +------------+
# .Value = this .Name = "textBox"
# .Type = typeof(Form) .MemberType = Field
この式ツリーで実際にオブジェクト参照を取得する唯一の場所は、 ConstantExpression
からです。これにより、 this
への参照を取得できます。したがって、このツリーでオブジェクト参照を取得する基本的な考え方は次のとおりです。
-
ConstantExpression
ノードに到達するまで、.Expression
軸に沿って式ツリーに降ります。 -
そのノードの
.Value
プロパティを取得します。これはルートオブジェクト参照です(上記の例ではthis
)。 -
式ツリーのリフレクションと
MemberInfo
ノードを使用して、オブジェクト参照を取得し、「アップ」に戻ります。式ツリー。
これを示すコードを次に示します。
Expression expr = ...; // <-- initially set to the expression tree's root
var memberInfos = new Stack<MemberInfo>();
// "descend" toward's the root object reference:
while (expr is MemberExpression)
{
var memberExpr = expr as MemberExpression;
memberInfos.Push(memberExpr.Member);
expr = memberExpr.Expression
}
// fetch the root object reference:
var constExpr = expr as ConstantExpression;
var objReference = constExpr.Value;
// "ascend" back whence we came from and resolve object references along the way:
while (memberInfos.Count > 0) // or some other break condition
{
var mi = memberInfos.Pop();
if (mi.MemberType == MemberTypes.Property)
{
objReference = objReference.GetType()
.GetProperty(mi.Name)
.GetValue(objReference, null);
}
else if (mi.MemberType == MemberTypes.Field)
{
objReference = objReference.GetType()
.GetField(mi.Name)
.GetValue(objReference);
}
}
ケース2:ルートオブジェクトは静的クラスメンバーです
Form.textBox.Text // where 'textBox' is a static member of type 'Form'
...は、異なる式ツリーになります。左下のヌル参照に注意してください:
. +====================+
. | MemberExpression |
. +====================+
# | |
# .Expression | | .Member
# v v
. +------------------+ +------------+
. | MemberExpression | | MemberInfo |
. +------------------+ +------------+
# | | .Name = "Text"
# .Expression | | .Member .MemberType = Property
# v v
. null +------------+
. | MemberInfo |
. +------------+
# .Name = "textBox"
# .MemberType = Field
# .DeclaringType = typeof(Form)
ここでは、「下降」を停止することはできません。 ConstantExpression
を待つことにより、フェーズします。代わりに、null参照に達すると下降を停止します。次に、次のようにルートオブジェクト参照を取得します。
var mi = memberInfos.Pop();
objReference = mi.DeclaringType
.GetField(member.Name, BindingFlags.Static) // or .GetProperty!
.GetValue(null);
&quot; ascend&quot;それ以降のフェーズは以前と同じです。
確かに多くの場合(ルートオブジェクトとしての名前付きパラメーターなど)がありますが、ここまでで基本的な考え方が理解できたので、ここでは省略します。
より簡単な解決策があります:
var pExpression = ((MemberExpression)expr.Body);
var bindingObject = Expression.Lambda(((MemberExpression)pExpression.Expression)).Compile().DynamicInvoke();
ありがとう、スタック-あなたの例は私を大いに助けてくれました!したがって、最初のケースにいくつか追加して貢献したいと思います:
メソッドから値を抽出するには、コードを置き換える必要があります:
// fetch the root object reference:
var constExpr = expr as ConstantExpression;
var objReference = constExpr.Value;
コード付き:
var newExpression = expr as NewExpression;
if (newExpression != null)
{
return newExpression.Constructor.Invoke(newExpression.Arguments.Select(GetObjectValue).ToArray());
}
var methodCallExpr = expr as MethodCallExpression;
if (methodCallExpr != null)
{
var value = methodCallExpr.Method.Invoke(methodCallExpr.Object == null
? null
: GetObjectValue(methodCallExpr.Object),
methodCallExpr.Arguments.Select(GetObjectValue).ToArray());
return value;
}
// fetch the root object reference:
var constExpr = expr as ConstantExpression;
if (constExpr == null)
{
return null;
}
var objReference = constExpr.Value;
// ... the rest remains unchanged
次のような式から値を抽出できる方法
aInstane.MethodCall(anArgument1, anArgument2) or
AType.MethodCall(anArgument1, anArgument2) or
new AType().MethodCall(anArgument1, aInstane.MethodCall(anArgument2, anArgument3))
これは私が単体テストで使用するものです:
internal static INotifyPropertyChanged SubModel < T, TProperty > (T model, Expression < Func < T, TProperty >> pickProperty) where T: INotifyPropertyChanged {
MemberExpression memberExpression = (MemberExpression) pickProperty.Body;
ParameterExpression parameterExpression = pickProperty.Parameters[0];
Expression mem = memberExpression.Expression;
var delegateType = typeof(Func < , > ).MakeGenericType(typeof(T), mem.Type);
LambdaExpression lambdaExpression = Expression.Lambda(delegateType, mem, parameterExpression);
object subModel = lambdaExpression.Compile().DynamicInvoke(model);
return subModel as INotifyPropertyChanged ? ? model;
}