3

We want to give a user a graphical format to design conditional statements on some data. Our application would take that graphical format, translate it into C#, compile it, and run the conditional statement against some data, returning a Boolean.

The catch is that these conditional statements need to be written and compiled (and, of course, executed) at runtime, since we won’t be rebuilding the application each time the user creates a new conditional statement.

We thought about using LINQ expression trees, but a compiled LINQ expression tree can’t be saved, which means we need to recompile each time the conditional statement is going to be executed.

We think a better alternative is to use CodeDOM to compile the conditional statement as a .dll (they would be converted into a static method of a static class that takes, as a parameter, the data to run against the conditional statement). This allows us to save the compiled statement, and we could load and unload the .dll at runtime. Also, it’s easier to generate the C# if-statement than the LINQ expression tree.

Alternatively, we could use Roslyn to generate the .dll’s. This is reportedly faster than CodeDOM, but Roslyn is still in CTP.

Are there hidden pitfalls, or a general pattern for doing this, that we should know about? Aside from being very careful to generate only functions that test against data (and does not modify data or allow invoking any other functions), what else should we be careful about? Will loading and unloading (potentially hundreds of) these .dll’s cause issues? If each .dll is given its own unique namespace, will loading and unloading (potentially hundreds of) them leave artifacts?

Hungry Beast
  • 3,677
  • 7
  • 49
  • 75
  • 1
    You could consider leveraging a runtime expression evaluator, such as [FLEE](http://flee.codeplex.com/) or [NCalc](http://ncalc.codeplex.com/). They're pretty easy to get up and running, you could easily have them execute some user-defined predicate, and you can (generally) restrict the user code to only the APIs you wish to expose (to reduce the possibility of malicious use). EDIT: Here's a quick example of using FLEE to generate `DateTime` values; it would be trivial to return `bool` instead: http://stackoverflow.com/questions/14730219/using-csharpcodeprovider-to-allow-user-created-functions – Chris Sinclair Apr 22 '13 at 22:16
  • Chris's is a good idea. Probably better performance, and maybe even some safety/sandboxing to protect your process from users who right bad C#(even if just accidentally). Both of those things could be alot of work to get right. I'd still be interested in seeing thoughtful answers to your questions though, just out of curiosity of general approaches to this kind of solution. – AaronLS Apr 22 '13 at 22:33
  • Any particular reason you aren't investigating windows workflow? The designer can be embedded in your application. http://msdn.microsoft.com/en-us/library/aa480213.aspx – NotMe Apr 22 '13 at 22:58
  • 3
    One more `Chris` and we'll be thoroughly confused here. – NSGaga-mostly-inactive Apr 22 '13 at 23:03

3 Answers3

3

we need to recompile each time the conditional statement is going to be executed

I don't think this is going to be a problem. Unless you need to compile many expressions per second, the performance hit of compiling an expression tree shouldn't be noticeable.

we could load and unload the .dll at runtime

Not really. You can't unload a normal assembly in .Net. A collectible assembly will be unloaded once it's detected it's not used, but that works only for dynamic assemblies (not ones loaded from disk). You can also unload an AppDomain, which also unloads all assemblies loaded into that domain, but that means running your statements in a separate AppDomain.

Also, it’s easier to generate the C# if-statement than the LINQ expression tree.

Does that really matter? You're going to write that code only once. And I don't think creating the if statement is actually that much harder with expression trees, once you know how to do it. Especially when compared with Roslyn, which is extremely verbose when used for code generation (because that's not its primary use case).

[Roslyn] is reportedly faster than CodeDOM, but Roslyn is still in CTP.

Could you quote a source for that? But I really doubt that the speed of compilation would be actually important for you.

If each .dll is given its own unique namespace …

That doesn't make any sense, a DLL doesn't have a namespace. In fact, the CLR doesn't really deal with namespaces, it just sees a class that has dots in its name.

svick
  • 236,525
  • 50
  • 385
  • 514
0

Just a few additional notes (on @svick -s answer which I 2nd for the most of it), different angle...

  1. I'd say to go with Roslyn if you can - depending on what you target. You'll get a next-gen tool for what you need and a true 'compiler service' as marketed :), but seriously.

  2. If you don't have extensive expression trees to build - i.e. limited set - I'd recommend that as a second option. You cannot unload dll-s with CodeDom, unless via AppDomain which is pain to 'communicate' (cross-proc basically).

  3. If you have more 'arbitrary' user code to run - or full 'files' with C# - which would mean that you'd have to pretty much mimic C# - then use CodeDom. CodeDOM is solid and it works, but I'd go in this order.

svick
  • 236,525
  • 50
  • 385
  • 514
NSGaga-mostly-inactive
  • 14,052
  • 3
  • 41
  • 51
0

It is anything but trivial to accomplish but you may want to consider dynamically creating the assembly in memory by emitting the IL yourself.

And example of this can be found here. You'll be doing something like this:

AppDomain domain = Thread.GetDomain();
// create a new assembly for the proxy
AssemblyBuilder assemblyBuilder = 
    domain.DefineDynamicAssembly(
        new AssemblyName("ProxyAssembly"), 
            AssemblyBuilderAccess.Run);

// create a new module for the proxy
ModuleBuilder moduleBuilder = 
    assemblyBuilder.DefineDynamicModule("ProxyModule", true);

// Set the class to be public and sealed
TypeAttributes typeAttributes = 
    TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Sealed;

// Construct the type builder
TypeBuilder typeBuilder = 
    moduleBuilder.DefineType(typeof(TInterface).Name 
    + "Proxy", typeAttributes, channelType);

List<Type> allInterfaces = new List<Type>(typeof(TInterface).GetInterfaces());
allInterfaces.Add(typeof(TInterface));

//add the interface
typeBuilder.AddInterfaceImplementation(typeof(TInterface));

//construct the constructor
Type[] ctorArgTypes = new Type[] { ctorArgType };
CreateConstructor(channelType, typeBuilder, ctorArgTypes);

//...

//construct the method builders from the method infos defined in the interface
List<MethodInfo> methods = GetAllMethods(allInterfaces);
foreach (MethodInfo methodInfo in methods)
{
    MethodBuilder methodBuilder = 
        ConstructMethod(channelType, methodInfo, typeBuilder, 
            ldindOpCodeTypeMap, stindOpCodeTypeMap);
    typeBuilder.DefineMethodOverride(methodBuilder, methodInfo);
}

//create the type and construct an instance
Type t = typeBuilder.CreateType();
TInterface instance = 
    (TInterface)t.GetConstructor(ctorArgTypes).Invoke(
        new object[] { channelCtorValue });

return instance;

Hope this helps.

Tyler Jensen
  • 795
  • 4
  • 9