create - lambda expressions c# where
Acceda al valor de una expresión miembro (8)
Si tengo un producto
var p = new Product { Price = 30 };
y tengo la siguiente consulta linq.
var q = repo.Products().Where(x=>x.Price == p.Price).ToList()
En un proveedor de IQueryable, recupero MemberExpression para el precio p que contiene una expresión constante, sin embargo, parece que no puedo recuperar el valor "30".
Actualización He intentado esto, pero parece que no funciona.
var memberExpression = (MemberExpression)GetRootConstantExpression(m);
var fi = (PropertyInfo)memberExpression.Member;
var val = fi.GetValue(((ConstantExpression)memberExpression.Expression).Value, null);
Aclamaciones.
¿Y qué estás tratando de lograr exactamente?
Porque para acceder al valor de Price
, tendrías que hacer algo como:
var valueOfPrice = q[0].Price;
La expresión constante apuntará a una clase de captura generada por el compilador. No he incluido los puntos de decisión, etc., pero he aquí cómo obtener 30 de eso:
var p = new Product { Price = 30 };
Expression<Func<Product, bool>> predicate = x => x.Price == p.Price;
BinaryExpression eq = (BinaryExpression)predicate.Body;
MemberExpression productToPrice = (MemberExpression)eq.Right;
MemberExpression captureToProduct = (MemberExpression)productToPrice.Expression;
ConstantExpression captureConst = (ConstantExpression)captureToProduct.Expression;
object product = ((FieldInfo)captureToProduct.Member).GetValue(captureConst.Value);
object price = ((PropertyInfo)productToPrice.Member).GetValue(product, null);
price
ahora es 30
. Tenga en cuenta que asumo que Price
es una propiedad, pero en realidad escribiría un método GetValue
que maneja propiedad / campo.
Puede compilar e invocar una expresión lambda cuyo cuerpo es el acceso de miembro:
private object GetValue(MemberExpression member)
{
var objectMember = Expression.Convert(member, typeof(object));
var getterLambda = Expression.Lambda<Func<object>>(objectMember);
var getter = getterLambda.Compile();
return getter();
}
La evaluación local es una técnica común al analizar árboles de expresión. LINQ to SQL hace esto exactamente en bastantes lugares.
Puedes usar lo siguiente:
var price = p.Price;
var q = repo.Products().Where(x=>x.Price == price).ToList()
Si tuvieras una clase:
public class Item
{
public int Id { get; set; }
}
y una instancia del objeto:
var myItem = new Item { Id = 7 };
Puede obtener el valor de Id usando una expresión usando el siguiente código:
Expression<Func<Item, int>> exp = x => x.Id;
var me = exp.Body as MemberExpression;
var propInfo = me.Member as PropertyInfo;
var value = propInfo.GetValue(myItem, null);
el valor contendrá "7"
Usar Expression.Lambda(myParameterlessExpression).Compile().Invoke()
tiene varios inconvenientes:
-
.Compile()
es lento . Puede llevar varios milisegundos completar incluso fragmentos de expresión pequeños. Sin embargo, laInvoke
invocación es súper rápida después, solo toma unos pocos nanosegundos para expresiones aritméticas simples o accesos de miembros. -
.Compile()
generará (emitirá) código MSIL. Eso puede sonar perfecto (y explica la excelente velocidad de ejecución), pero el problema es: ¡ese código ocupa memoria, que no puede liberarse antes de que la aplicación finalice , incluso cuando el GC recolectó la referencia de delegado!
Uno puede evitar Compile()
completo para evitar estos problemas o almacenar en caché los delegados compilados para volver a usarlos. This pequeña biblioteca ofrece tanto la interpretación de Expressions
como la compilación en caché , donde todas las constantes y cierres de la expresión se reemplazan por parámetros adicionales automáticamente, que luego se vuelven a insertar en un cierre, que se devuelve al usuario. Ambos procesos están bien probados, se usan en producción, ambos tienen sus pros y sus contras, pero son 100 veces más rápidos que Compile()
y evitan la pérdida de memoria.
q
es del tipo List<Product>
. La Lista no tiene una propiedad de Precio, solo los Productos individuales.
El primer o el último Producto tendrá un precio.
q.First().Price
q.Last().Price
Si sabes que solo hay uno en la colección, también puedes aplanarlo usando Single
q.Single().Price
MemberExpression right = (MemberExpression)((BinaryExpression)p.Body).Right;
Expression.Lambda(right).Compile().DynamicInvoke();