C# 6, Mac, Run-time Code Generation and What’s between Them

July 30, 2015

one comment

Well, there goes my vow to write every two weeks. I have two things to say in my defense:

  • I only want to write about interesting stuff. At a women’s coding event I once attended someone said something along the lines of “men write about any fart that comes out of them”. Apparently women don’t. Or at least I don’t. So it takes time until I get to something I feel is worth your reading time.
  • In January I made a big career change and became the team leader of our embedded developers. I currently lead a team of five men, three of whom have about 20 years experience. I have none. I also don’t have managing experience. Well, I didn’t have any seven months ago. So I learned quite a lot since then. The main thing I learned is that managing five people (plus myself) can be an almost full time job. This means I didn’t learn too many technical things. Well, I learned some, but it’s basic embedded stuff. Maybe some day I’ll write about being a high-level programmer in a low-level world (I’ll give you a preview: it’s frustrating). I also learned A LOT about people and managing people and communication and all that other stuff. But I wanted to dedicate this blog to technical matters and in any case I think I prefer not to discuss personal issues here. [As a side note, I’ll be moving to a different city in October so will be seeking a new job. Maybe I will get back to learning exciting technical stuff more often :-)]

Now to the main issue we came here for. C#, Mac and run-time code generation. In my previous capacity, one of the libraries I worked on was a department-cross communication library – RPC. We had C++, Python and C# implementations for it. In C# we wanted to encourage using interfaces (rooting for abstraction and testability) so we wrote a Python script which received an interface and generated a C# client, using our RPC infrastructure library. Obviously no one used it. At some point I had a dream to make this a VS plugin which could connect to the server and generate code in the project (similarly to WCF service references). We even added code to the server which generated meta-data which could be consumed by a Python client to generate code. And again, no one used it. And it’s not like we did a bad job or that no one wanted to use it. It was just that we didn’t get to it until it was too late and everyone already had their APIs closed and clients written. That’s a topic for a different time though.

Speaking of WCF clients, you know how there’s this really annoying thing that you have to wrap each call in a try-catch block which checks whether the channel is faulted? Or alternatively you need to register on the event that the channel becomes faulted. What’s up with that? We end up wrapping each client with this boiler-plate code. That’s damn annoying. Python script to the rescue? Not this time around!!!

Well, obviously I didn’t really want to write a VS plugin. I think I’d be happy if someone else did it for me. But doing it myself wasn’t really a dream of mine. I don’t particularly like developing UI and it sounds like a nightmare to test and maintain for all VS versions/updates out there. Enter run-time code generation. This post is about my latest journey in the .NET world: generating interface-based RPC clients during run-time.

The Mac

I don’t have a personal machine with Windows. I use my husband’s old MacBook Air which can’t really run a Windows virtual machine. So he created a virtual machine in Azure for me. But I hate using it. It takes 20 minutes to start and frankly, it’s kind of slow. Also, I have to turn it off which means all my programs are shut down. And I’m lazy and spoiled. I like my programs waiting for me nicely to give them some attention. Enter Mac. With .NET becoming open source and all, I decided to give it a try. I’m working with Xamarin Studio. The cool thing about it is that it’s very similar to Visual Studio. The solution and project formats are the same (which is awesome) and the general experience is similar. There’s a package manager, an options window, there’s auto-completion and namespace resolution. Had to relearn all the keyboard shortcuts but they mostly make sense. For example, command+D is “Go to definition”. I suppose the “D” is there for Definition. Unlike F12 which is for what exactly?? So the overall experience was OK. But really just OK. There are problems with the intellisense and sometimes it does weird stuff. I think mostly it happened when there were some compilation errors. Sometimes half my code was colored red and half was colored black. Just because it felt like it, I suppose. Also, maybe it’s just me having hard time with the Mac shortcuts but sometimes random bits of code would appear out of the blue. Lines would get split in the middle.

On a more technical note, trying to work with WCF surfaced some differences between the Windows implementation and Mono – there’s a bug in the Web reference feature (they get the namespace wrong), Mono doesn’t support setting a timeout in BasicHttpBinding, and it seems that in Mono getting a timeout doesn’t fault the channel (a very quick search didn’t get me any corroboration though).

C# 6

Since I had some string formatting to do, e.g. generating stuff like

