2010-05-15 13 views
7

यह अपेक्षाकृत एक लैम्ब्डा समारोह है कि यहां तक ​​कि गहरे गुण सहित एक वस्तु से एक संपत्ति के मूल्य वापस आ जाएगी, बनाने के लिए आसान है से लैम्ब्डा कार्रवाई बनाएं ...समारोह अभिव्यक्ति

Func<Category, string> getCategoryName = new Func<Category, string>(c => c.Name); 

और इस रूप में कहा जा सकता है इस प्रकार ...

string categoryName = getCategoryName(this.category); 

लेकिन, केवल ऊपर जिसके परिणामस्वरूप समारोह (या मूल रूप से समारोह बनाने के लिए इस्तेमाल अभिव्यक्ति), किसी को भी लिए एक आसान तरीका प्रदान कर सकते हैं दी विरोध कार्रवाई बनाने ...

Action<Category, string> setCategoryName = new Action<Category, string>((c, s) => c.Name = s);

... जो एक ही संपत्ति मूल्य सक्षम हो जाएगा निम्नानुसार निर्धारित किए हैं?

setCategoryName(this.category, ""); 

ध्यान दें कि मैं समारोह या अभिव्यक्ति से प्रोग्राम के कार्रवाई बनाने का एक तरीका रहा हूँ - मुझे आशा है कि मैं पता चला है कि मैं पहले से ही पता है कि यह मैन्युअल रूप से बनाने का तरीका।

मैं नेट 3.5 और 4.0 दोनों में काम करने के लिए खुला हूं।

धन्यवाद।

अद्यतन:

शायद मैं अपने सवाल में स्पष्ट नहीं किया जा रहा हूँ, इसलिए मुझे कोशिश करते हैं और अधिक स्पष्ट रूप से प्रदर्शित मैं क्या करने की कोशिश कर रहा हूँ करते हैं।

मैं निम्न विधि है (कि मैं इस सवाल के प्रयोजनों के लिए बनाया है) ...

void DoLambdaStuff<TObject, TValue>(TObject obj, Expression<Func<TObject, TValue>> expression) { 

    Func<TObject, TValue> getValue = expression.Compile(); 
    TValue stuff = getValue(obj); 

    Expression<Action<TObject, TValue>> assignmentExpression = (o, v) => Expression<TObject>.Assign(expression, Expression.Constant(v, typeof(TValue))); 
    Action<TObject, TValue> setValue = assignmentExpression.Compile(); 

    setValue(obj, stuff); 

} 

क्या मैं देख रहा हूँ कि कैसे मैं कोड के भीतर "assignmentExpression" बना सकता है इतना है कि मैं इसे setValue में संकलित कर सकते हैं? मुझे लगता है कि यह अभिव्यक्ति से संबंधित है। असाइन करें, लेकिन मैं कोड को पूरा करने के लिए पैरामीटर के सही संयोजन को काम नहीं कर सकता।

अंतिम परिणाम

Category category = *<get object from somewhere>*; 
this.DoLambdaStuff(category, c => c.Name); 

कॉल करने के लिए सक्षम होने के लिए है और यह बारी में एक गेटर और श्रेणी वस्तु का "नाम" संपत्ति के लिए एक सेटर पैदा करेगा।

उपरोक्त संस्करण संकलित करता है, लेकिन जब मैं setValue() को कॉल करता हूं तो इसके परिणामस्वरूप "अभिव्यक्ति लिखने योग्य" के साथ एक ArgumentException में परिणाम होता है।

फिर से धन्यवाद।

+0

मैं सच में समझ में न आप यह स्वचालित रूप से मैन्युअल रूप से करने का विरोध करने से क्या मतलब है। हालांकि, अगर आप गुण सेट करना चाहते हैं और तय करें कि आप किस संपत्ति को रनटाइम पर सेट करना चाहते हैं, तो आपको प्रतिबिंब का उपयोग करना चाहिए। इसके अलावा, आप रन-टाइम लैम्ब्डा एक्सप्रेशन बनाने के लिए अभिव्यक्ति वृक्ष का उपयोग कर सकते हैं। – Henri

उत्तर

0

यह expression trees का उपयोग करना संभव होना चाहिए, जिसे लैम्ब्डा अभिव्यक्तियों से संशोधित किया जा सकता है, संशोधित किया जा सकता है, और उसके बाद एक प्रतिनिधि में संकलित किया जा सकता है।

+5

यह वही चीज है जिसे मैं ढूंढ रहा था, मैं बस एक उदाहरण की उम्मीद कर रहा था! –

3

ठीक है, कोड मैं देख रहा हूँ कुछ इस तरह चला जाता है ...

ParameterExpression objectParameterExpression = Expression.Parameter(
    typeof(TObject)), 
    valueParameterExpression = Expression.Parameter(typeof(TValue) 
); 
Expression<Action<TObject, TValue>> setValueExpression = Expression.Lambda<Action<TObject, TValue>>(
    Expression.Block(
    Expression.Assign(
     Expression.Property(
     objectParameterExpression, 
     ((MemberExpression) expression.Body).Member.Name 
    ), 
     valueParameterExpression 
    ) 
), 
    objectParameterExpression, 
    valueParameterExpression 
); 
Action<TObject, TValue> setValue = setValueExpression.Compile(); 

