2016-07-22 16 views
8

में अभिव्यक्ति वृक्ष के साथ फ़ील्ड गेटर/सेटर this post और its follow-up question पर उदाहरणों के बाद, मैं संकलित अभिव्यक्तियों का उपयोग करके फ़ील्ड गेटर्स/सेटर्स बनाने की कोशिश कर रहा हूं।बेस क्लास

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

यहाँ मेरी सेटर कार्रवाई बिल्डर:

public static Action<T1, T2> GetFieldSetter<T1, T2>(this FieldInfo fieldInfo) { 
    if (typeof(T1) != fieldInfo.DeclaringType && !typeof(T1).IsSubclassOf(fieldInfo.DeclaringType)) { 
    throw new ArgumentException(); 
    } 
    ParameterExpression targetExp = Expression.Parameter(typeof(T1), "target"); 
    ParameterExpression valueExp = Expression.Parameter(typeof(T2), "value"); 
    // 
    // Expression.Property can be used here as well 
    MemberExpression fieldExp = Expression.Field(targetExp, fieldInfo); 
    BinaryExpression assignExp = Expression.Assign(fieldExp, valueExp); 
    // 
    return Expression.Lambda<Action<T1, T2>> (assignExp, targetExp, valueExp).Compile(); 
} 

अब, मैं एक कैश सूची में सामान्य setters (क्योंकि निश्चित रूप से, हर बार सेटर के निर्माण के लिए एक प्रदर्शन हत्यारा है) की दुकान है, जहां मैं उन्हें के रूप में कास्ट सरल "वस्तुओं":

void setFieldValue(string fieldName, object value) { 
     var setterAction = setters[fieldName]; 
     // TODO: now the problem => how do I invoke "setterAction" with 
     // object and fldInfos[fieldName] as parameters...? 
} 

मैं:

