У меня была та же проблема, но несколько более сложная, и ответ Дарина Димитрова дал мне хорошее начало. Я опубликую свои результаты здесь, несмотря на то, что это «старый» вопрос.
Случай 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
. Основная идея получения любой ссылки на объект в этом дереве заключается в следующем:
Спускайтесь в дерево выражений вдоль осей .Expression
, пока не достигнете узла ConstantExpression
.
Получить свойство этого узла .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
. Вместо этого вы прекращаете спускаться, когда достигнете нулевой ссылки. Затем вы получаете ссылку на корневой объект следующим образом:
var mi = memberInfos.Pop();
objReference = mi.DeclaringType
.GetField(member.Name, BindingFlags.Static) // or .GetProperty!
.GetValue(null);
Фаза «вознесения», начиная с этого момента, такая же, как и раньше.
Конечно, есть и другие случаи (например, именованные параметры в качестве корневого объекта), но я надеюсь, что к настоящему времени у меня есть основная идея, поэтому я остановлюсь здесь.