Ho bisogno di costruire un sistema in cui ho un numero di espressioni che sono memorizzate in un file. Queste espressioni dovrebbero essere lette nel programma, compilate in espressioni linq e valutate come funzioni su un numero di oggetti. Tuttavia, queste espressioni dovrebbero contenere riferimenti ad alcune funzioni nel codice (cioè non sarebbero fatte di soli operatori di C # di base).
Sto cercando di usare DynamicExpression da System.Linq.Dynamic e funziona quasi, tranne che le mie funzioni dal codice non vengono riconosciute. Fondamentalmente, quando ho questo codice qui sotto:
public class GeoObj
{
public int layer;
public Polygon poly;
}
class Program
{
public static bool ComplicatedMethod(GeoObj x, GeoObj y)
{
bool result = // some quite complicated computation on x and y, say a polygon intersection test
return result;
}
static void Main(string[] args)
{
GeoObj o1 = new GeoObj(); // here set o1 fields
GeoObj o2 = new GeoObj(); // here set o2 fields
string sExpr = @"(x.layer == y.layer) && (ComplicatedMethod(x,y))";
var xparam = Expression.Parameter(typeof(GeoObj), "x");
var yparam = Expression.Parameter(typeof(GeoObj), "y");
Expression<Func<GeoObj, GeoObj, bool>> expr = (Expression<Func<GeoObj, GeoObj, bool>>)System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { xparam, yparam }, typeof(bool), sExpr);
var del2 = expr.Compile();
Console.WriteLine(del2(o1, o2));
}
}
(Naturalmente, nella versione finale, sExpr verrebbe letto da un file di testo, ma questo era solo un esempio.)
Il codice genera un'eccezione in ParseLambda, affermando che "ComplicatedFunction" non è noto. Posso vedere come ParseLambda non saprebbe di "ComplicatedFunction" perché non sa in quale assembly viene utilizzato.
C'è un modo per compilare quella stringa in un'espressione reale? Se creo l'espressione nel mio codice usando qualcosa di hardcoded come questo:
Expression<Func<GeoObj, GeoObj, bool>> expr = (x, y) => (x.layer == y.layer) && ComplicatedFunction(x,y));
funziona come volevo, ma non posso anticipare tutte le possibili espressioni.
Se non è possibile, suppongo che la cosa migliore da fare sia analizzare le espressioni di stringa in un albero di analisi e creare l'espressione da quell'albero. Dal momento che ci sono solo alcune funzioni (come "ComplicatedFunction") che verranno utilizzate, sembra fattibile. Qual è il modo migliore / codice migliore per creare l'albero di sintassi da tale espressione? Avrei bisogno di qualcosa che non generasse eccezioni quando un simbolo non è noto (come "ComplicatedFunction") e mi renderebbe facile per me analizzare l'albero per costruire l'espressione.
Grazie.
-------------------- AGGIORNARE ----------------------------- ----
La risposta di @ pil0t mi ha messo sulla strada giusta per risolvere questo problema. Fondamentalmente, devi scaricare il file sorgente originale per Dynamic Linq e apportare due modifiche.
Cercare:
static readonly Type[] predefinedTypes = {
e rimuovere la parola chiave 'readonly' (in modo che possa essere modificata).
Quindi, aggiungere la seguente funzione alla classe ExpressionParser:
public static void AddPredefinedType(Type type)
{
predefinedTypes = predefinedTypes.Union(new[] { type }).ToArray();
keywords = ExpressionParser.CreateKeywords();
}
Puoi usare la nuova funzione in questo modo:
using System.Linq.Dynamic;
...
ExpressionParser.AddPredefinedType(typeof(GeoObj));
Una nota sulla sintassi per le espressioni però. La funzione appena aggiunta ti darà accesso ai metodi della classe 'GeoObj' (o qualsiasi altra classe tu usi), e solo a quelli. Ciò significa che dovrai modificare l'espressione come segue:
class GeoObj {
public bool field1;
public bool method1() { /* return something */ }
public static ComplicatedMethod(GeoObj o1, GeoObj o2) { ... }
}
...
string myExpr = @"x.field1 && y.method1() && GeoObj.ComplicatedMethod(x,y)";
In altre parole, tutte le funzioni che è necessario utilizzare devono essere spostate all'interno della classe, come suggerito da @Shlomo. Mi sarebbe piaciuta una notazione più pulita (es. Usare "ComplicatedMethod (x, y)" invece di "GeoObj.ComplicatedMethod (x, y)", o avere qualcosa come Method1 (x) invece di x.Method1 ()), ma queste sono per lo più preferenze estetiche a questo punto. Inoltre, poiché il numero di tali metodi che possono essere utilizzati è ridotto, le espressioni possono essere riscritte internamente per passare da espressioni simili a chiamate di funzioni a chiamate di metodi.
Grazie a tutti per avermi indicato nella giusta direzione.
L'ho fatto rattoppando le fonti originali con
public class Foo
{
...
public static void AddPredefinedType(Type type)
{
ExpressionParser.predefinedTypes = ExpressionParser.predefinedTypes.Union(new[] { type }).ToArray();
ExpressionParser.CreateKeywords();
}
}
dopo questo, puoi usare
Foo.AddPredefinedType(typeof(Program));
e tutti i metodi del Programma saranno disponibili
Non posso davvero aiutarti con le cose di DynamicExpression
. Se stai cercando come creare espressioni, questo potrebbe aiutarti a iniziare.
Supponendo che la tua classe GeoObj
assomigli a questo:
class GeoObj
{
public static bool ComplicatedFunction(GeoObj a, GeoObj b)
{
return false;
}
public object layer { get; set; }
}
Ecco come si costruisce un'espressione simile a quella del tuo esempio:
Expression<Func<GeoObj, GeoObj, bool>> expr = (x, y) => (x.layer == y.layer) && GeoObj.ComplicatedFunction(x,y);
var complicatedFunctionMethodInfo = typeof (GeoObj).GetMethod("ComplicatedFunction");
var paramX = Expression.Parameter(typeof (GeoObj), "x");
var paramY = Expression.Parameter(typeof (GeoObj), "y");
var expr2 = Expression.Lambda<Func<GeoObj, GeoObj, bool>>(
Expression.AndAlso
(
Expression.Equal
(
Expression.Property(paramX, "layer"),
Expression.Property(paramY, "layer")
),
Expression.Call(complicatedFunctionMethodInfo, paramX, paramY)
),
paramX,
paramY
);
expr
ed expr2
sono funzionalmente equivalenti.