Arbres d'expression et évaluation paresseuse en C #

c# expression-trees lazy-evaluation

Question

J'ai ce petit morceau de code où je prends un tableau de chaînes ParameterExpression et convertis un index particulier en un type cible. Je le fais soit en appelant Parse (si le type est primitif) ou en tentant une conversion brute (éventuellement en une chaîne ou une chaîne implicite).

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;
}

Cela fonctionne, mais j'essaie de réécrire le conditionnel en tant que tel.

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

Cela se traduit toujours par System.InvalidOperationException , probablement parce qu'il tente les deux conversions. Je trouve le deuxième style plus intuitif à écrire, c’est donc regrettable.

Puis-je écrire cela d'une manière qui diffère de l'évaluation au moment où les valeurs sont réellement nécessaires?

Réponse acceptée

Les expressions représentent le code en tant que données, les branches vraies et fausses ne sont pas évaluées ici; il ne s'agit pas de "tenter les deux conversions". Au lieu de cela, il essaie de créer un arbre d'expression qui représente chaque conversion. La condition cependant, étant une Constant , est en train d'être intégrée dans la condition basée sur le type avec impatience.

Vous construisez une expression avec la structure suivante:

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

Lorsque T est int (et donc "Test" est la constante true ), cela ne compile pas car il n'y a pas de int.Parse(val) valide de string en int , même si au moment de l'exécution, il évaluerait / exécuterait toujours int.Parse(val) .

Lorsque T est Foo , cela compilerait lorsque Foo aurait à la fois une méthode statique Parse(string val) et un opérateur de conversion explicite.

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

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

Même s'il n'exécutera jamais l'opérateur de distribution explicite, car Foo n'est pas primitif.

En réalité, votre code d'origine génère déjà une expression qui utilisera la stratégie de conversion "correcte" basée sur T sans essayer de compiler / évaluer l'autre. Si cela ne fonctionne pas déjà pour vous, je suppose que c'est parce que les types impliqués n'ont pas de distribution explicite définie à partir de string .

En paramValue , je déconseillerais de réutiliser paramValue en tant que paramValue d'origine (non convertie) et convertie, ce qui rend le débogage beaucoup plus difficile qu'il ne devrait l'être, entre autres.


Réponse populaire

Le débogage ressemble souvent au journalisme ... Dans le journalisme, il existe cinq principes : qui , quoi , , quand , pourquoi (et comment ) et la programmation est similaire. qui jette l'exception (c'est le quoi )? Rendons le code plus facile à déboguer:

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;
}

Et puis appelez comme ça:

public static void MyMethod(int par1)
{
}

et alors

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

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

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

Maintenant ... qui jette l'exception? Expression.Convert(paramValue, paramType) lève une exception ... Et pourquoi ? Parce que vous essayez de faire un:

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

C'est sûrement illégal! Même le code "mort" (code inaccessible) doit être "compilable" en .NET (en langage IL). Donc, votre erreur essaie d'introduire un code mort illégal dans votre expression, ce serait:

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

Cela ne compilerait pas en C # et ne pourrait probablement même pas être écrit en code IL. Et les classes Expression jettent dessus.




Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi