Sunday, April 17, 2011

How do I attach a method to a dynamically-created C# type at runtime?

I have been saddled with using an in-house data access library that is effectively XML passed to a stored procedure, which returns XML. There is nothing I can do about this. I tried to get ActiveRecord approved, but my request was declined. However, using the excellent code provided at http://blog.bodurov.com/Post.aspx?postID=27, I added an extension method to IEnumerable that converts the key-value pairs I make out of the ragged XML coming back into strongly typed objects, complete with property names!

This:

dict["keyName1"]

becomes

MyObject.keyName1

Now the interface supports databinding! Pretty cool! I'd like to take it a step further, though. I want the objects emitted to have Save() methods too, so that I can ape the ActiveRecord pattern and provide my web guys with an intuitive object layer to use from ASP.net.

How do I write a method in Visual Studio, in source code, and attach it at runtime to the emitted objects? I am not interested in (or qualified for) writing assembly or IL. I'd like to do this in C#. This is my first StackOverflow question and I am posting this with company-mandated IE6, so please be gentle.

From stackoverflow
  • From what I gather on that article, it's creating anonymous types for you, and you're using that to get the values. If that's the case, there's no easy way to add methods on to those objects. However, if the XML structure will be the same every time the SP executes, why not create a concrete class that has all the properties you need, and populate a collection of those objects yourself with the XML. That way, you can easily add any methods you need directly into the class...

    EDIT: Based on our discussion in the comments, here's a thought:

    In the code there, when you're building up the type, you're using: ModuleBuilder.DefineType. There's an overload to DefineType which takes a type to extend. Link.. Therefore, create an interface (it doesn't event have to have any methods in it), and when you're dynamically building up the type, extend that interface using the overload I linked you to. Then create an extension method on that interface that does the Save().

    There's another overload that may be of interest that takes a type to extend, and interfaces:

    http://msdn.microsoft.com/en-us/library/f53tx4x8.aspx

    EDIT2: Code sample:

    First, create an interface:

    public interface ISaveExtentable //I suck at naming stuff :-p
    {
    
    }
    

    Then, in the code you liked to in that site, you'll find a method called: GetTypeBuilder. Change it to this:

            private static TypeBuilder GetTypeBuilder(string typeSigniture)
            {
                AssemblyName an = new AssemblyName("TempAssembly" + typeSigniture);
                AssemblyBuilder assemblyBuilder =
                    AppDomain.CurrentDomain.DefineDynamicAssembly(
                        an, AssemblyBuilderAccess.Run);
                ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
    
                TypeBuilder tb = moduleBuilder.DefineType("TempType" + typeSigniture
                                    , TypeAttributes.Public |
                                    TypeAttributes.Class |
                                    TypeAttributes.AutoClass |
                                    TypeAttributes.AnsiClass |
                                    TypeAttributes.BeforeFieldInit |
                                    TypeAttributes.AutoLayout
                                    , typeof(object), new Type[] {typeof(ISaveExtentable)});
                return tb;
            }
    

    Then, create an extension method on that interface to do the save:

        public static class SaveExtendableExtensions
        {
              public static void Save(this ISaveExtentable ise)
              {
                  //implement save functionality. 
              }
        }
    

    You'll most likely need to use reflection in your Save method to get all the properties since the type was created dynamically.

    Chris McCall : "if the XML structure will be the same every time the SP executes" I get BRD 1245 RFW 3456Name of Project!etc with different attributes each time :(
    BFree : Yes, but I'm sure there's a list of attributes that are available to choose from. Put all those attributes in the class as Nullable fields (if they are value types) and populate just the ones you get back. I don't think you need to use some crazy solution to build a type dynamically...
    Chris McCall : The XML is generated dynamically and I don't want to have to maintain a library of concrete classes for each permutation.
    Chris McCall : I like this edit. I'm not trying to ask for da codez but would you mind terribly throwing some sample code up in your answer to sketch out the idea a little better?
    Denis Vuyka : I'm afraid you are introducing a memory leak by using TypeBuilder in this case. Assemblies generated in such a way won't be unloaded from current application domain until the whole domain is unloaded (meaning application closed).
    BFree : Once an assembly is loaded into an appdomain, there's no way to unload it ever, unless you tear down the appdomain. This isn't a memory leak this is by design. Therefore, if you want to create a type dynamically, this is the way to do it. There is no other way.
  • good answer very useful.

  • I think this is called mix-ins which aren't directly or easily available in .net. However as I understand it this is one of the main reasons the LinFu framework was designed.

0 comments:

Post a Comment