CodeDom needs help.
So a few weeks ago I got an idea to write a little helper shim that would make it easier to pass a parameter into a managed thread proc (the ThreadStart delegate type doesn't accept parameters). What I ultimately decided was to write a little wrapper class that took an object array as parms to Start() and passed them to a thread object held internally. The wrapper class "ThreadEx" would behave just like Thread, except that it would take that extra parameter to Start.
Then I decided that that this was a generically useful thing to do. Some scenarios where this kind of wrapping/composition would be useful include:
So I decided to write a tool that would take a class as input and produce a class that wraps it. So for example if the original class looked like this:
class A { public int myField; public void method(int a, int b); }
the wrapper would look like this:
class AEx { A wrapped = new A(); public int myField { get { return wrapped.myField; } set { wrapped.myField == value; } } public void method(int a, int b) { wrapped.method(a,b); } }
This seemed like a fun little project. It seemed like I could re-expose all the interfaces, forward the methods, and expose public properties and fields as properties on the wrapper. I had toyed with code generation with System.CodeDom a bit and decided it would be the perfect technology for implementing the tool, because then users of the tool could choose C# or VB.NET for their wrappers.
The ida behind CodeDom is pretty straightforward. You generate an object model with objects that represent "code" things like classes, methods, properties, and events. The objects in this model are somewhat language-neutral and this model is consumed by Language specific Providers that then produce source code in that language.
When I started writing the code it indeed started out pretty easily, but it didn't take too long for limitations of CodeDom to appear that basically make it impossible to build such a tool without some nasty hacks. Nearly all of them showed up when I tried to wrap class System.String. The problem is that some languages can handle some constructs (like say, unsafe keywords) and some familiar language constructs don't really have 1::1 mappings into IL/Metadata (e.g. Indexers). In addition CodeDom can't handle some types of constructs. The ones I ran into included:
CodeDom can't emit nested namespaces. This one doesn't affect my tool that much but it does kind of suck.
CodeDom can't be used to emit custom Add/Remove functions for events. For many events this wouldn't matter, but it matters a lot if the wrapped class provides implementation with a construct like
public event myDelegateType MyEvent { add{...} remove{...} }
CodeDom cannot properly represent indexers. I wound up emitting indexers by using CodeSnippets, which are basically string values. Indexers are actually implemented as properties marked with a [DefaultMember] attribute. CodeDOM has a CodeIndexerExpression class (useful if you want to call an indexer) but no CodeMemberIndexer class to use if you want to actually *implement* an indexer. I wound up generating strings manually and saving them into the CodeDOM tree as CodeSnippets. This sucks because CodeSnippets are language-specific.
CodeDom cannot represent explicit interface implementations. Explicit interface implementation functions are kind of strange, because they are sort of private. and sort of public. I haven't been able to find a way to represent them in the CodeDom. For example, if the wrapped type has a method with a signature like void iface.method() then there is no way to write a CodeDom tree that will emit the proper function declaration in VB, e.g:
Overloads Function test(ByVal a As Integer, ByRef b As Integer, ByRef c As Integer) As Integer Implements ITest.test
A somewhat amusing thing I ran into as well was that in VB parameter names must not be the same as the method name. So for example, if I wrap String.Format in VB by default I get this:
Public Overloads Shared Function Format(ByVal format As String, ByVal arg0 As Object) As String Return String.Format(format, arg0) End Function
This would be fine in C#, but in VB this declaration breaks because the first parameter has the same name as the function (This proves that System.String wasn't implemented in VB!)
CodeDom can't hold the "unsafe" keyword. This makes some sense, because VB can't implement unsafe code at all, but it's still irritating that it's not a method flag. System.String for example has constructors that take a Char * initializer. I can dutifully emit the "Char *" but I can't easily emit the "unsafe" keyword.
All of these together meant that writing a completely capable wrapper generator is well-night impossible with CodeDom. CodeDom has other limitations as well (Ian Griffiths tells me it can't emit switch statements for example) that really degrade its usefulness. My little tool currently works for the 80% case but the interesting classes to wrap almost all have some features my tool can't handle. CodeDom is a fairly interesting idea - here's hoping it gets a little TLC someday...
9:37:07 AM
|