.NET Framework
Alberi di espressione
Ricerca…
Osservazioni
Gli alberi di espressione sono strutture di dati utilizzate per rappresentare le espressioni di codice in .NET Framework. Possono essere generati dal codice e attraversati a livello di codice per tradurre il codice in un'altra lingua o eseguirlo. Il generatore più popolare di Expression Trees è il compilatore C # stesso. Il compilatore C # può generare alberi di espressioni se viene assegnata un'espressione lambda a una variabile di tipo Expression <Func <... >>. Di solito questo accade nel contesto di LINQ. Il consumatore più popolare è il provider LINQ di Entity Framework. Consuma gli alberi di espressioni dati a Entity Framework e genera un codice SQL equivalente che viene quindi eseguito sul database.
Albero delle espressioni semplici generato dal compilatore C #
Considera il seguente codice C #
Expression<Func<int, int>> expression = a => a + 1;
Poiché il compilatore C # rileva che l'espressione lambda è assegnata a un tipo di espressione anziché a un tipo delegato, genera un albero di espressioni approssimativamente equivalente a questo codice
ParameterExpression parameterA = Expression.Parameter(typeof(int), "a");
var expression = (Expression<Func<int, int>>)Expression.Lambda(
Expression.Add(
parameterA,
Expression.Constant(1)),
parameterA);
La radice dell'albero è l'espressione lambda che contiene un corpo e un elenco di parametri. Il lambda ha 1 parametro chiamato "a". Il corpo è una singola espressione di tipo CLR BinaryExpression e NodeType di Aggiungi. Questa espressione rappresenta l'aggiunta. Ha due sottoespressioni denotate come sinistra e destra. A sinistra è ParameterExpression per il parametro "a" e Right è una espressione costante con il valore 1.
L'uso più semplice di questa espressione è la stampa:
Console.WriteLine(expression); //prints a => (a + 1)
Che stampa il codice C # equivalente.
L'albero delle espressioni può essere compilato in un delegato C # ed eseguito dal CLR
Func<int, int> lambda = expression.Compile();
Console.WriteLine(lambda(2)); //prints 3
Solitamente le espressioni sono tradotte in altri linguaggi come SQL, ma possono anche essere utilizzate per invocare membri privati, protetti e interni di tipi pubblici o non pubblici come alternativa a Reflection.
creazione di un predicato del campo modulo == valore
Per creare un'espressione come _ => _.Field == "VALUE"
in fase di runtime.
Dato un predicato _ => _.Field
e un valore stringa "VALUE"
, crea un'espressione che verifica se il predicato è vero o meno.
L'espressione è adatta per:
-
IQueryable<T>
,IEnumerable<T>
per testare il predicato. - Entity Framework o
Linq
toSQL
per creare una clausolaWhere
che verifica il predicato.
Questo metodo creerà un'espressione Equal
appropriata che verifica se Field
equivale a "VALUE"
.
public static Expression<Func<T, bool>> BuildEqualPredicate<T>(
Expression<Func<T, string>> memberAccessor,
string term)
{
var toString = Expression.Convert(Expression.Constant(term), typeof(string));
Expression expression = Expression.Equal(memberAccessor.Body, toString);
var predicate = Expression.Lambda<Func<T, bool>>(
expression,
memberAccessor.Parameters);
return predicate;
}
Il predicato può essere utilizzato includendo il predicato in un metodo di estensione Where
.
var predicate = PredicateExtensions.BuildEqualPredicate<Entity>(
_ => _.Field,
"VALUE");
var results = context.Entity.Where(predicate).ToList();
Espressione per il recupero di un campo statico
Avere un esempio digita in questo modo:
public TestClass
{
public static string StaticPublicField = "StaticPublicFieldValue";
}
Possiamo recuperare il valore di StaticPublicField:
var fieldExpr = Expression.Field(null, typeof(TestClass), "StaticPublicField");
var labmda = Expression.Lambda<Func<string>>(fieldExpr);
Può quindi essere compilato in un delegato per il recupero del valore del campo.
Func<string> retriever = lambda.Compile();
var fieldValue = retriever();
// Il risultato fieldValue è StaticPublicFieldValue
InvocationExpression Class
La classe InvocationExpression consente il richiamo di altre espressioni lambda che fanno parte dello stesso albero di espressione.
Li creo con il metodo Expression.Invoke
statico.
Problema Vogliamo ottenere gli articoli che hanno "auto" nella descrizione. Dobbiamo controllarlo per null prima di cercare una stringa all'interno, ma non vogliamo che venga chiamata eccessivamente, in quanto il calcolo potrebbe essere costoso.
using System;
using System.Linq;
using System.Linq.Expressions;
public class Program
{
public static void Main()
{
var elements = new[] {
new Element { Description = "car" },
new Element { Description = "cargo" },
new Element { Description = "wheel" },
new Element { Description = null },
new Element { Description = "Madagascar" },
};
var elementIsInterestingExpression = CreateSearchPredicate(
searchTerm: "car",
whereToSearch: (Element e) => e.Description);
Console.WriteLine(elementIsInterestingExpression.ToString());
var elementIsInteresting = elementIsInterestingExpression.Compile();
var interestingElements = elements.Where(elementIsInteresting);
foreach (var e in interestingElements)
{
Console.WriteLine(e.Description);
}
var countExpensiveComputations = 0;
Action incCount = () => countExpensiveComputations++;
elements
.Where(
CreateSearchPredicate(
"car",
(Element e) => ExpensivelyComputed(
e, incCount
)
).Compile()
)
.Count();
Console.WriteLine("Property extractor is called {0} times.", countExpensiveComputations);
}
private class Element
{
public string Description { get; set; }
}
private static string ExpensivelyComputed(Element source, Action count)
{
count();
return source.Description;
}
private static Expression<Func<T, bool>> CreateSearchPredicate<T>(
string searchTerm,
Expression<Func<T, string>> whereToSearch)
{
var extracted = Expression.Parameter(typeof(string), "extracted");
Expression<Func<string, bool>> coalesceNullCheckWithSearch =
Expression.Lambda<Func<string, bool>>(
Expression.AndAlso(
Expression.Not(
Expression.Call(typeof(string), "IsNullOrEmpty", null, extracted)
),
Expression.Call(extracted, "Contains", null, Expression.Constant(searchTerm))
),
extracted);
var elementParameter = Expression.Parameter(typeof(T), "element");
return Expression.Lambda<Func<T, bool>>(
Expression.Invoke(
coalesceNullCheckWithSearch,
Expression.Invoke(whereToSearch, elementParameter)
),
elementParameter
);
}
}
Produzione
element => Invoke(extracted => (Not(IsNullOrEmpty(extracted)) AndAlso extracted.Contains("car")), Invoke(e => e.Description, element))
car
cargo
Madagascar
Predicate is called 5 times.
La prima cosa da notare è come l'accesso vero e proprio, racchiuso in un richiamo:
Invoke(e => e.Description, element)
, e questa è l'unica parte che tocca e.Description
e al suo posto, il parametro extracted
di tipo string
viene passato a quello successivo:
(Not(IsNullOrEmpty(extracted)) AndAlso extracted.Contains("car"))
Un'altra cosa importante da notare qui è AndAlso
. Calcola solo la parte sinistra, se la prima parte restituisce 'falso'. È un errore comune utilizzare l'operatore bit per bit "And" al posto di esso, che calcola sempre entrambe le parti e non riuscirebbe con una NullReferenceException in questo esempio.