0

I have found some questions here on SO that have shown several methods to instantiate a class from a string, the only one i found how to make work was Activator.CreateInstance. Knowing it wasn't the fastest i tried to find something else and found Compiled Expressions.

Now, how could i implement a Compiled Expression to instantiate a new class based on a given string as its type ? Is it possible ?

This is my code:

public List<HtmlBlock> ParseBlocks( Page page, ControllerContext controller )
{
    var nodeBlocks = GetNodes( page.html );
    var blocks = new List<HtmlBlock>();

    Parallel.ForEach( nodeBlocks, block => blocks.Add( ParseNode( block, controller ) ) );

    return blocks;
}

private static HtmlBlock ParseNode( HtmlBlock block, ControllerContext controller )
{
    try
    {
        //Instantiate the class
        var type = Activator.CreateInstance( null, "Site.ViewModels." + block.Type );

        //Populate selected template
        block.SetHtml( new HelperController().RenderView( block.Template, type.Unwrap(), controller ) );

        return block;
    }
    //Suppress any error since we just want to hide the block on parse error
    catch (Exception)
    {
        block.SetHtml( "" );

        return block;
    }
}

Just to give some context, i'm trying to create a custom template builder, the user can input an HTML tag like this one:

<template dir="_Courses" where="active=1" order="name" type="CoursesViewModel"></template>

And i will render the selected template with data from my DB. What i need is to instantiate the CoursesViewModel with 4 parameters: string where, string select, string order, int take, those are my query filtering parameters.

