2015-07-08 15 views
8

के मैं निम्नलिखित वर्ग है लगता है चलो बनाने के लिए:कैसे गतिशील रूप से एक लैम्ब्डा अभिव्यक्ति

public class Show 
{ 
    public string Language { get; set; } 
    public string Name { get; set; } 
} 

इस जानकारी के आधार पर, मेरा लक्ष्य इस तरह एक लैम्ब्डा अभिव्यक्ति बनाने के लिए है:

g => g.Language == lang && g.Name == name 

lang और name स्थानीय चर हैं जो अभिव्यक्ति बनाते समय निरंतर मानों के रूप में जोड़ना चाहते हैं।

आप देख सकते हैं, संकलित समारोह प्रकार का होगा Func<Genre, bool>

आप अधिक स्पष्ट रूप से समझने में मदद करने के लिए, मैं इस के समान कुछ हासिल करना चाहते हैं:

string lang = "en"; 
string name = "comedy"; 
Genre genre = new Genre { Language = "en", Name = "comedy" }; 
Expression<Func<Genre, bool>> expression = CreateExpression(genre, lang, name); 
// expression = (g => g.Language == "en" && g.Name == "comedy") 

मैं के बारे में पता कर रहा हूँ अभिव्यक्ति पेड़ों का अस्तित्व, लेकिन मैं इस विषय के लिए काफी नया हूं इसलिए मुझे यह भी नहीं पता कि कैसे शुरू किया जाए।

क्या इस समस्या को हल किया जा सकता है? मैं गतिशील रूप से ऐसी अभिव्यक्ति कैसे बना सकता हूं?

+0

मैं समस्या अनुमान लगा रहा हूँ मोर है ई अभ्यास में जटिल है जैसा आपने वर्णन किया है, है ना? यदि आप उन गुणों को जानते हैं जिन्हें आप समय से पहले परीक्षण कर रहे हैं, तो क्या आप कोई फ़ंक्शन नहीं लिख सकते हैं जो फ़ंक्शन देता है? – kaveman

+0

@kaveman हकीकत में, यह है, आप सही हैं। मेरे लिए और यहां तक ​​कि खुद को भ्रमित किए बिना समझाया जाना मेरे लिए बहुत जटिल है। मेरे पास एक भंडार इंटरफ़ेस है जो अभिव्यक्ति का उपयोग करके मेरी संस्थाओं से पूछताछ कर सकता है। समस्या यह है कि यह अभिव्यक्ति उपर्युक्त प्रारूप में होनी चाहिए: '&&' से अलग की गई प्रमुख स्थितियां। –

+0

@ केवेमैन मैं मुख्य मानों के साथ एक सामान्य पैरामीटर के रूप में (मेरे उदाहरण में, शैली) प्राप्त कर रहा हूं, इसलिए मुझे प्रतिबिंब के माध्यम से अपने मुख्य गुण लाने की आवश्यकता है (मुझे लगता है कि मुझे यह पता है कि यह कैसे करें) और अभिव्यक्ति बनाएं इसलिए जब आप कहते हैं * प्राप्त * कहने का मतलब क्या है, तो मैं Genre इकाइयों –

उत्तर

1
public Expression<Func<TValue, bool>> CreateExpression<TValue, TCompare>(TValue value, TCompare compare) 
{ 
    var pv = Expression.Parameter(typeof(TValue), "data"); 
    var compareProps = typeof(TCompare).GetProperties(); 

    // First statement of the expression 
    Expression exp = Expression.Constant(true); 

    foreach (var prop in typeof(TValue).GetProperties()) 
    { 
     // Check if the compare type has the same property 
     if (!compareProps.Any(i => i.Name == prop.Name)) 
      continue; 

     // Build the expression: value.PropertyA == "A" 
     // which "A" come from compare.PropertyA 
     var eq = Expression.Equal(
      Expression.Property(pv, prop.Name), 
      Expression.Constant(compareProps 
       .Single(i => i.Name == prop.Name) 
       .GetValue(compare))); 

     // Append with the first (previous) statement 
     exp = Expression.AndAlso(exp, eq); 
    } 

    return Expression.Lambda<Func<TValue, bool>>(exp, pv); 
} 

उपयोग:

var value = new { Lang = "en", Name = "comedy"}; 

