web-dev-qa-db-fra.com

variable '' de type '' référencée à partir de la portée '', mais elle n'est pas définie

Eh bien, le code suivant s'explique de lui-même; Je veux combiner deux expressions en une en utilisant l'opérateur And. La dernière ligne provoque l'erreur d'exécution:

Informations supplémentaires: variable 'y' de type 'System.String' référencée depuis la portée '', mais elle n'est pas définie

Code:

Expression<Func<string, bool>> e1 = y => y.Length < 100;
Expression<Func<string, bool>> e2 = y => y.Length < 200;

var e3 = Expression.And(e1.Body, e2.Body);

var e4 = Expression.Lambda<Func<string, bool>>(e3, e1.Parameters.ToArray());
e4.Compile(); // <--- causes run-time error
27
Hans

Merci, tout le monde a collaboré.

Comme l'a souligné @dasblinkenlight, les deux paramètres des deux expressions ne sont pas identiques. Raison? Eh bien, c'est l'astuce du compilateur. Lors de la compilation, il crée une classe pour chaque expression et nomme chaque paramètre quelque chose comme xxx1, xxx2, ... complètement différent des noms d'origine.

Et la réponse pour .Net 4.0+:

Comment combiner deux lambdas

11
Hans

Le problème est que les objets d'expression de paramètre qui représentent la variable y dans les expressions e1 Et e2 Sont différents. Le fait que les deux variables soient nommées de la même manière et aient le même type n'a pas d'importance: e1.Parameters.First() et e2.Parameters.First() n'est pas le même objet.

Cela provoque le problème que vous voyez: seul le paramètre de e1y est disponible pour Lambda<>, Tandis que le paramètre de e2y est hors de portée.

Pour résoudre ce problème, utilisez les API Expression pour créer e1 Et e2. De cette façon, vous seriez en mesure de partager l'expression de paramètre entre eux, éliminant ainsi le problème de portée.

19
dasblinkenlight

Comme indiqué dans l'autre réponse, vous avez deux expressions où les deux ont un paramètre nommé y. Ceux-ci ne sont pas automatiquement liés les uns aux autres.

Pour compiler correctement votre expression, vous devez spécifier les deux paramètres de l'expression source:

Expression<Func<string, bool>> e1 = (y => y.Length > 0);
Expression<Func<string, bool>> e2 = (y => y.Length < 5);

var e3 = Expression.And(e1.Body, e2.Body);

// (string, string) by adding both expressions' parameters.
var e4 = Expression.Lambda<Func<string, string, bool>>(e3, new[] 
{ 
    e1.Parameters[0], 
    e2.Parameters[0] 
});

Func<string, string, bool> compiledExpression = e4.Compile();

bool result = compiledExpression("Foo", "Foo");

Bien sûr, vous voudriez une expression qui combine les deux expressions avec un seul paramètre. Vous pouvez reconstruire les expressions comme ceci:

ParameterExpression param = Expression.Parameter(typeof(string), "y");
var lengthPropertyExpression = Expression.Property(param, "Length");

var e1 = Expression.GreaterThan(lengthPropertyExpression, Expression.Constant(0));
var e2 = Expression.LessThan(lengthPropertyExpression, Expression.Constant(5));

var e3 = Expression.AndAlso(e1, e2);

var e4 = Expression.Lambda<Func<string, bool>>(e3, new[] { param });

Func<string, bool> compiledExpression = e4.Compile();

bool result = compiledExpression("Foo");

Quant à votre commentaire que vous ne voulez pas reconstruire l'expression, mais faites-le sur le corps et les paramètres d'une expression existante: cela fonctionne en utilisant ExpressionRewriter de Combinaison de deux expressions lambda en c # et AndAlso from Remplacement du nom du paramètre dans le corps d'une expression :

Expression<Func<string, bool>> e1 = (y => y.Length > 0);
Expression<Func<string, bool>> e2 = (z => z.Length < 10);

var e3 = ParameterReplacer.AndAlso<string>(e1, e2);

Func<string, bool> compiledExpression = e3.Compile();

bool result = compiledExpression("Foo");
13
CodeCaster