2014-09-06 11 views
7

Poniżej znajduje się prosty kod demonstracyjny mojego problemu.Uzyskanie ConstantExpression.Value, gdy wartość rzeczywista owinięta w DisplayClass z powodu zamknięcia

[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 sposób powoduje VisitConstant powoływać się na dwóch różnych drogach:

1.TestParam -> ->SearchVisitConstant

przez ścieżkę wykonanie stałej ekspresji (1 L) przekazywany do Search Metoda jest rzeczywistą stałą wartością. Tutaj wszystko jest w porządku, aserse udaje się zgodnie z oczekiwaniami. Gdy VisitConstant jest wywoływana przez pierwszą ścieżkę i jej jest .

2.TestParam ->GetStudent ->Search ->VisitConstant

W tej ścieżce wykonanie stałej ekspresji (ID: 1 l), jest pobierana przez GetStudent jako argumentu, i przekazywane do Search sposób wewnątrz zamknięcia.

Problem

Problem polega na drugiej ścieżce wykonywania. Po wywołaniu VisitConstant przez drugą ścieżkę node.Value.GetType() jest MyProject.Tests.ExpressionTests+<>c__DisplayClass0, a ta klasa ma publiczne pole o nazwie id (tak samo jak argument metody) o wartości , która ma wartość 1L.

Pytanie

Jak mogę uzyskać id wartość w drugiej ścieżce? Wiem o zamknięciach, czym jest DisplayClass i dlaczego jest tworzony podczas kompilacji itp. Interesuje mnie tylko uzyskanie jego wartości pola. Jedną rzeczą, o której mogę myśleć, jest refleksja. Z czymś podobnym, ale nie wydaje się zadbane.

node.Value.GetType().GetFields()[0].GetValue(node.Value); 

Bonus Problem

Podczas odtwarzania z kodem dla gettting id wartość Zmieniłem VisitConstant sposób jak poniżej (nie rozwiąże mój problem, choć) i uzyskać wyjątek mówiąc „«przedmiot»robi nie zawierają definicji dla 'id'”

enter image description here

Bonus Pytanie

Jak dynamika są rozwiązywane w czasie rzeczywistym i DisplayClass jest tworzony w czasie kompilacji, dlaczego nie mamy dostęp do jego pól z dynamic? Podczas gdy poniższy kod działa, spodziewałem się, że kod również zadziała.

var st = new {Id = 1L}; 
object o = st; 
dynamic dy = o; 
Assert.AreEqual(1L, dy.Id); 

Odpowiedz

3

Here is an article that explains how to do it, i zawiera kod, który to robi.Zasadniczo, można utworzyć wyrażenie, które reprezentuje właśnie to podwyrażenie, skompilować je do delegata, a następnie wykonać tego delegata. (Artykuł wyjaśnia też, jak rozpoznać podwyrażeń które mogą być ocenione, ale myślę, że nie jesteś tym zainteresowany.)

Używając kodu z artykułu, modyfikując swój kod do następujących będzie działać:

private void Search<T>(Expression<Func<T, bool>> filter) 
{ 
    new MyExpressionVisitor().Visit(Evaluator.PartialEval(filter)); 
} 

Jak dynamika są rozwiązywane w czasie rzeczywistym i DisplayClass jest tworzony w czasie kompilacji, dlaczego nie możemy jej dostęp Pola dynamic?

Bo DisplayClass jest private klasa zagnieżdżona ExpressionTests, więc kod wewnątrz MyExpressionVisitor nie może uzyskać dostępu do swoich członków.

Jeśli utworzysz MyExpressionVisitor klasę zagnieżdżoną wewnątrz ExpressionTests, dynamic rozpocznie pracę nad DisplayClass.

Typy anonimowe nie zachowują się w ten sposób, ponieważ nie są emitowane jako typy zagnieżdżone private.

2

VisitConstant nie pomoże tutaj, ponieważ odbiera kompilator skonstruowaną ConstantExpression który używa prywatna anonimowej klasy do przechowywania wartości lambda został zamknięty przez (The DisplayClassxxx)

Zamiast tego, powinniśmy zastąpić VisitMember metody i sprawdzić jego MemberExpression który ma już ConstantExpression jako wewnętrzny Expression.

Oto test pracy z niewielkim odbiciem.

[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(); 

    } 
} 
+0

Nice! Jest to najszybszy sposób uzyskania podstawowych wartości zamknięcia, to powinna być zaakceptowana odpowiedź. – jorgebg

Powiązane problemy