// The compareValue should have the same property name as the value, 
// or the expression will just ignore the property 
var compareValue = new { Lang = "en", Name = "comedy", Other = "xyz" }; 

// The create expression content is 
// {data => ((True AndAlso (data.Lang == "en")) AndAlso (data.Name == "comedy"))} 
bool isMatch = CreateExpression(value, compareValue).Compile()(value); // true 
0

आप आवश्यक गुणों को परिभाषित करने वाले इंटरफ़ेस के विरुद्ध एक Func बनाने के लिए इंटरफेस का उपयोग कर सकते हैं।

उदाहरण:

interface IExpressionable { 
    string Language { get;set; } 
    string Name { get;set; } 
} 
class Genre : IExpressionable { 
    string Language {get;set;} 
    string Name {get;set;} 
} 
Genre genre = new Genre(); 
Expression<Func<IExpressionable, bool>> expression = CreateExpression(genre, lang, name); 
expression = (g => g.Language == "en" && g.Name == "comedy") 

इस अवधारणा का एक वैकल्पिक कार्यान्वयन अपने इंटरफेस पर एक GetLanguage और getName विधि है, जो ग्राहकों के लिए एक "भाषा" लौटने के लिए में अंतर्निहित तरीकों को लागू करने के लिए मजबूर होना उपलब्ध कराने के द्वारा किया जा सकता है और "नाम" अपने स्वयं के आंतरिक तरीकों के आधार पर। ऊपर अपनी टिप्पणी से

interface IExpressionable { 
    string GetExpressionOne(); 
    string GetExpressionTwo(); 
} 
class Genre : IExpressionable { 
    string Language {get;set;} 
    string Name {get;set;} 
    public string GetExpressionOne() { 
     return Language; 
    } 
    public string GetExpressionOne() { 
     return Name; 
    } 
} 
class SomethingElse { 

    string Orange {get;set;} 
    string BananaPeel {get;set;} 
    public string GetExpressionOne() { 
     return Orange; 
    } 
    public string GetExpressionOne() { 
     return BananaPeel; 
    } 
} 
Genre genre = new Genre(); 
SomethingElse else = new SomethingElse(); 
Expression<Func<IExpressionable, bool>> expression = CreateExpression(genre, lang, name); 
Expression<Func<IExpressionable, bool>> expression2 = CreateExpression(else, lang, name); 

expression = (g => g.GetExpressionOne() == "en" && g.GetExpressionTwo() == "comedy"); 

संपादित करें: @kaveman I only have the key values, but I can fetch the key properties via reflection using some custom attributes that I defined. In this example, Language and Name would be decorated with an attribute that defines them as key properties

मेरा जवाब यह पूरी तरह से सोचा से बचना है। एक इंटरफ़ेस बनाएं जो आपको जो भी अभिव्यक्ति खोज रहे हैं, उसे करने के लिए आवश्यक विधियों को परिभाषित करता है, और संस्थाओं से इसका उत्तराधिकारी होता है। इंटरफ़ेस पर आपके तरीके "GetKeyOne" और "GetKeyTwo" हो सकते हैं। तब आपकी अभिव्यक्ति को गतिशील नहीं होना चाहिए, आपको बस परिभाषित करने की आवश्यकता है कि जब अभिव्यक्ति कुंजीऑन और कीटवो के साथ बातचीत कर रही है, जिसे प्रत्येक कार्यान्वयन में परिभाषित किया गया है।

+0

यह एक अच्छा समाधान है, लेकिन 'भाषा' और' नाम 'गुणों का अस्तित्व वास्तव में वर्तमान प्रकार के प्रकार पर निर्भर करता है इकाई, इसलिए मुझे अपने पास कई अलग-अलग इकाई प्रकारों के लिए एक अलग इंटरफ़ेस बनाना होगा (वे पूरी तरह से अलग-अलग गुण हो सकते हैं) –

+0

यह एक उदाहरण है और आप जो भी चाहें गुणों का नाम दे सकते हैं, यहां तक ​​कि "ExpressionArgument1" और "ExpressionArgument2" । संपादन देखें। –

+0

क्या मैं 'CreateExpression' का शरीर देख सकता हूं? –

0