इस कोड काम करता है, लेकिन केवल उथले गुण (जो है, तत्काल ऑब्जेक्ट के गुणों को) के लिए, लेकिन के लिए काम नहीं करता है गहरी गुण - हालांकि गेटर फ़ंक्शन गहरे गुणों के लिए काम कर सकता है। यह जानना दिलचस्प होगा कि क्या कोई मुझे गहरे गुणों के साथ काम करने के लिए संशोधित करने में मदद कर सकता है लेकिन मैं इसे एक अलग प्रश्न के रूप में उठाऊंगा।

+2

यह कोड केवल .NET 4 में काम करता है क्योंकि इसे .NET 4. –

2

जैसा कि मार्टिन ने ऊपर उल्लेख किया है, "गहरी" गुणों ने अपना समाधान तोड़ दिया। कारण है कि यह काम नहीं करता इस अभिव्यक्ति है:

Expression.Property(
    objectParameterExpression 
, ((MemberExpression)expression.Body).Member.Name 
) 

इसके लिए कारण तुरंत स्पष्ट नहीं है: व्युत्पन्न वर्ग अपनी स्वयं की प्रॉपर्टी गेटर है, जो आधार वर्ग पर रीडायरेक्ट वाणी है, लेकिन एक इसी सेटर को परिभाषित नहीं करता ।

इस समस्या को हल करने के लिए, आपको प्रतिबिंब के माध्यम से संपत्ति का पता लगाने, इसके घोषित करने के प्रकार की तलाश करने की आवश्यकता है, और फिर घोषणाकर्ता से संबंधित संपत्ति प्राप्त करें। व्युत्पन्न वर्ग पर मिलने वाली संपत्ति के विपरीत, घोषणाकर्ता की संपत्ति में गेटटर और एक सेटर होता है, और इसलिए असाइन किया जा सकता है। (यह मानता है कि संपत्ति का एक सेटर बिल्कुल घोषित किया गया है: यदि घोषणाकर्ता एक सेटटर प्रदान नहीं करता है, तो कोई भी इसे संभवतः प्रदान नहीं कर सकता है।)

यहां एक कंकाल कार्यान्वयन है जो इस समस्या को संबोधित करता है। GetPropertyOrField के कॉल के साथ उपरोक्त कॉल को Expression.Property पर बदलें, और मार्टिन का समाधान काम करने जा रहा है।

private static MemberExpression GetPropertyOrField(Expression baseExpr, string name) { 
    if (baseExpr == null) { 
     throw new ArgumentNullException("baseExpr"); 
    } 
    if (string.IsNullOrWhiteSpace(name)) { 
     throw new ArgumentException("name"); 
    } 
    var type = baseExpr.Type; 
    var properties = type 
     .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) 
     .Where(p => p.Name.Equals(name)) 
     .ToArray(); 
    if (properties.Length == 1) { 
     var res = properties[0]; 
     if (res.DeclaringType != type) { 
      // Here is the core of the fix: 
      var tmp = res 
       .DeclaringType 
       .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) 
       .Where(p => p.Name.Equals(name)) 
       .ToArray(); 
      if (tmp.Length == 1) { 
       return Expression.Property(baseExpr, tmp[0]); 
      } 
     } 
     return Expression.Property(baseExpr, res); 
    } 
    if (properties.Length != 0) { 
     throw new NotSupportedException(name); 
    } 
    var fields = type 
     .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) 
     .Where(p => p.Name.Equals(name)) 
     .ToArray(); 
    if (fields.Length == 1) { 
     return Expression.Field(baseExpr, fields[0]); 
    } 
    if (fields.Length != 0) { 
     throw new NotSupportedException(name); 
    } 
    throw new ArgumentException(
     string.Format(
      "Type [{0}] does not define property/field called [{1}]" 
     , type 
     , name 
     ) 
    ); 
} 
5
void DoLambdaStuff<TObject, TValue>(TObject obj, Expression<Func<TObject, TValue>> expression) { 

    Func<TObject, TValue> getValue = expression.Compile(); 
    TValue stuff = getValue(obj); 

    var p = Expression.Parameter(typeof(TValue), "v"); 
    Expression<Action<TObject, TValue>> assignmentExpression = 
     Expression.Lambda<Action<TObject, TValue>>(Expression.Assign(expression.Body, p), expression.Parameters[0], p); 

    Action<TObject, TValue> setValue = assignmentExpression.Compile(); 

    setValue(obj, stuff); 
} 
+0

में पेश किए गए अभिव्यक्ति पेड़ के लिए बेहतर समर्थन की आवश्यकता है, क्या आप समझा सकते हैं कि आपका कोड क्या करता है? – Math

+0

+1, उतना ही सरल। – nawfal

+1

+1। इस कोड का उपयोग करके मैं 'निजी फ़ील्ड' और 'नेस्टेड निजी गुणों' के लिए मूल्य भी सेट कर सकता हूं। क्षेत्र/संपत्ति का प्रकार भी 'संरचना' हो सकता है और यह सिर्फ काम करता है। वास्तव में यह कैसे काम करता है इसका एक स्पष्टीकरण पसंद करेंगे। –

संबंधित मुद्दे