Получение ConstantExpression.Value, когда фактическое значение обернуто в DisplayClass из-за закрытия
Ниже приведен простой демонстрационный код моей проблемы.
[TestClass]
public class ExpressionTests
{
[TestMethod]
public void TestParam()
{
Search<Student>(s => s.Id == 1L);
GetStudent(1L);
}
private void GetStudent(long id)
{
Search<Student>(s => s.Id == id);
}
private void Search<T>(Expression<Func<T, bool>> filter)
{
var visitor = new MyExpressionVisitor();
visitor.Visit(filter);
}
}
public class MyExpressionVisitor : ExpressionVisitor
{
protected override Expression VisitConstant(ConstantExpression node)
{
Assert.AreEqual(1L, node.Value);
return base.VisitConstant(node);
}
}
TestParam
причины метода VisitConstant
быть вызванным по двум разным путям:
1. TestParam
-> Search
-> VisitConstant
В этом пути выполнения константное выражение (1L) передается Search
Метод является реальной постоянной величиной. Здесь все ок, утверждать удается как положено. когда VisitConstant
вызывается по первому пути node.Value.GetType()
является Int64
И его .Value
является 1L
,
2. TestParam
-> GetStudent
-> Search
-> VisitConstant
В этом пути выполнения константное выражение (id: 1L), берется GetStudent
в качестве аргумента и передал Search
метод внутри замыкания.
проблема
Проблема на втором пути выполнения. когда VisitConstant
вызывается через второй путь node.Value.GetType()
является MyProject.Tests.ExpressionTests+<>c__DisplayClass0
и этот класс имеет открытое поле с именем id
(такой же как GetStudent
аргумент метода), который имеет значение 1L
,
Вопрос
Как я могу получить id
значение во втором пути? Я знаю о замыканиях, что DisplayClass
и почему он создается во время компиляции и т. д. Меня интересует только получение значения этого поля. Одна вещь, о которой я могу думать, это через отражение. С чем-то вроде ниже, но это не кажется опрятным.
node.Value.GetType().GetFields()[0].GetValue(node.Value);
Проблема с бонусами
Играя с кодом для получения id
значение я изменил VisitConstant
метод как ниже (который не решит мою проблему все же) и получит исключение, говорящее, что "объект" не содержит определения для "id"
Бонусный вопрос
Как решаются динамики во время выполнения и DisplayClass
создается во время компиляции, почему мы не можем получить доступ к его полям с dynamic
? Хотя приведенный ниже код работает, я ожидал, что код тоже будет работать.
var st = new {Id = 1L};
object o = st;
dynamic dy = o;
Assert.AreEqual(1L, dy.Id);
2 ответа
Вот статья, которая объясняет, как это сделать, и включает код, который это делает. По сути, вы можете создать выражение, представляющее только это подвыражение, скомпилировать его в делегат и затем выполнить этот делегат. (В статье также объясняется, как определить подвыражения, которые можно оценить, но, думаю, вас это не интересует.)
Используя код из статьи, измените ваш код следующим образом:
private void Search<T>(Expression<Func<T, bool>> filter)
{
new MyExpressionVisitor().Visit(Evaluator.PartialEval(filter));
}
Как решаются динамики во время выполнения и
DisplayClass
создается во время компиляции, почему мы не можем получить доступ к его полям сdynamic
?
Потому что DisplayClass
это private
класс вложенный внутрь ExpressionTests
так код внутри MyExpressionVisitor
не может получить доступ к своим членам.
Если вы делаете MyExpressionVisitor
вложенный класс внутри ExpressionTests
, dynamic
начнет работать над DisplayClass
,
Анонимные типы не ведут себя таким образом, потому что они не выводятся как вложенные private
типы.
VisitConstant
здесь не поможет, так как получает компилятор ConstantExpression
который использует объект частного анонимного класса для хранения значений, лямбда была закрыта (DisplayClassxxx
)
Вместо этого мы должны переопределить VisitMember
метод и осмотреть его MemberExpression
что уже есть ConstantExpression
как внутренний Expression
,
Вот рабочий тест с небольшим размышлением.
[TestClass]
public class UnitTest2
{
[TestMethod]
public void TestMethod2()
{
Search<Student>(s => s.Id == 1L);
GetStudent(1L);
}
private void GetStudent(long id)
{
Search<Student>(s => s.Id == id);
}
private void Search<T>(Expression<Func<T, bool>> filter)
{
var visitor = new MyExpressionVisitor2();
visitor.Visit(filter.Body);
}
}
//ExpressionVisitor
public class MyExpressionVisitor2 : ExpressionVisitor
{
protected override Expression VisitMember(MemberExpression node)
{
switch (node.Expression.NodeType)
{
case ExpressionType.Constant:
case ExpressionType.MemberAccess:
{
var cleanNode = GetMemberConstant(node);
//Test
Assert.AreEqual(1L, cleanNode.Value);
return cleanNode;
}
default:
{
return base.VisitMember(node);
}
}
}
private static ConstantExpression GetMemberConstant(MemberExpression node)
{
object value;
if (node.Member.MemberType == MemberTypes.Field)
{
value = GetFieldValue(node);
}
else if (node.Member.MemberType == MemberTypes.Property)
{
value = GetPropertyValue(node);
}
else
{
throw new NotSupportedException();
}
return Expression.Constant(value, node.Type);
}
private static object GetFieldValue(MemberExpression node)
{
var fieldInfo = (FieldInfo)node.Member;
var instance = (node.Expression == null) ? null : TryEvaluate(node.Expression).Value;
return fieldInfo.GetValue(instance);
}
private static object GetPropertyValue(MemberExpression node)
{
var propertyInfo = (PropertyInfo)node.Member;
var instance = (node.Expression == null) ? null : TryEvaluate(node.Expression).Value;
return propertyInfo.GetValue(instance, null);
}
private static ConstantExpression TryEvaluate(Expression expression)
{
if (expression.NodeType == ExpressionType.Constant)
{
return (ConstantExpression)expression;
}
throw new NotSupportedException();
}
}