मान लिया जाये कि आप संस्थाओं है कि आप यहाँ से निपटने में रुचि रखते हैं में से प्रत्येक के गुणों में से प्रत्येक पर गुण (comments देखें), और आप प्रकार पैरामीटर है (यह T कहते हैं) CreateExpression के लिए एक संभावित हस्ताक्षर है:

Expression<Func<T, bool>> CreateExpression(T entity, IOrderedDictionary<string, string> propertyTests); 

इस तरह, जाना जाता सामान्य प्रकार के साथ अपने CreateExpression काम करता है, और फोन करने वाले क्या इकाई के गुण के खिलाफ बनाने के लिए परीक्षण निर्दिष्ट कर सकते हैं। यहां हम मानते हैं कि key संपत्ति का नाम प्रतिबिंबित करने के लिए है (सुनिश्चित करें कि यह ज्ञात विशेषता है) जबकि value कहा गया संपत्ति का आवश्यक मूल्य है। IOrderedDictionary&& परीक्षणों के शॉर्ट-सर्किट को लागू करने का एक तरीका है, यदि यह आपकी बात है।

आप T पर अतिरिक्त बाधाएं जोड़ सकते हैं (उदा।T कुछ इंटरफेस) को लागू करना चाहिए के माध्यम से generic type constraints using where

भी देखें Check if property has attribute

और Reflection - get attribute name and value on property

0

यह यह करना होगा, और यह काफी उपयोगकर्ता के अनुकूल

private Expression<Func<Genre,bool>> CreateExpression(params Expression<Func<Object>>[] selectors) 
{ 
    //We are working on a Genre type, and make a parameter of it 
    //Query so far looks like g => 
    var param = Expression.Parameter(typeof(Genre),"g"); 

    //Set the base expression to make it easy to build 
    //Query so far looks like g => true 
    Expression expression = Expression.Constant(true); 

    foreach(var selector in selectors) { 
     //Find out the name of the variable was passed 
     var selectorname = TestExtension.nameof(selector); 
     //Get the value 
     var selectorValue = selector.Compile()(); 

     //Create an accessor to the genre (g.Language for example) 
     var accessMethod = Expression.PropertyOrField(param, selectorname); 

     //Check if it equals the value (g.Language == "en") 
     var equalExpr = Expression.Equal(accessMethod, Expression.Constant(selectorValue)); 

     //Make it an And expression 
     //Query so far looks like g => true && g.Language == "en" 
     expression = Expression.AndAlso(expression, equalExpr); 

     //Second pass through the loop would build: 
     //g => true && g.Language == "en" && g.Name == "comedy" 
    } 

    //Turn it into a lambda func and cast it 
    var result = Expression.Lambda(expression, param) as Expression<Func<Genre, bool>>; 
    return result; 
} 

public class Genre 
{ 
    public string Language { get; set; } 
    public string Name { get; set; } 
} 

//Taken from my answer at http://stackoverflow.com/a/31262225/563532 

public static class TestExtension 
{ 
    public static String nameof<T>(Expression<Func<T>> accessor) 
    { 
     return nameof(accessor.Body); 
    } 

    public static String nameof<T, TT>(this T obj, Expression<Func<T, TT>> propertyAccessor) 
    { 
     return nameof(propertyAccessor.Body); 
    } 

    private static String nameof(Expression expression) 
    { 
     if (expression.NodeType == ExpressionType.MemberAccess) 
     { 
      var memberExpression = expression as MemberExpression; 
      if (memberExpression == null) 
       return null; 
      return memberExpression.Member.Name; 
     } 
     return null; 
    } 
} 

निर्माण करने के लिए तो आप का उपयोग करना चाहिए यह इस तरह है:

string language = "en"; 
string name = "comedy"; 
var genre = new Genre { Language = "en", Name="comedy" }; 

var query = CreateExpression(() => language,() => name); 

ध्यान दें कि चर संपत्ति नामों से मेल खाना चाहिए। अन्यथा, आप मानचित्रण किसी तरह का [lang => भाषा] आदि

की आवश्यकता होगी यह मूल्यांकन करने के लिए:

var matches = query.Compile()(genre); 

या, आप उदाहरण के लिए एफई में इसे पारित कर सकते हैं:

dtx.Genre.Where(query); 
संबंधित मुद्दे