Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Dynamic Encapsulation: On and off line hierarchy modification

0.00/5 (No votes)
18 Feb 2008 1  
This idea shows how to encapsulate objects from different hierarchies into a single “interface”. Later, I’ll show the way C# could be modified to include this feature as a built-in syntactic resource.

Introduction

While working with a data bound application, I needed to use a DataSource for several controls to form different hierarchies (Windows.Forms and Web.UI.WebControls). All of them had this property, but they didn't inherit it from a common ancestor class or interface. If DataSource was a Method, delegates could be the way to factorize my code. As it isn't, there is no C# way to solve my problem.

The need

Let's take these two classes. They would share the same methods, or some of them, and the same properties but not the same base class or interface implementation; their common ancestor is object. They also do pretty similar stuff. They're just not mine, so I can't modify the hierarchy.

1.png

Figure 1: Hierarchy "The Need" and a Client

Then I have this client class that can work with either of them through some method. I'll need to build a couple of overloads, doing exactly the same:

public class SomeAndAnotherClient
{
  public void DoSomething(ISomeInterface some)
  {
    some.Method1();
    some.Method2("string1", "string2");
    int x = some.Property1;
    string y = some.Property2;
  } 
  public void DoSomething(IAnotherInterface some)
  {
    some.Method1();
    some.Method2("string2", "string2");
    int x = some.Property1;
    string y = some.Property2;
  }
}
Listing 1: Client class using overloads

This approach is bearable but it could become annoying if there were many situations like this in the same project, month... lifetime. Depends on how patient you might be, I'm not.

There is also another issue, later I might get a third class I like to use in the same task, also providing the same methods and properties and also with a common ancestor object. Now I would need to make a third overload, which is no problem, if you were patient. What if my product has been released? I'll need to release version two, just because I'd like to use a third class.

The proxy

Using a single proxy class, you can pack this problem into a single module, which can be updated whenever necessary.

public class SomeAndAnotherProxy { 
  protected SomeClass obj_SomeClass;
  protected AnotherClass obj_AnotherClass; 
  public SomeAndAnotherProxy(SomeClass some) { ... }
  public SomeAndAnotherProxy(AnotherClass another) { ... }
  public int Property1 { 
    get 
    { 
      if (obj_SomeClass != null)
        return obj_SomeClass.Property1;
      else return obj_AnotherClass.Property1; 
     }  
  } 
  public void Method1() {  
    if (obj_SomeClass != null)
      obj_SomeClass.Method1();
    else obj_AnotherClass.Method1(); 
  }  
  // Same for Property2 and Method2(params string[]) 
}
Listing 2: Proxy class

Now that I have a guy to proxy for me, I don't need overloads in my client class. The really hard task is to add a new class to my proxy; I'll need to create new constructor overloads and add conditional lines to every single property and method.

I'm not patient, proxy isn't good enough.

Delegates-based proxy

Let's imagine we are dealing with methods only, no properties on "Some" and "Another" classes. I could then create a delegate type for every method "kind" (signature) and an assignable member of the correct delegate type for each method; I'll name each member the same as the method they would reference to.

public class DelegatesProxy {
  public delegate void Method1Dlg();
  public delegate void Method2Dlg(params string[] args);

  public Method1Dlg Method1;
  public Method2Dlg Method2;
}

public static class UsingAllOfIt {
  public static void UsingDelegatesProxy()
  {
    SomeClass some = new SomeClass() ;
    AnotherClass another = new AnotherClass() ;
    // Creating a Proxy for SomeClass instance
    DelegatesProxy proxy = new DelegatesProxy();
    proxy.Method1 += some.Method1;
    proxy.Method2 += some.Method2;
    
    SomeAndAnotherClient.DoSomething(proxy);
    
    // Creating a Proxy for AnotherClass instance;
    proxy = new DelegatesProxy();
    proxy.Method1 += another.Method1;
    proxy.Method2 += another.Method2;
  }
}
Listing 3: Proxy and delegates

Love this approach!! Don't you? Every time I want to include a new class, I need to change... absolutely nothing, just create a proxy instance and correctly bind all the methods to their delegates. Unfortunately, there are issues here. It is easy to make mistakes, for instance: I could bind members incorrectly and I cannot reference properties using delegates. Beautiful, but still not enough.