OBS: I have also tried to work with FastActivator but to use it i have to also use `Type.GetType( "Site.ViewModels." + block.Type ) and i thik that it would end up as costly as my other option, it this right ?

EDIT 1

I have executed two tests using my MVC application and applied 3 different methods, the results are in miliseconds and where used 20k iterations. The third one is a method that uses a switch/case to look for the correct class by

1) ViewModelFactory.CreateInstance("NameSpace.ClassName", "", "", "", 0)
2) Activator.CreateInstance(null, "NameSpace.ClassName")
3) HtmlParser.GetClassType("ClassName")

------------------------------------------------------------------------
   1st Test   2nd Test | 20k
1) 93068    | 110499
2) 117460   | 89995
3) 82866    | 77477

I used a PasteBin to share the code. Oddly the methods have worked differently on each case, on the first execution @Ivan Stoev was the slowest code but on the page refresh his code worked better and my switch/case was the fastest. Could anyone please explain why this ?

EDIT 2

These tests implemented the changed version of Ivan Stoev's code where the Dictionary was changed to ConcurrentDictionary and the Activator was implemented with parameters

1) ViewModelFactory.CreateInstance( "ClassName", "", "", "", 0 )

2) var type = Type.GetType( "NameSpace.ClassName" );
   var obj = Activator.CreateInstance( type, new object[] { "", "", "", 0 } );

3) HtmlParser.GetClassType("ClassName")

------------------------------------------------------------------------
   1st Test   2nd Test | 200k
1) 3418     | 3674
2) 5759     | 5859
3) 3776     | 4117

Here is the bin with the modified code: PasteBin

Community
  • 1
  • 1
Ariel
  • 911
  • 1
  • 15
  • 40
  • 1
    I don't know if it'll get any faster. You'll need to use some form of reflection no matter what. Unless... you have a fixed number of classes and just instantiated directly. – Jeff Mercado Mar 14 '16 at 18:43
  • @JeffMercado, what do you mean with instantiate directly ? I can't use `Compiled Expressions` ? – Ariel Mar 14 '16 at 18:45
  • As in doing something like: `if (block.Type == "SomeType") return new SomeType();` – Jeff Mercado Mar 14 '16 at 18:46
  • Yeah, that wouldn't really be nice unless what i'm using right now is really expensive. I calculate i will have ~2k page impressions at my maximum usage – Ariel Mar 14 '16 at 18:53
  • Why was it downvoted ? Would you kindly explain ? – Ariel Mar 14 '16 at 18:55
  • 1
    So you want to instantiate class by name having constructor with 4 parameters `string where, string select, string order, int take`, correct? Or just parameterless constructor? – Ivan Stoev Mar 14 '16 at 19:28
  • Yes, that's what i wanted but i also want to know if it is my best option given the amount of page calls i will have. – Ariel Mar 14 '16 at 19:31
  • 1
    Well, you'll need to measure. But the option exists. But I'm unsure what you type of constructor from my previous comment do want: (A) or (B)? – Ivan Stoev Mar 14 '16 at 19:37
  • @JeffMercado, what would be the signature of a function in your example ? – Ariel Mar 14 '16 at 19:38
  • @IvanStoev, sorry. What i want is a constructor with the four parameters. – Ariel Mar 14 '16 at 19:39

2 Answers2

1

Just because Ivan already wrote the basic code, I'll post the equivalent based on Expression trees. It is probably slower both at generation and even at execution.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

public static class ViewModelFactory
{
    static readonly Type[] arguments = { typeof(string), typeof(string), typeof(string), typeof(int) };

    static readonly ConcurrentDictionary<string, Func<string, string, string, int, object>> factoryCache = new ConcurrentDictionary<string, Func<string, string, string, int, object>>();

    public static object CreateInstance(string typeName, string where, string select, string order, int take)
    {
        Func<string, string, string, int, object> factory;

        lock (factoryCache)
        {
            if (!factoryCache.TryGetValue(typeName, out factory))
            {
                var type = Type.GetType(typeName);
                var ci = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, arguments, null);

                ParameterExpression par1 = Expression.Parameter(typeof(string), "par1");
                ParameterExpression par2 = Expression.Parameter(typeof(string), "par2");
                ParameterExpression par3 = Expression.Parameter(typeof(string), "par3");
                ParameterExpression par4 = Expression.Parameter(typeof(int), "par4");

                var exprNew = Expression.New(ci, par1, par2, par3, par4);

                var lambda = Expression.Lambda<Func<string, string, string, int, object>>(exprNew, par1, par2, par3, par4);
                factory = lambda.Compile();
                factoryCache.Add(typeName, factory);
            }
        }
        return factory(where, select, order, take);
    }
}
xanatos
  • 109,618
  • 12
  • 197
  • 280
  • Thank you for giving other option for the problem – Ariel Mar 15 '16 at 13:30
  • Yeah, both of you. I would like to know who downvotes and doesn't leave a constructive explanation of why. Downvoting is pretty easy if you don't have to explain yourself – Ariel Mar 15 '16 at 13:51
0

You can use the following helper class, but you need to measure the performance yourself:

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

public static class ViewModelFactory
{
    static readonly Type[] arguments = { typeof(string), typeof(string), typeof(string), typeof(int) };

    static readonly Dictionary<string, Func<string, string, string, int, object>>
    factoryCache = new Dictionary<string, Func<string, string, string, int, object>>();

    public static object CreateInstance(string typeName, string where, string select, string order, int take)
    {
        Func<string, string, string, int, object> factory;
        lock (factoryCache)
        {
            if (!factoryCache.TryGetValue(typeName, out factory))
            {
                var type = Type.GetType(typeName);
                var ci = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, arguments, null);
                var dm = new DynamicMethod("Create" + typeName, type, arguments, true);
                var il = dm.GetILGenerator();
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldarg_1);
                il.Emit(OpCodes.Ldarg_2);
                il.Emit(OpCodes.Ldarg_3);
                il.Emit(OpCodes.Newobj, ci);
                il.Emit(OpCodes.Ret);
                factory = (Func<string, string, string, int, object>)dm.CreateDelegate(
                    typeof(Func<string, string, string, int, object>));
                factoryCache.Add(typeName, factory);
            }
        }
        return factory(where, select, order, take);
    }
}

UPDATE: As @xanatos correctly mentioned, the above can be improved by replacing the Dictionary and Monitor locks with ConcurrentDictionary:

using System;
using System.Collections.Concurrent;
using System.Reflection;
using System.Reflection.Emit;

public static class ViewModelFactory
{
    static readonly Type[] arguments = { typeof(string), typeof(string), typeof(string), typeof(int) };

    static readonly ConcurrentDictionary<string, Func<string, string, string, int, object>>
    factoryCache = new ConcurrentDictionary<string, Func<string, string, string, int, object>>();

    static readonly Func<string, Func<string, string, string, int, object>> CreateFactory = typeName =>
    {
        var type = Type.GetType(typeName);
        var ci = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, arguments, null);
        var dm = new DynamicMethod("Create" + typeName, type, arguments, true);
        var il = dm.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Ldarg_2);
        il.Emit(OpCodes.Ldarg_3);
        il.Emit(OpCodes.Newobj, ci);
        il.Emit(OpCodes.Ret);
        return (Func<string, string, string, int, object>)dm.CreateDelegate(
            typeof(Func<string, string, string, int, object>));
    };

    public static object CreateInstance(string typeName, string where, string select, string order, int take)
    {
        var factory = factoryCache.GetOrAdd(typeName, CreateFactory);
        return factory(where, select, order, take);
    }
}
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • I made a test with 20k iterations, is there anywhere i can put those results ? – Ariel Mar 14 '16 at 20:24
  • 2
    I would have used `Expression` trees... But just because it is easier to write the code... But `Reflection.Emit` is surely faster to generate. – xanatos Mar 15 '16 at 13:08
  • @IvanStoev, i saw that your code has a caching, how would it work ? It will load after a pageload and when will it be reloaded or cleaned ? – Ariel Mar 15 '16 at 13:12
  • 1
    @Terkhos It's a standard C# **static** field lifetime. Should not be reloaded or cleaned until AppDomain is unloaded. – Ivan Stoev Mar 15 '16 at 13:17
  • @IvanStoev Considering that it is built for a Web server, I would use `ConcurrentDictionary<>` (multiple calls to the pages are possible at the same time) – xanatos Mar 15 '16 at 13:20
  • 1
    @Terkhos Also please note that the performance tests you did are not equivalent. `Activator.CreateInstance` is specifically optimized for parameterless constructors. That's why it was important if you needed a constructor with parameters. Another thing. The code above can be affected by the long typename string (calculating the hashcode). It can be improved by stripping out the namespace and pass just the class name (the namespace will be hardcoded inside the implementation `GetType` call). – Ivan Stoev Mar 15 '16 at 13:33
  • @IvanStoev, you mean like this: `Type.GetType('Site.ViewModels.' + typeName)` and on the call: `ViewModelFactory.CreateInstance( block.Type, "", "", "", 0 )` ? – Ariel Mar 15 '16 at 13:36
  • 1
    @xanatos Absolutely, good point! But will left that improvement for OP. – Ivan Stoev Mar 15 '16 at 13:36
  • @IvanStoev, how would i implement what xanatos suggested ? I'm sorry but i'm kind of new on C# – Ariel Mar 15 '16 at 13:38
  • 1
    @Terkhos It's about changing the `Dictionary` with `ConcurentDictionary` and removing the `lock` statement. – Ivan Stoev Mar 15 '16 at 13:40