// initialization of the setters dictionary 
Dictionary<string, object> setters = new Dictionary(string, object)(); 
Dictionary<string, FieldInfo> fldInfos = new Dictionary(string, FieldInfo)(); 
FieldInfo f = this.GetType().GetField("my_int_field"); 
setters.Add(f.Name, GetFieldSetter<object, int>(f); 
fldInfos.Add(f.Name, f); 
// 
f = this.GetType().GetField("my_string_field"); 
setters.Add(f.Name, GetFieldSetter<object, string>(f); 
fldInfos.Add(f.Name, f); 

अब मैं इस तरह एक फ़ील्ड मान सेट करने का प्रयास बस एक सामान्य विधि को कॉल कर सकते हैं और हर बार कास्ट कर सकते हैं, लेकिन मैं प्रदर्शन ओवरहेड के बारे में चिंतित हूं ... कोई सुझाव?

- संपादित उत्तर पर Mr Anderson's answer आधार पर, मैं एक छोटे से परीक्षण कार्यक्रम जो सीधे मान सेट की तुलना बनाया, कैश की गई प्रतिबिंब (जहां FieldInfo के कैश किए गए हैं) और कैश की गई बहु प्रकार कोड। मैं ऑब्जेक्ट विरासत का उपयोग 3 स्तर तक विरासत (ObjectC : ObjectB : ObjectA) के साथ करता हूं।

Full code is of the example can be found here.

परीक्षण की एकल यात्रा आउटपुट निम्नलिखित देता है:

------------------------- 
---  OBJECT A  --- 
------------------------- 
    Set direct:  0.0036 ms 
    Set reflection: 2.319 ms 
    Set ref.Emit:  1.8186 ms 
    Set Accessor:  4.3622 ms 

------------------------- 
---  OBJECT B  --- 
------------------------- 
    Set direct:  0.0004 ms 
    Set reflection: 0.1179 ms 
    Set ref.Emit:  1.2197 ms 
    Set Accessor:  2.8819 ms 

------------------------- 
---  OBJECT C  --- 
------------------------- 
    Set direct:  0.0024 ms 
    Set reflection: 0.1106 ms 
    Set ref.Emit:  1.1577 ms 
    Set Accessor:  2.9451 ms 
बेशक

, यह बस से पता चलता वस्तुओं बनाने की लागत - यह हमें मापने के लिए संचित संस्करण बनाने की भरपाई की अनुमति देता है प्रतिबिंब और अभिव्यक्तियों का।

------------------------- 
---  OBJECT A  --- 
------------------------- 
    Set direct:  33.2744 ms 
    Set reflection: 1259.9551 ms 
    Set ref.Emit:  531.0168 ms 
    Set Accessor:  505.5682 ms 

------------------------- 
---  OBJECT B  --- 
------------------------- 
    Set direct:  38.7921 ms 
    Set reflection: 2584.2972 ms 
    Set ref.Emit:  971.773 ms 
    Set Accessor:  901.7656 ms 

------------------------- 
---  OBJECT C  --- 
------------------------- 
    Set direct:  40.3942 ms 
    Set reflection: 3796.3436 ms 
    Set ref.Emit:  1510.1819 ms 
    Set Accessor:  1469.4459 ms 

पूर्णता के लिए के लिए::

आगे, 1.000.000 बार चलाते हैं मैं "सेट" विधि करने के लिए कॉल हटाया प्रतिबिंब विधि के लिए सेटर (FieldInfo हो रही की लागत को उजागर करने के अभिव्यक्ति मामले के लिए Action<object, object>)। यहाँ के परिणाम:

------------------------- 
---  OBJECT A  --- 
------------------------- 
    Set direct:  3.6849 ms 
    Set reflection: 44.5447 ms 
    Set ref.Emit:  47.1925 ms 
    Set Accessor:  49.2954 ms 


------------------------- 
---  OBJECT B  --- 
------------------------- 
    Set direct:  4.1016 ms 
    Set reflection: 76.6444 ms 
    Set ref.Emit:  79.4697 ms 
    Set Accessor:  83.3695 ms 

------------------------- 
---  OBJECT C  --- 
------------------------- 
    Set direct:  4.2907 ms 
    Set reflection: 128.5679 ms 
    Set ref.Emit:  126.6639 ms 
    Set Accessor:  132.5919 ms 

नोट: समय यहाँ वृद्धि तथ्य यह है कि पहुँच गुना बड़ा शब्दकोशों के लिए धीमी है (के रूप में वे O(1) अभिगम समय है) की वजह से नहीं है, लेकिन इस तथ्य की वजह है कि समय की संख्या हम इसका उपयोग बढ़ गया है (ObjectA के लिए प्रति बार 4 बार, ObjectB के लिए 8, ObjectC के लिए 12) ... जैसा कि कोई देखता है, केवल सृजन ऑफ़सेट केवल एक अंतर बनाता है (जिसे उम्मीद की जा सकती है)।

नीचे पंक्ति: हमने 2 या उससे अधिक के कारक द्वारा प्रदर्शन में सुधार किया है, लेकिन हम अभी भी प्रत्यक्ष क्षेत्र सेट के प्रदर्शन से बहुत दूर हैं ... सूची में सही सेटटर को पुनर्प्राप्त करना समय के 10% ।

मैं प्रतिबिंब के स्थान पर अभिव्यक्ति पेड़ों के साथ प्रयास करूंगा। यह देखने के लिए कि क्या हम अंतराल को और कम कर सकते हैं ... कोई टिप्पणी स्वागत से अधिक है।

संपादित 2 मैं के रूप में this post पर Eli Arbel ने सुझाव दिया एक सामान्य "Accessor" वर्ग का उपयोग कर दृष्टिकोण का उपयोग कर परिणाम गयी।

+3

"चिंतित प्रदर्शन के बारे में" काफी इसे काट नहीं है। इसका परीक्षण करें, देखें कि यह काफी अच्छा प्रदर्शन करता है, और उस पर आधारित निर्णय लेता है। मुझे कोई कारण नहीं दिख रहा है कि एक सामान्य विधि का उपयोग क्यों करना आपके वर्तमान दृष्टिकोण से भी बदतर होगा। – Luaan

+0

मैं इस दृष्टिकोण (अभिव्यक्ति) का उपयोग करता था, फिर मैंने पाया कि 'System.Reflection.Emit.DyanamicMethod' अधिक सरल है। –

+0

मुझे लगता है कि गतिशील रनटाइम कैश भी इस तरह की चीजें हैं। मुझे नहीं लगता कि यह इतना धीमा प्रदर्शन करेगा। – MBoros

उत्तर

1

यदि आप इसे कई प्रकारों पर संचालन का समर्थन करना चाहते हैं, तो आपके फ़ंक्शन कैश को Type और फ़ील्ड नाम (string) द्वारा अनुक्रमित किया जाना चाहिए, और कार्यों को आलसी बनाया जाना चाहिए। इस प्रयास करें:

private static Dictionary<Type, Dictionary<string, Action<object, object>>> _typeMapper = new Dictionary<Type, Dictionary<string, Action<object, object>>>(); 

public static void Set(object obj, string fieldName, object newValue) 
{ 
    if (obj == null) 
    { 
     throw new ArgumentNullException("obj"); 
    } 
    Type type = obj.GetType(); 
    Dictionary<string, Action<object, object>> fieldMapper; 
    Action<object, object> action; 
    if (_typeMapper.TryGetValue(type, out fieldMapper)) 
    { 
     // entry has been created for this type. 
     if (!fieldMapper.TryGetValue(fieldName, out action)) 
     { 
      // method has not been created yet, must build it. 
      FieldInfo fld = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 
      if (fld == null) 
      { 
       throw new ArgumentException("No field " + fieldName); 
      } 
      action = buildSetter(fld); 
      fieldMapper.Add(fieldName, action); // add it to method cache for future use. 
     } 
    } 
    else 
    { 
     // -- ADDED CODE: forgot to create the new fieldMapper..... 
     fieldMapper = new Dictionary<string, Action<object, object>>(); 

    // type has not been added yet, so we know method has not been built yet either. 
     FieldInfo fld = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 
     if (fld == null) 
     { 
      throw new ArgumentException("No field " + fieldName); 
     } 
     action = buildSetter(fld); 
     fieldMapper.Add(fieldName, action); // add it to method cache for future use. 
     _typeMapper.Add(type, fieldMapper); // add it to type cache for future use. 
    } 
    action(obj, newValue); // invoke the method. 
} 
// this is my preferred setter-builder, feel free to use expressions instead. 
private static Action<object, object> buildSetter(FieldInfo fld) 
{ 
    DynamicMethod dyn = new DynamicMethod("set_" + fld, typeof(void), new[] { typeof(object), typeof(object) }, fld.DeclaringType); 
    ILGenerator gen = dyn.GetILGenerator(); 
    gen.Emit(OpCodes.Ldarg_0); 
    gen.Emit(OpCodes.Castclass, fld.DeclaringType); 
    gen.Emit(OpCodes.Ldarg_1); 
    if (fld.FieldType.IsClass) 
    { 
     gen.Emit(OpCodes.Castclass, fld.FieldType); 
    } 
    else 
    { 
     gen.Emit(OpCodes.Unbox_Any, fld.FieldType); 
    } 
    gen.Emit(OpCodes.Stfld, fld); 
    gen.Emit(OpCodes.Ret); 
    return (Action<object, object>)dyn.CreateDelegate(typeof(Action<object, object>)); 
} 

अन्यथा, यदि आप केवल जरूरत है एक प्रकार के साथ ऐसा करते हैं, अपनी प्रक्रिया हो जाता है:

private static Dictionary<string, Action<MyType, object>> _mapper = new Dictionary<string, Action<MyType, object>>(); 

public static void Set(MyType obj, string fieldName, object newValue) 
{ 
    if (obj == null) 
    { 
     throw new ArgumentNullException("obj"); 
    } 
    Action<MyType, object> action; 
    if (!_mapper.TryGetValue(fieldName, out action)) 
    { 
     FieldInfo fld = typeof(MyType).GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 
     if (fld == null) 
     { 
      throw new ArgumentException("No field " + fieldName); 
     } 
     action = buildSetter(fld); 
     _mapper.Add(fieldName, action); 
    } 
    action(obj, newValue); // invoke the method. 
} 

private static Action<MyType, object> buildSetter(FieldInfo fld) 
{ 
    DynamicMethod dyn = new DynamicMethod("set_" + fld, typeof(void), new[] { typeof(MyType), typeof(object) }, typeof(MyType)); 
    ILGenerator gen = dyn.GetILGenerator(); 
    gen.Emit(OpCodes.Ldarg_0); 
    gen.Emit(OpCodes.Ldarg_1); 
    if (fld.FieldType.IsClass) 
    { 
     gen.Emit(OpCodes.Castclass, fld.FieldType); 
    } 
    else 
    { 
     gen.Emit(OpCodes.Unbox_Any, fld.FieldType); 
    } 
    gen.Emit(OpCodes.Stfld, fld); 
    gen.Emit(OpCodes.Ret); 
    return (Action<MyType, object>)dyn.CreateDelegate(typeof(Action<MyType, object>)); 
} 
+0

ग्रेट उत्तर, धन्यवाद। मैं दोनों के प्रदर्शन की जांच करने और इसे यहां पोस्ट करने के लिए थोड़ा प्रदर्शन परीक्षा कार्यक्रम लिखूंगा। – neggenbe