public {returnType} {methodName}({parameterType} parameter)
{
    return ({returnType})_client.ExecuteMethod ("{remoteMethodName}", parameter);
}

I figured it would be a good idea to use the new C# 6 syntax for string formatting. But this ended up in lines of code similar to this:

"\n\tpublic{returnType}{methodName}({parameterType}parameter)\n\t{\n\t\treturn({returnType})_client.ExecuteMethod(\"{remoteMethodName}\",parameter);\n\t}";

The formatting with C# 6 worked and indeed the feature is cool but obviously one can’t write too much code like this. I couldn’t get to compile my run-time generated code for too long, so I ended up using template text files as embedded resources in the project. Unfortunately I couldn’t figure out whether it was possible to somehow use the new formatting with string variable rather than string literals, so I ended up using String.Replace, which was ugly but also wasn’t my main purpose. As a big boss, I now understand why my old boss kept talking to me about differentiating between important and non-important tasks. My main purpose was run-time code generation, not string formatting.

Run-Time Code Generation

To some extent this is the most uninteresting part of this whole experience. It’s merely using an API. This is the main method:

public static T GenerateRpcClient<T> (IRpcClient client) where T : class
{
	string code = GenerateInterfaceWrapperCode<T> (); 
	var provider = CodeDomProvider.CreateProvider("CSharp");
	var parameters = new CompilerParameters ();
	parameters.ReferencedAssemblies.Add (typeof(T).Assembly.Location);
	var result = provider.CompileAssemblyFromSource (parameters, code);
	if (result.Errors.HasErrors) 
	{
		throw new Exception ("Could not compile auto-generated code");
	}

	var smartClientType = result.CompiledAssembly.GetType ("SmartClient");
	return (T)Activator.CreateInstance (smartClientType, new object[] { client }, null);
}

The rest is just background. But it was very exciting to see it work. First thing I did, before even writing this blog post was writing an e-mail with the subject “you might find this useful” to my old boss.

The last topic that I need to address is debugging this thing. What happens if we end up having to debug the auto-generated code? I tried the code as above both on my Mac and on a Windows machine. In Xamarin Studio I got a disassembly of the code:

Xamarin Studio IL Disassembly

Xamarin Studio IL Disassembly

Same (only much worse – actual assembly!!) in Visual Studio 2015 (which is finally out!):

Visual Studio Assembly Disassembly

Visual Studio Assembly Disassembly

Thank God for StackOverflow though. Obviously I’m not the first one who wants to debug this thing. 6 years later, and this answer still rocks!

public static T GenerateRpcClient<T>(IRpcClient client) where T : class
{
    string code = GenerateInterfaceWrapperCode<T>();
    var provider = CodeDomProvider.CreateProvider("CSharp");
    var parameters = new CompilerParameters()
    {
        IncludeDebugInformation = true,
        TempFiles = new TempFileCollection(Path.GetTempPath(), true)
    };
    parameters.ReferencedAssemblies.Add (typeof(T).Assembly.Location);
    var result = provider.CompileAssemblyFromSource (parameters, code);
    if (result.Errors.HasErrors) 
    {
        throw new Exception ("Could not compile auto-generated code");
    }

    var smartClientType = result.CompiledAssembly.GetType ("SmartClient");
    return (T)Activator.CreateInstance (smartClientType, new object[] { client }, null);
}

Add two magical lines and the code can be debugged both in Xamarin Studio:

Xamarin Studio Temporary File Debugging

Xamarin Studio Temporary File Debugging

And in Visual Studio:

Visual Studio Temporary File Debugging

Visual Studio Temporary File Debugging

The only thing I might change about this is disabling the mechanism on production.

I also started writing a self-fixing smart WCF client. But I encountered some issues with different behavior on Windows and on Mono. As my husband told me several times during the last couple of days – the worst enemy of a good job is a perfect job. So I’ll stop now. I know how to generate code at run-time, I know how to debug it and I know its potential. Now you do too.

As always, all the code can be found on my GitHub repository.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

one comment

  1. Kevin GosseAugust 3, 2015 ב 12:54 PM

    To inject the boilerplate code for WCF, an alternative is to use the RealProxy class to generate a proxy around the service interface (IRemoteMathOperations in your example). From there, you can do whatever check is needed before forwarding the call to the IChannel.

    Reply