Деревья выражений и ленивые вычисления в C # - PullRequest
0 голосов
/ 28 июня 2018

У меня есть небольшой кусочек кода, в котором я беру массив ParameterExpression строк и преобразую определенный индекс в целевой тип. Я делаю это либо путем вызова Parse (если тип является примитивным), либо путем попытки необработанного преобразования (возможно, в строку или неявную строку).

static Expression ParseOrConvert(ParameterExpression strArray, ConstantExpression index, ParameterInfo paramInfo)
{
    Type paramType = paramInfo.ParameterType;

    Expression paramValue = Expression.ArrayIndex(strArray, index);

    if (paramType.IsPrimitive) {
        MethodInfo parseMethod = paramType.GetMethod("Parse", new[] { typeof(string) });
        // Fetch Int32.Parse(), etc.

        // Parse paramValue (a string) to target type
        paramValue = Expression.Call(parseMethod, paramValue);
    }
    else {
        // Else attempt a raw conversion
        paramValue = Expression.Convert(paramValue, paramType);
    }

    return paramValue;
}

Это работает, но я пытаюсь переписать условное выражение как таковое.

paramValue = Expression.Condition(
    Expression.Constant(paramType.IsPrimitive),
    Expression.Call(parseMethod, paramValue),
    Expression.Convert(paramValue, paramType)
);

Это всегда приводит к System.InvalidOperationException, предположительно, потому что он пытается оба преобразования. Я считаю, что второй стиль более интуитивно понятен для написания, поэтому это неудачно.

Могу ли я записать это так, чтобы отложить оценку до того момента, когда значения действительно необходимы?

Ответы [ 2 ]

0 голосов
/ 28 июня 2018

Часто отладка похожа на журналистику ... В журналистике есть пять Вт : , кто , , что , , где , когда , почему (плюс как ) .. и в программировании это похоже. кто выдает исключение (то есть что )? Давайте сделаем код более простым для отладки:

static Expression ParseOrConvert(ParameterExpression strArray, ConstantExpression index, ParameterInfo paramInfo)
{
    Type paramType = paramInfo.ParameterType;

    Expression paramValue = Expression.ArrayIndex(strArray, index);
    MethodInfo parseMethod = paramType.GetMethod("Parse", new[] { typeof(string) });

    var isPrimitive = Expression.Constant(paramType.IsPrimitive);
    var call = Expression.Call(parseMethod, paramValue);
    var convert = Expression.Convert(paramValue, paramType);

    var paramValue2 = Expression.Condition(
        isPrimitive,
        call,
        convert
    );

    return paramValue2;
}

А потом назовите это как:

public static void MyMethod(int par1)
{
}

, а затем

ParameterExpression strArray = Expression.Parameter(typeof(string[]));

// paramType int
var paramInfo = typeof(Program).GetMethod("MyMethod").GetParameters()[0];

var result = ParseOrConvert(strArray, Expression.Constant(0), paramInfo);

Теперь ... Кто выбрасывает исключение? Expression.Convert(paramValue, paramType) выдает исключение ... А почему ? Потому что вы пытаетесь сделать:

string paramValue = ...;
convert = (int)paramValue;

Это, безусловно, незаконно! Даже «мертвый» код (код, который не может быть достигнут) должен быть «компилируемым» в .NET (на языке IL). Таким образом, ваша ошибка пытается ввести какой-то недопустимый мертвый код в ваше выражение, которое будет выглядеть так:

string paramValue = ...;
isPrimitive = true ? int.Parse(paramValue) : (int)paramValue;

Это не скомпилируется в C # и, вероятно, даже не может быть написано в коде IL. И классы Expression бросают на него.

0 голосов
/ 28 июня 2018

Выражения представляют код как данные, ветви true и false здесь не оцениваются; это не «попытка обоих преобразований». Вместо этого он пытается построить дерево выражений, которое представляет каждое преобразование. Однако условие, являющееся Constant, заполняется условным условием в зависимости от типа.

Вы строите выражение со следующей структурой:

var result = true // <`true` or `false` based on the type T> 
    ? T.Parse(val)
    : (T) val;

Когда T равно int (и, следовательно, «Test» является константой true), это не компилируется, потому что нет допустимого приведения от string до int, хотя во время выполнения это будет всегда оценивать / выполнять int.Parse(val).

Когда T равен Foo, он будет скомпилирован, если Foo имеет как статический метод Parse(string val), так и явный оператор приведения

public class Foo
{
    public static Foo Parse(string fooStr)
    {
        return default(Foo);
    }

    public static explicit operator Foo(string fooStr)
    {
        return default(Foo);
    }
}

Даже если он будет выполнять только явный оператор приведения, потому что Foo не является примитивным.

Ваш исходный код на самом деле уже создает выражение, которое будет использовать «правильную» стратегию преобразования, основанную на T, не пытаясь скомпилировать / оценить другое. Если это уже не работает для вас, я подозреваю, что это связано с тем, что участвующие типы не имеют явного приведения, определенного из string.

Кроме того, я бы не рекомендовал повторно использовать paramValue в качестве как исходного (не преобразованного), так и преобразованного значения, что делает отладку намного сложнее, чем это необходимо, среди прочего.

...