Delegates and properties

I'll take a couple of delegates, one for "set" one for "get", we could also think of indexable properties, but this extension can be done easily, and as now we are talking C#, no indices for properties.

public class DelegatesProxyWithProperies : DelegatesProxy
{  
  public delegate string get_Property1Dlg();
  public delegate int get_Property2Dlg();
  public delegate void set_Property2Dlg();
  public get_Property1Dlg get_Property1;
  public set_Property2Dlg set_Property2;
  public get_Property2Dlg get_Property2;
}
Listing 4: A proxy with property delegates

We can reference both methods and delegates now. We'll need to use Reflection to create getter and setter references.

proxy.get_Property1 += (DelegatesProxyWithProperies.get_Property1Dlg)
  Delegate.CreateDelegate(typeof(DelegatesProxyWithProperies.get_Property1Dlg),
    some, "get_Property1");  
proxy.get_Property2 += (DelegatesProxyWithProperies.get_Property2Dlg)
  Delegate.CreateDelegate(typeof(DelegatesProxyWithProperies.get_Property2Dlg),
    some, "get_Property2");
proxy.set_Property2 += (DelegatesProxyWithProperies.set_Property2Dlg)
  Delegate.CreateDelegate(typeof(DelegatesProxyWithProperies.set_Property2Dlg),
    some, "set_Property2"); 
Listing 5: Creating property delegates

Right here, we can completely encapsulate objects with similar properties and methods. Though it is so hard to write it down every time, we can make many, many mistakes doing so. I think this one is good, but there's something missing.

Just to make it easier

I created a class to make all this process easier. It plays the "Property Delegate" role.

2.png

Figure 2: A delegate for properties

You would access property delegate values through the property Value. An implicit operator was implemented when using the property as a Right value. Unfortunately, the Left side assigns must be done through Value.

PropertyDelegate<int> Property1 = new PropertyDelegate<int>(some, "Property1");  
int x = Property1; 
Console.WriteLine("x={0}", x);
Property1.Value = 12345 ;
x = Property1 ;
Console.WriteLine("x={0}", x);
Listing 6: Using property delegates

To create a class using this approach instead of lose delegates for setter and getter:

public class DelegatesProxyWithPropertyDelegates : DelegatesProxy  
{
  public PropertyDelegate<int> Property1;
  public PropertyDelegate<string> Property2;
}
Listing 7: A proxy using PropertyDelegates

Using it will be just the same. There's another issue here. Properties can be read/write, readonly, or writeonly (which I've never used). I could (actually have) created three classes, one for each situation, changes nothing; right now, we bind properties to their delegates as if they were always read/write.

How about events?

If we want to include every interface-valid instance member, we cannot leave them aside. Events behave pretty much like properties, they just don't have accessors but "add" and "remove" operations. Even easier, both add and remove use the same signature. Skipping the middle approach I used for properties, an event delegate would look like:

3.png

Figure 3: Event delegates

DelegateType represents the event type. As I cannot use System.Delegate on Generics constrains, it is unconstrained and there is a clear mistake that can be made by using it. Once more, be careful when using this. There is a useful feature here. Events are never assigned to or from, so an implicit operator in both directions is not useful, but overloading "+" and "-" operators, we get a great result. We can use them as if they were the original events.

EventDelegate<EventHandler> handler = 
         new EventDelegate<EventHandler>(some, "Event1");
handler += new EventHandler(Handler);

some.OnEvent1(EventArgs.Empty);

handler -= new EventHandler(Handler);
some.OnEvent1(EventArgs.Empty); 
Listing 8: Using event delegates

In the previous listing, OnEvent1 is just a public trigger for the event Event1.

Putting it all together

So far we have a proxy class with members that can reference methods, properties, and events. Let's create a proxy class using all of it.

public class FullProxy
{
  public delegate void Method1Dlg();
  public delegate void Method2Dlg(params string[] args);  

  public Method1Dlg Method1;
  public Method2Dlg Method2;
  
