none
Creating DynamicClass with custom get/set method, using System.Reflection.Emit RRS feed

  • Question

  • HI,
    I'm trying to create custom class using type builder. I have class that has structure similar to this:
    class MyClass
    {
         Property1 Prop;
    }
    
    class Property1
    {
         Dictionary<string,string> Dict;
    }
    I want to create property for each value in list Prop.Dict
    For example:

    if I have instance of object created like this:
    MyClass myClass=new MyClass();
    
    myClass.Prop = new Property1();
    myClass.Prop.Dict = new Dictionary(string,string)();
    
    myClass.Prop.Dict.Add("FirstProperty","Value of first property");
    myClass.Prop.Dict.Add("SecondProperty","Value of second property");
    
    

    When I get instance of an object, I go through all the values in Dict and create Fields using  Dict[i].Key for field name, by using code (dp.Name is Dict[i].Key, and dp.Type i string), and I want to create property with get and set:

    FieldBuilder fb = tb.DefineField("m_" + dp.Name, dp.Type, FieldAttributes.Private);
    PropertyBuilder pb = tb.DefineProperty(dp.Name, PropertyAttributes.HasDefault, dp.Type, null);
    If I use this code (which can be found all over the net):
        getbld = typebld.DefineMethod("get_" + name, attribs, clrtype, None)
        getilgen = getbld.GetILGenerator()
        getilgen.Emit(OpCodes.Ldarg_0)
        getilgen.Emit(OpCodes.Ldfld, fieldbld)
        getilgen.Emit(OpCodes.Ret)
    
        setbld = typebld.DefineMethod("set_" + name, attribs, None, (clrtype,))
        setilgen = setbld.GetILGenerator()
        setilgen.Emit(OpCodes.Ldarg_0)
        setilgen.Emit(OpCodes.Ldarg_1)
        setilgen.Emit(OpCodes.Stfld, fieldbld)
        setilgen.Emit(OpCodes.Ret)
    It generates default get, and set methods, and I get DynamicClass that looks like this:

    Class DynamicClass
    {
         Property1 Prop;
         
         private string _FirstProperty;
         public string FirstProperty
         {
             get
             {
                 return _FirstProperty;
             }
             set
             {
                 _FirstProperty=value;
             }
         }
         private string _SecondProperty;
         public string SecondProperty
         {
             get
             {
                 return _SecondProperty;
             }
             set
             {
                 _SecondProperty=value;
             }
         }
    }
    But what I need is for first property to have get/set method like this:

    private string _FirstProperty;
         public string FirstProperty
         {
             get
             {
                 foreach(KeyValuePair<string,string> kvp in Prop.Dict)
                 {
                      if(kvp.Name=="FirstProperty") return kvp.Value;
                 }
                 return String.Empty;
             }
             set
             {
                 foreach(KeyValuePair<string,string> kvp in Prop.Dict)
                 {
                      if(kvp.Name=="FirstProperty") kvp.Value=value;
                 }
             }
         }
    
    And then when someone wants to get/set value, he/she doesn't have to go through list, all that is needed is getting/setting value of the property
    Thursday, July 2, 2009 9:46 AM

Answers

  • Not sure what I can do to help you understand IL.  Amazon.com has books about it.  Why are you using DynamicMethod in the first place?

    Hans Passant.
    Thursday, July 2, 2009 12:13 PM
    Moderator
  • Try to simplify the IL by compiling your sample project in release mode with optimizations turned on, the generated IL should be more compact (it should reduce the number of nop,  of locals, etc). If you're having problems understanding the generated IL codes keep a copy of ECMA 335 (http://www.ecma-international.org/publications/standards/Ecma-335.htm) as a lookup reference and try to break your code generation into smaller steps (get enumerator, use element, etc) or implement your property in a different way - for example using "for" instead of "foreach" loops. 

    Another implementation alternative if you're not comfortable with IL would be to create a set of helper methods (bundled in a static class or something) to help you implement the methods, something like :

    internal static class PropertyHelper
    {
       internal string GetByKey(IDictionary<string, string> dict, string key) 
       {
               // return value by kety
      }
    }

    // code generation part
    getbld = typebld.DefineMethod("get_" + name, attribs, clrtype, None)
    getilgen = getbld.GetILGenerator()
    getilgen.Emit(OpCodes.Ldfld, propertiesDict) // or collection, or whatever you use for implementation
    getilgen.Emit(OpCodes.Ldstr, name) // property name
    getilgen.Emit(OpCodes.Call, <PropertyHelper.GetByKey>) // call GetByKey helper
    getilgen.Emit(OpCodes.Ret)

    This way you simplify the IL code generation and do the heavy lifting in a language you're familiar with. 

    HTH
    Thursday, July 2, 2009 1:36 PM

