Search code examples
c#expressiongeneric-type-argument

Cannot Create Generator using Expressions


What I have:

public class HubGroup: HubObject
{
    public HubGroup(elnGroup group)
    {//do stuff}
}

public class elnGroup: elnObject
{
    //has properties
}

My requirement is when I give 2 types to a method, it will generate a function that will accept object of elnGroup as a parameter and return a new Instance of HubGroup. I tried a lot of things but I couldn't find a way that I can do it fast (these generator functions will be running several times)

I know you will say use generics but my Types are generated at runtime. All I have is the Base classes for both the Types which I can explicity declare. The code you see below is bits of pieces i have, just to give u a heads up of what I happening.

So I call it like:

public class ApiDataHandler
{
    //this will be called by a method to give json content for objEln
    public void SoFunny<T>(string strContent, T objHub) where T : HubObject
    {
        if (typeof(T) == typeof(HubGroup))
        {
            if (string.IsNullOrEmpty(strContant))
            {
                throw new Exception("Cannot parse null/empty string! ---ApiDataHandler---");
            }

            var objEln = JsonConvert.DeserializeObject<elnGroup>(strContant);
            GetHubObjectGenerator(objEln.GetType(), objHub.GetType());
        }
    }
}

What I want to create:

Func<object,object> generator = (input) => {var bObj = input as BObject;
                                            var aObj = new AObject(bObj);
                                            return aObj;
                                            }

I have done this: but it keeps saying:

InvalidOperationException: variable 'objEln' of type 'ElnHub.HubObjectModel.elnGroup' referenced from scope '', but it is not defined

//also inside ApiData Handler class
    public Func<object, object> GetHubObjectGenerator(Type elnObjectType, Type hubObjectType)
    {
        ParameterExpression inputParam = Expression.Parameter(typeof(object), "input");
        ParameterExpression objCastedAsEln = Expression.Parameter(elnObjectType, "objEln");
        ParameterExpression objHub = Expression.Parameter(hubObjectType, "objHub");
        var cast = Expression.TypeAs(inputParam, elnObjectType);
        var assignCast = Expression.Assign(objCastedAsEln, cast);
        var constructor = hubObjectType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { elnObjectType }, null);
        var callingConstructor = Expression.New(constructor, new[] { objCastedAsEln });
        var assignNewObj = Expression.Assign(objHub, callingConstructor);
        var bodyBlock = Expression.Block(new[] { inputParam },
                assignCast,
                assignNewObj,
                objHub
            );
        var l = Expression.Lambda<Func<object, object>>(
                bodyBlock,
                inputParam
            );

        Func<object, object> HubObjectGenerator = l.Compile();

        return HubObjectGenerator;
    }

I have also tried this where i send generic types, but couldnt find my way. A bit lost here:

    public Func<T,T1> GetHubObjectGenerator<T,T1>() where T : elnObject where T1 : HubObject
    {
        ParameterExpression argParam = Expression.Parameter(typeof(T), "objEln");
        var constructor = typeof(T1).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance,null,new[] { typeof(T) },null);
        Func<T, T1> HubObjectGenerator = Expression.Lambda<Func<T, T1>>(
                Expression.New(constructor, new[] { argParam, }),
                argParam
            ).Compile();

        return HubObjectGenerator;
    }

Solution

  • You want to write something like:

    Func<object,object> generator = (input) =>
    {
        return new AObject((BObject)input);
    }
    

    You need something like:

    public Func<object, object> GetHubObjectGenerator(Type elnObjectType, Type hubObjectType)
    {
        var inputParameter = ExpressionParameter(typeof(object), "input");
        var castInput = Expression.Convert(inputParameter, elnObjectType);
        var constructor = hubObjectType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { elnObjectType }, null);
        var instantiation = Expression.New(constructor, castInput);
        var lambda = Expression.Lambda<Func<object, object>>(instantiation, inputParameter);
        return lambda.Compile();
    }
    

    You can use generics here easily also, you don't even need a cast:

    public Func<THub, TEln> GetHubObjectGenerator<THub, TEln>() where THub : HubObject, TEln : elnObject
    {
        var inputParameter = ExpressionParameter(typeof(TEln), "input");
        var constructor = typeof(THub).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(TEln) }, null);
        var instantiation = Expression.New(constructor, inputParameter);
        var lambda = Expression.Lambda<Func<THub, TEln>>(instantiation, inputParameter);
        return lambda.Compile();
    }
    

    I didn't use variables or Expression.Block here, as there's no need. If you did want to create intermediate variables, use Expression.Variable (Expression.Parameter is for input parameters only). Something like:

    public Func<object, object> GetHubObjectGenerator(Type elnObjectType, Type hubObjectType)
    {
        var inputParameter = ExpressionParameter(typeof(object), "input");
    
        var castInputVar = Expression.Variable(elnObjectType, "eln");
        var castInput = Expression.Convert(inputParameter, elnObjectType);
        var castInputAssign = Expression.Assign(castInputVar, castInput);
    
        var constructor = hubObjectType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { elnObjectType }, null);
        var hubObjectVar = Expression.Variable(hubObjectType, "hub");
        var instantiation = Expression.New(constructor, castInputVar);
        var hubObjectAssign = Expression.Assign(hubObjectVar, instantiation);
    
        var block = Expression.Block(new[] { castInputVar, hubObjectVar },
            castInputAssign,
            hubObjectAssign,
            hubObject);
    
        var lambda = Expression.Lambda<Func<object, object>>(block, inputParameter);
        return lambda.Compile();
    }