.NET Framework
Uttrycksträd
Sök…
Anmärkningar
Uttrycksträd är datastrukturer som används för att representera koduttryck i .NET Framework. De kan genereras med kod och genomgås programmatiskt för att översätta koden till ett annat språk eller köra den. Den mest populära generatoren för Expression Trees är själva C # -kompilatorn. C # -kompilatorn kan generera uttrycksträd om ett lambda-uttryck tilldelas en variabel av typen Expression <Func <... >>. Vanligtvis händer detta i samband med LINQ. Den mest populära konsumenten är Entity Framework's LINQ-leverantör. Det förbrukar uttrycksträd som ges till Entity Framework och genererar motsvarande SQL-kod som sedan körs mot databasen.
Enkelt uttrycksträd genererat av C # Compiler
Tänk på följande C # -kod
Expression<Func<int, int>> expression = a => a + 1;
Eftersom C # -kompileraren ser att lambda-uttrycket är tilldelat en uttryckstyp snarare än en delegattyp genererar det ett uttrycksträd som är ungefär motsvarande denna kod
ParameterExpression parameterA = Expression.Parameter(typeof(int), "a");
var expression = (Expression<Func<int, int>>)Expression.Lambda(
Expression.Add(
parameterA,
Expression.Constant(1)),
parameterA);
Trädets rot är lambda-uttrycket som innehåller en kropp och en lista med parametrar. Lambda har en parameter kallad "a". Kroppen är ett enda uttryck av CLR-typ BinaryExpression och NodeType of Add. Detta uttryck representerar tillägg. Det har två subexpressions betecknade vänster och höger. Vänster är ParameterExpression för parametern "a" och höger är en ConstantExpression med värdet 1.
Den enklaste användningen av detta uttryck är att skriva ut det:
Console.WriteLine(expression); //prints a => (a + 1)
Vilket skriver ut motsvarande C # -kod.
Uttrycksträdet kan sammanställas till en C #-delegat och köras av CLR
Func<int, int> lambda = expression.Compile();
Console.WriteLine(lambda(2)); //prints 3
Vanligtvis översätts uttryck till andra språk som SQL, men kan också användas för att åberopa privata, skyddade och interna medlemmar av offentliga eller icke-offentliga typer som alternativ till reflektion.
bygga ett predikat för formfält == värde
För att bygga upp ett uttryck som _ => _.Field == "VALUE"
vid körning.
Med ett predikat _ => _.Field
och ett strängvärde "VALUE"
, skapar du ett uttryck som testar om predikatet är sant eller inte.
Uttrycket är lämpligt för:
-
IQueryable<T>
,IEnumerable<T>
att testa predikatet. - entitetsram eller
Linq
tillSQL
att skapa enWhere
klausul som testar predikatet.
Denna metod bygger ett lämpligt Equal
uttryck som testar om Field
lika med "VALUE"
eller inte.
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;
}
Predikatet kan användas genom att inkludera predikatet i en Where
förlängningsmetod.
var predicate = PredicateExtensions.BuildEqualPredicate<Entity>(
_ => _.Field,
"VALUE");
var results = context.Entity.Where(predicate).ToList();
Uttryck för att hämta ett statiskt fält
Med ett exempel på detta sätt:
public TestClass
{
public static string StaticPublicField = "StaticPublicFieldValue";
}
Vi kan hämta värdet på StaticPublicField:
var fieldExpr = Expression.Field(null, typeof(TestClass), "StaticPublicField");
var labmda = Expression.Lambda<Func<string>>(fieldExpr);
Det kan sedan dvs. sammanställas till en delegat för att hämta fältvärde.
Func<string> retriever = lambda.Compile();
var fieldValue = retriever();
// fieldValue-resultat är StaticPublicFieldValue
InvocationExpression Class
InvocationExpression-klassen tillåter åkallelse av andra lambda-uttryck som är delar av samma Expression-träd.
Du skapar dem med statisk Expression.Invoke
metod.
Problem Vi vill komma på de artiklar som har "bil" i beskrivningen. Vi måste kontrollera det för null innan vi söker efter en sträng inuti men vi vill inte att den kallas för mycket, eftersom beräkningen kan vara dyr.
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
);
}
}
Produktion
element => Invoke(extracted => (Not(IsNullOrEmpty(extracted)) AndAlso extracted.Contains("car")), Invoke(e => e.Description, element))
car
cargo
Madagascar
Predicate is called 5 times.
Först att notera är hur den faktiska tillgången, insvept i en anrop:
Invoke(e => e.Description, element)
, och detta är den enda delen som vidrör e.Description
, och i stället för den e.Description
extracted
parameter av string
till nästa:
(Not(IsNullOrEmpty(extracted)) AndAlso extracted.Contains("car"))
En annan viktig sak att notera här är AndAlso
. Den beräknar endast den vänstra delen, om den första delen returnerar "falsk". Det är ett vanligt misstag att använda den bitvisa operatören 'Och' istället för den, som alltid beräknar båda delarna och skulle misslyckas med en NullReferenceException i detta exempel.