  public ReadOnlyPropertyDelegate<int> Property1;
  public PropertyDelegate<string> Property2;
 
  public EventDelegate<EventHandler> Event1;
}
Listing 9: A full proxy

Using these class instances, we can completely encapsulate, in the same way we used to do using interfaces, types from different hierarchies.

FullProxy full = new FullProxy();

full.Method1 = new FullProxy.Method1Dlg(some.Method1);
full.Method2 = new FullProxy.Method2Dlg(some.Method2);
full.Property1 = new ReadOnlyPropertyDelegate<int>(some, "Property1");
full.Property2 = new PropertyDelegate<string>(some, "Property2");
full.Event1 = new EventDelegate<EventHandler>(some, "Event1");
full.Event1 += Handler;

some.OnEvent1(EventArgs.Empty);
full.Method1();
full.Method2("string1", "string2");
full.Property1.Value = (int)DateTime.Now.Ticks;
int x = full.Property1; // same as full.Property1.Value
int y = full.Property1.Value; // same as full.Property1
string s = full.Property2; 
Listing 10: Using a full proxy

Definitely this is the one, it just needs some makeup.

The rules

Let's encapsulate the type Hypothetical. We should only care for the interface valid members: Methods, Properties, and Events.

<visibility> <type_descriptor> Hypothetical
{
  // 1 .. N1 Read / Write Properties
  Type1i Property1i { get ... set ...}

  // 1 .. N2 Readonly Properties
  Type2i Property2i { get ... }

  // 1 .. N3 Writeonly Properties
  Type3i Property3i { set ... }

  // 1 .. N4 Methods Type4i
  Methodi(<parameter_specs>i)

  // 1 .. N5 Events
  event Type5i Eventi;
}
// <type_descriptor> : class | struct | interface 
Listing 11: Hypothetical type

Encapsulating the proxy for Hypothetical will be:

public class HypotheticalProxy
{
  // 1 .. N1 Read / Write Properties
  public PropertyDelegate<Type1i> Property1i ;

  // 1 .. N2 Readonly Properties
  public ReadOnlyPropertyDelegate<Type2i> Property2i ;

  // 1 .. N3 Writeonly Properties
  public WriteOnlyPropertyDelegate<Type3i> Property3i ;

  // 1 .. N4 Methods 
  delegate Type4i MethodiDlg(<parameter_specs>i) ;
  public MethodiDlg Methodi ;

  // 1 .. N5 Events 
  public event Type5i Eventi ;
}
Listing 12: Hypothetical proxy

So far

Let's see what problems we still might have:

  • Wrong binds:
    • Not existing members.
    • Wrong member types and/or parameters.
    • Wrong members, e.g., two methods F and G having the same signature and binding method F on target to field G on proxy, G on target to field F on proxy.
  • Inconsistent accessor for properties, e.g., bind a readonly property to a read/write property delegate.
  • Slow member invocations.
  • Slow binds.

Bindings can be automated so these mistakes won't be made. Member invocations proved to be fast enough, see statistics. Bindings will be slow, some approaches will be used to make this a minor issue, but if you need to bind once and use many times, there is not going to be great performance degradation on your application; for multiple bindings, it definitely will.

Automating bindings

Creating classes which can do all binding work, we get:

4.png

Figure 4: DynamicInterface

After shaping my library a little, I get this. Member delegates and a DynamicInterface. Proxies will decent from DynamicInterface. Descendents of DynamicInterface are supposed to define only fields of type PropertyDelegate for read/write properties, ReadOnlyPropertyDelegate for readonly properties, EventDelegate for events and delegates for methods.

public class DynamicInterfaceProxy : DynamicInterface
{
  public delegate void Method1Dlg();
  public delegate void Method2Dlg(params string[] args);

  public Method1Dlg Method1;
  public Method2Dlg Method2;

  public ReadOnlyPropertyDelegate<int> Property1;
  public PropertyDelegate<string> Property2;

  public EventDelegate<EventHandler> Event1;

  public DynamicInterfaceProxy() {}
  public DynamicInterfaceProxy(object instance) : base(instance) { }
}
Listing 13: Creating dynamic interface descendents

