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 till SQL att skapa en Where 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.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow