C# Выражение Linq - Как получить экземпляр выражения - PullRequest
2 голосов
/ 06 апреля 2020

Есть ли способ получить используемый экземпляр из выражения linq в качестве ссылки или что-то еще?

На данный момент у меня есть следующее, чтобы объявить мое выражение:

var testClass = new ClassToTest();
otherClass.RunTest(() => testClass.NumberOfCars);

Теперь я хочу получить использованный объект экземпляра из выражения. Так, чтобы позже я мог получить другое свойство динамически, например. Я знаю, что могу просто передать больше параметров самой функции, чтобы использовать их, но мне интересно, возможно ли это и по выражению.

Обновление

public class ClassToTest
{
    public int NumberOfCars;
    public int NumberOfChildren;

    public ClassToTest()
    {
        NumberOfCars = 1;
        NumberOfChildren = 2;
    }
}

public class TestingClass<TResult>
{
    public bool RunTest(Expression<Func<TResult>> expression)
    {
        // var numberOfCars = get info with the help of the expression and reflection 
        // var instance = get used instance in expression
        if (instance.NumberOfChildren > 2 && numberOfCars == 1)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

var otherClass = new TestingClass<int>();

Это просто основа c пример, чтобы понять мою проблему. Пример может быть лучше решен, чтобы передать оба значения в качестве параметра и проверить их, но мне было интересно, возможно ли это заархивировать.

1 Ответ

1 голос
/ 06 апреля 2020

Expression - это дерево того, что компилируется компилятором C#. Вы должны использовать ExpressionVisitor для извлечения информации Expression

Ваш базовый пример c не так уж тривиален. Вы объявляете переменную вне выражения и используете ее внутри. Это называется закрытием. Вы также используете поле вместо свойства, которое приведет к другому выражению.

class Program
{
    static void Main(string[] args)
    {
        ClassToTest testClass = new ClassToTest();
        new TestingClass<Int32>().RunTest(() => testClass.NumberOfCars);
    }
}

Будет скомпилировано как

internal class Program
{
  [CompilerGenerated]
  private sealed class <>c__DisplayClass0_0
  {
    public ClassToTest testClass;
  }

  private static void Main(string[] args)
  {
    <>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
    <>c__DisplayClass0_.testClass = new ClassToTest();
    new TestingClass<int>().RunTest(
      Expression.Lambda<Func<int>>(
        Expression.Field(
          Expression.Field(
            Expression.Constant(<>c__DisplayClass0_, typeof(<>c__DisplayClass0_0)),
            "testClass", //FieldInfo.GetFieldFromHandle((RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/)),
          "NumberOfCars", //FieldInfo.GetFieldFromHandle((RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/)), 
         Array.Empty<ParameterExpression>()));
  }
}

Чтобы получить объект экземпляра без использования посетителя выражения, вы можете сделать:

public bool RunTest(Expression<Func<TResult>> expression)
{
    // NumberOfCars
    var e1 = (MemberExpression)expression.Body;
    // testClass
    var e2 = (MemberExpression)e1.Expression;
    // closureObject 
    var e3 = (ConstantExpression)e2.Expression; 

    var closureObject = e3.Value;
    var testClassObject = ((FieldInfo)e2.Member).GetValue(closureObject);
    var numberOfCars = ((FieldInfo)e1.Member).GetValue(testClassObject);
 }

Но вы никогда не должны манипулировать дерево выражений, подобное этому. Всегда используйте ExpressionVisitor и всегда понимайте, что вы посещаете.

Следующий посетитель - пример, который будет работать для вашего указанного c сценария.

public class XVisitor : ExpressionVisitor
{
    public static Object XVisit(Expression e)
    {
        XVisitor visitor = new XVisitor();
        visitor.Visit(e);

        return visitor._instance;
    }

    private Object _instance;

    protected override Expression VisitMember(MemberExpression node)
    {
        if (node.Expression.Type.GetCustomAttribute<CompilerGeneratedAttribute>() != null)
        {
            Object closureInstance = ((ConstantExpression)node.Expression).Value;
            this._instance = ((FieldInfo)node.Member).GetValue(closureInstance);
        }
        return base.VisitMember(node);
    }
}

С помощью ExpressionVisitor вы можете делать почти все, что вы хотите, с помощью выражения, но вам нужно хорошее понимание того, как все работает в компиляторе C#.

...