Looks pretty much the same as FullProxy, isn't it? DynamicBind iterates through all fields in descendent types, binding them to correct members, delegates to methods, ReadonlyPropertyDelegate fields to readonly properties, EventDelegate fields to events, and so. Now I have two ways to bind proxies with targets, during proxy creation or using DynamicBind.

SomeClass some = new SomeClass();
AnotherClass another = new AnotherClass();

// Creating a bound proxy instance.
DynamicInterfaceProxy dynProxy = new DynamicInterfaceProxy(some);

dynProxy.Event1 += Handler;
some.OnEvent1(EventArgs.Empty);

dynProxy.DynamicBind(another); // Binding to another target.

dynProxy.Event1 += Handler;
another.OnEvent1(EventArgs.Empty); 
Listing 14: Binding proxies

Speeding bindings up

I've been using Reflection a lot. Reflection works great, but slow, very. Let's use Reflection but just one time, like a compiler would. Instead of binding member by member using Reflection, I will create (using emit) a class to do fast bindings; class creation would be slow, but you can do it just once for the whole application execution. I'll allow saving this binding class, just in case you would want it for later use or want to create the binder class just once.

5.png

Figure 5: Binders

A binder will contain the exact code needed to make assigns pretty fast. The bindings generator creates a class for every <Proxy, Target> pair you want and places them into a dynamically generated assembly which can be saved and be statically used later.

I guess naming them "static" is not quite correct, but as they can be used statically or dynamically, I chose to name them after the most attractive way.

Now let's take a very small proxy and see how it works:

public class SmallProxy  
{
  PropertyDelegate<string> Property2;
}
...
BindingsGenerator gen = new BindingsGenerator("Small");
IStaticBinder<SmallProxy, SomeClass> binder = 
  gen.CreateStaticBinder<SmallProxy, SomeClass>();

gen.SaveAssembly("c:\\temp");
...
SmallProxy small = binder.Bind(some); 
Listing 15: Binder generation

The instance binder obtained from CreateStaticBinder can be used to do SmallProxy to SomeClass bindings, and we can include a reference to c:\temp\Small.dll in our project to do the same. There are two overloads for the method CreateStaticBinder. Use the generic one if you know both the Proxy and Target types at compile time.

Binder from the inside

6.png

Figure 6: ILDasm look at a binder

Using ILDasm Small.dll, we can see it contains a type named SomeClass_TO_SmallProxy. This naming I chose for binder types to be easily recognized for later use. You can have as many binders as you want in one assembly; just avoid naming conflicts. Type names are quite long because I'm using Generics and nested types.

Close look at Bind, also from the inside

Now let's take a look at the MSIL code generated by BindingsGenerator. Type names have been simplified so they are readable.

.method public hidebysig virtual final instance class SmallProxy
  Bind(class SomeClass [1]) cil managed
{
  .maxstack  5
  .locals init (class SmallProxy [0])

  newobj     instance void SmallProxy::.ctor()
  stloc.0
  
  ldloc.0
  ldarg      1
  ldftn      instance string SomeClass::get_Property2()
  newobj     instance void class PropertyDelegate<string>/PropertyGetter<string>::
               .ctor(object, native int) 
  ldarg      1 
  ldftn      instance void SomeClass::set_Property2(string)
  newobj     instance void class PropertyDelegate<string>/PropertySetter<string>::
               .ctor(object,native int) 
  
  newobj     instance void class PropertyDelegate<string>::
               .ctor(class PropertyDelegate<string>/PropertyGetter<string>, 
                     class PropertyDelegate<string>/PropertySetter<string>)

  stfld      class PropertyDelegate<string> SmallProxy::Property2
  ldloc.0
  ret 
}
Listing 17: Bind's MSIL

First things first: BindingsGenerator creates the resulting instance using the default constructor and stores it in a local variable, proxies must have one. Later, it creates delegates for both the getter and setter using the target instance and a function pointer. Then it creates a PropertyDelegate using both delegates and stores it in the Property1 field. In the end, it loads SmallProxy on the stack to be returned. For readonly and writeonly properties, it will generate the creation of a single delegate. For events, again two delegates will be created. There will be as many blocks as there are members in the proxy.