All replies

  • Rewrite the property to look like this:

      get {
        return Prop.Dict.Contains("FirstProperty") ? Prop.Dict["FirstProperty"] : string.empty;
      }
      set {
        if (Prop.Dict.Contains("FirstProperty")) Prop.Dict["FirstProperty"] = value;
        else Prop.Dict.Add("FirstProperty", value);
      }

    Then compile that code and look at the generated IL with Ildasm.exe.  So you know what Emit() statements you'll need.

    Hans Passant.
    Thursday, July 2, 2009 10:16 AM
    Moderator
  • Thanks nobugz,
    I have tried to simplify my sample, so it would be more understandable, but that had some drawbacks :)

    Your post was helpful, but..
    In my case I'm not actually using Dictionary.. I have custom collection type in which I store different objects, so I cannot use Contains, since I don't have object to use as parameter in Contains. I only have it's name.
    I will try to use your suggestion, and combine it with foreach (if it's possible)
    Thursday, July 2, 2009 11:28 AM
  • Well, use a Dictionary<>.  It is a *lot* faster than iterating the collection with foreach.

    Hans Passant.
    Thursday, July 2, 2009 11:43 AM
    Moderator
  • I cannot! I already have class that is generated with lists and other collections. So I'm extending that type and giving it new properties that will be mapped from(and to) collection member.
    And ildasm. exe gives me 65 lines of code, which I'm finding very hard to understand :(

            get_FirstProperty() cil managed
    {
      // Code size       94 (0x5e)
      .maxstack  2
      .locals init ([0] class [Revolution.Core.Entities]Revolution.Common.Entities.Condition cond,
               [1] class [Revolution.Core.Entities]Revolution.Common.Entities.Condition CS$1$0000,
               [2] class [mscorlib]System.Collections.Generic.IEnumerator`1<class [Revolution.Core.Entities]Revolution.Common.Entities.Condition> CS$5$0001,
               [3] bool CS$4$0002)
      IL_0000:  nop
      IL_0001:  nop
      IL_0002:  ldarg.0
      IL_0003:  ldfld      class Template.SomeClass Template.DynamicClass::Prop
      IL_0008:  ldfld      class [Revolution.Core.Entities]Revolution.Core.Entities.EditableEntityCollection`1<class [Revolution.Core.Entities]Revolution.Common.Entities.Condition> Template.SomeClass::Dict
      IL_000d:  callvirt   instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.ObjectModel.Collection`1<class [Revolution.Core.Entities]Revolution.Common.Entities.Condition>::GetEnumerator()
      IL_0012:  stloc.2
      .try
      {
        IL_0013:  br.s       IL_003a
        IL_0015:  ldloc.2
        IL_0016:  callvirt   instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<class [Revolution.Core.Entities]Revolution.Common.Entities.Condition>::get_Current()
        IL_001b:  stloc.0
        IL_001c:  nop
        IL_001d:  ldloc.0
        IL_001e:  callvirt   instance string [Revolution.Core.Entities]Revolution.Common.Entities.NamedObject::get_Name()
        IL_0023:  ldstr      "FirstProperty"
        IL_0028:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                       string)
        IL_002d:  ldc.i4.0
        IL_002e:  ceq
        IL_0030:  stloc.3
        IL_0031:  ldloc.3
        IL_0032:  brtrue.s   IL_0039
        IL_0034:  nop
        IL_0035:  ldloc.0
        IL_0036:  stloc.1
        IL_0037:  leave.s    IL_005b
        IL_0039:  nop
        IL_003a:  ldloc.2
        IL_003b:  callvirt   instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
        IL_0040:  stloc.3
        IL_0041:  ldloc.3
        IL_0042:  brtrue.s   IL_0015
        IL_0044:  leave.s    IL_0056
      }  // end .try
      finally
      {
        IL_0046:  ldloc.2
        IL_0047:  ldnull
        IL_0048:  ceq
        IL_004a:  stloc.3
        IL_004b:  ldloc.3
        IL_004c:  brtrue.s   IL_0055
        IL_004e:  ldloc.2
        IL_004f:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
        IL_0054:  nop
        IL_0055:  endfinally
      }  // end handler
      IL_0056:  nop
      IL_0057:  ldnull
      IL_0058:  stloc.1
      IL_0059:  br.s       IL_005b
      IL_005b:  nop
      IL_005c:  ldloc.1
      IL_005d:  ret
    } // end of method DynamicClass::get_FirstProperty
    

    Thursday, July 2, 2009 11:50 AM
  • Not sure what I can do to help you understand IL.  Amazon.com has books about it.  Why are you using DynamicMethod in the first place?

    Hans Passant.
    Thursday, July 2, 2009 12:13 PM
    Moderator
  • Try to simplify the IL by compiling your sample project in release mode with optimizations turned on, the generated IL should be more compact (it should reduce the number of nop,  of locals, etc). If you're having problems understanding the generated IL codes keep a copy of ECMA 335 (http://www.ecma-international.org/publications/standards/Ecma-335.htm) as a lookup reference and try to break your code generation into smaller steps (get enumerator, use element, etc) or implement your property in a different way - for example using "for" instead of "foreach" loops. 

    Another implementation alternative if you're not comfortable with IL would be to create a set of helper methods (bundled in a static class or something) to help you implement the methods, something like :

    internal static class PropertyHelper
    {
       internal string GetByKey(IDictionary<string, string> dict, string key) 
       {
               // return value by kety
      }
    }

    // code generation part
    getbld = typebld.DefineMethod("get_" + name, attribs, clrtype, None)
    getilgen = getbld.GetILGenerator()
    getilgen.Emit(OpCodes.Ldfld, propertiesDict) // or collection, or whatever you use for implementation
    getilgen.Emit(OpCodes.Ldstr, name) // property name
    getilgen.Emit(OpCodes.Call, <PropertyHelper.GetByKey>) // call GetByKey helper
    getilgen.Emit(OpCodes.Ret)

    This way you simplify the IL code generation and do the heavy lifting in a language you're familiar with. 

    HTH
    Thursday, July 2, 2009 1:36 PM