BindingsGenerator does pretty much the same a compiler would if it supports encapsulation. Instead, a compiler could generate a binder for every kind of assign used.

Runtime statistics

Would it be faster? It is impossible to tell without testing it. Let's take CompleteProxy and test some loops:

public class CompleteProxy : DynamicInterface
{
  public PropertyDelegate<string> Property2;
  public ReadOnlyPropertyDelegate<int> Property1;
  public WriteOnlyPropertyDelegate<string> Property3;
  public EventDelegate<EventHandler> Event1;
  public delegate void Method1Dlg();
  public delegate void Method2Dlg(params string[] args);
  public Method1Dlg Method1;
  public Method2Dlg Method2;
}
...
// Init goes here, just one time for loops marked "Init"
// All initializations:
//
// staticProxy = binder.Bind(some) ;
// dynamicProxy.DynamicBind(some) ;
// some = new SomeClass() ;
DateTime start = DateTime.Now;
for (int count = 0; count <= 10000; count++)
{
  // Loop, can include or not initializations.
}
DateTime end = DateTime.Now ;
DisplayStats(start, end) ;
Listing 18: Benchmark code

Table 1: Benchmark results

Loop Code Time (ms)

Some dynamic bound

dynamicProxy.DynamicBind(some);

dynamicProxy.Method1();

3890

Some static bound

staticProxy = binder.Bind(some);

staticProxy.Method1() ;

1640

Direct call

some = new Some() ;

some.Method1() ;

1578

Some dynamic bound, Init

dynamicProxy.Method1() ;

1546

Some static bound, Init

staticProxy.Method1() ;

1531

Direct call, Init

some.Method1() ;

1562

Dynamic assigns are clearly the worst; dynamic are only a little bit slower than creating instances of SomeClass directly; this behavior will not always be like this. Target class constructions can take much time, they might need for resources to become ready or so, and proxy instances just need to initialize their members, it will always take the same time. Once bound, no loop seems to be faster or slower.

Let's dream

If Anders Hejlsberg likes this idea, he might include this on C# 4. Same as yield and value; they can include a new context sensitive keyword encapsulation which will be used like this.

public encapsulation Proxy
{
  int Property1 {get;}
  string Property2 { get; set; }
  void Method1();
  void Method2(params string[] args);
  event EventHandler Event1 ;
}
Listing 19: The sweetest dream about C# 4

The C# compiler will generate a class like my DynamicInterface. Some custom attributes could help when using proxies so IDEs show members as what they represent (properties, methods, events) instead of what they actually are (fields).

public class Proxy  
{
  public void delegate Method1Dlg() ;
  public void delegate Method2Dlg() ;

  [ShowAs(AutoCompleteStyle.Property)]
  public ReaonlyPropertyDelegate<int> Property1 ;

  [ShowAs(AutoCompleteStyle.Property)] 
  public PropertyDelegate<string> Property2 ;

  [ShowAs(AutoCompleteStyle.Method)]
  public Method1Dlg Method1 ;

  [ShowAs(AutoCompleteStyle.Method)]
  public Method2Dlg Method2 ;

  [ShowAs(AutoCompleteStyle.Event)]
  public EventDelegate<EventHandler> ;
}
Listing 20: Dream C# 4 generated proxy

For every assign, they would generate MSIL similar to that my BindingsGenerator creates. Two solutions are possible: in-lining code for initializing all fields in the proxy, or creating a class to do the job (like binders).

Conclusions: Should I say TO DOs

I still need to fix much, but I think this library is already useful. I think this concept may help many developers or at least hope so. I still need to delegate indexers but it is not difficult. I also need to throw better messages in my Exceptions.

References

  • Meyer Bertrand: Object-Oriented Software Construction Second Edition. Prentice Hall, 2002.
  • Lidin Serge: Inside Microsoft .NET IL Assembler. Microsoft Press, 2002.
  • Barr Daniel: Three C# Tips: Indexed properties, property delegates, and read-only subproperties. csharptips.aspx.
  • Call properties through delegates and pass them by Reference, http://geekswithblogs.net/akraus1/archive/2006/02/10/69047.aspx.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here