-1

I have an Expression<Func<Tin, object>> object and I need to cast it to the Expression<Func<Tin, Tout>> object.

In fact I have this:

x => new <>f__AnonymousType6`1(MyProp = x.MyProp)

and I need to have it as:

x => new MyType(){MyProp = x.MyProp}

Note that I have an AnonymousType here!

To achieve this I wrote a function as below:

public static Expression<Func<Tin, Tout>> Transform<Tin, Tout>(this Expression<Func<Tin, object>> source)
{
    var param = Expression.Parameter(typeof(Tout));

    var body = new Visitor<Tout>(param).Visit(source.Body);

    Expression<Func<Tin, Tout>> lambda = Expression.Lambda<Func<Tin, Tout>>(body, param);
    return lambda;
}

And a Visitor class:

class Visitor<T> : ExpressionVisitor
{
    ParameterExpression _parameter;

    public Visitor(ParameterExpression parameter)=>_parameter = parameter;

    protected override Expression VisitParameter(ParameterExpression node)=>_parameter;

    protected override Expression VisitMember(MemberExpression node)
    {
        if (node.Member.MemberType != System.Reflection.MemberTypes.Property)
            throw new NotImplementedException();

        var memberName = node.Member.Name;

        var otherMember = typeof(T).GetProperty(memberName);

        var inner = Visit(node.Expression);
        return Expression.Property(inner, otherMember);
    }
}

But when I run it, I get this error:

System.ArgumentException: 'Expression of type '<>f__AnonymousType6`1[System.String]' cannot be used for return type 'MyType''

Update

In Tin and Tout classes I have some parametric constructors and a private parameter-less constructor. I do not want to use parametric constructors as they have arguments which might be different from the expression required one. I need to build the Expression using private parameter-less constructor.

So if I use the below code:

var ctor = typeof(TOut).GetPrivateConstructor();
if (ctor != null) // can replace
     return Expression.New(ctor, node.Arguments);

Or even this:

var ctor = typeof(TOut).GetPrivateConstructor();
    if (ctor != null) // can replace
    {
         var expr = Expression.New(ctor);
         expr.Update(node.Arguments);//<=====Exception in this line
         return expr;
    }

I will get the following error:

Incorrect number of arguments for constructor

And if I use the following:

var ctor = typeof(TOut).GetPrivateConstructor();
if (ctor != null) // can replace
     return Expression.New(ctor);

I will miss the arguments!

Update 2

If I use it as:

var ctor = typeof(TOut).GetPrivateConstructor();
if (ctor != null) // can replace
{
   var expr = Expression.New(ctor);

   FieldInfo argementsField = expr.GetType().GetRuntimeFields().FirstOrDefault(a => a.Name == "_arguments");
   argementsField.SetValue(expr, node.Arguments);

   expr.Update(node.Arguments);
   return expr;
}

The expression will be built, but will not be executed as it produces the following:

x => new MyType(MyProp = x.MyProp)

Which is incorrect again will produce the following error as expected:

Incorrect number of arguments for constructor

Vahid Farahmandian
  • 6,081
  • 7
  • 42
  • 62

1 Answers1

2

Assuming that MyType looking like this

public class MyType
{
    public MyType(string myProp)
    {
        MyProp = myProp;
    }

    public string MyProp { get; set; }
}

you can create a generic visitor:

public class MyVisitor<TIn, TOut> : ExpressionVisitor
{
    private readonly Type funcToReplace;

    public MyVisitor()
    {
        funcToReplace = typeof(Func<,>).MakeGenericType(typeof(TIn), typeof(object));
    }

    // this hack taken from https://stackoverflow.com/a/2483054/4685428
    // and https://stackoverflow.com/a/1650895/4685428
    private static bool IsAnonymousType(Type type)
    {
        var markedWithAttribute = type.GetCustomAttributes(
          typeof(CompilerGeneratedAttribute)).Any();
        var typeName = type.Name;

        return markedWithAttribute
          && typeName.StartsWith("<>")
          && typeName.Contains("AnonymousType");
    }

    protected override Expression VisitNew(NewExpression node)
    {
        if (IsAnonymousType(node.Type))
        {
            var arguments = node.Arguments.Select(a => a.Type).ToArray();
            var ctor = typeof(TOut).GetConstructor(arguments);
            if (ctor != null) // can replace
                return Expression.New(ctor, node.Arguments);
        }
        return base.VisitNew(node);
    }

    protected override Expression VisitLambda<T>(Expression<T> node)
    {
        if (typeof(T) != funcToReplace)
            return base.VisitLambda(node);

        var p = node.Parameters.First();
        var body = Visit(node.Body);

        return Expression.Lambda<Func<TIn, TOut>>(body, p);
    }
}

Usage:

Expression<Func<TypeOfX, object>> input = x => new {MyProp = x.MyProp};

var visitor = new MyVisitor<TypeOfX, MyType>();
var result = (Expression<Func<TypeOfX, MyType>>) visitor.Visit(input);

Some explanation:

In VisitNew we check that constructor is belong to anonymous type. If so we trying to search in TOut type for constructor with same arguments. On success we replace anonymous type constructor with constructor from TOut

Aleks Andreev
  • 7,016
  • 8
  • 29
  • 37
  • I think from the question the Object Represents the TOut, so chances are you just want to use a Convert – johnny 5 Apr 15 '19 at 21:33
  • @johnny5 _constructor that takes in both arguements_ I did not get what your mean, sorry. My code replace a call to anonymous constructor with a call to defined constructor (if it exists). I've tested it and it's works on OP input. Could you please post another answer with `Expression.Convert` example? I would be glad to learn something new about Expressions – Aleks Andreev Apr 15 '19 at 22:44
  • @alekz no your right my fault, I though the OP wanted to cast an expression like described in the title – johnny 5 Apr 15 '19 at 23:28
  • @AleksAndreev I have updated mu question and added some more information. Thank for your time and solution – Vahid Farahmandian Apr 16 '19 at 09:28
  • @AleksAndreev Thanks to your help I have solved this problem and faced other problem. here is my solution and the new problem:https://stackoverflow.com/q/55730825/1666800 – Vahid Farahmandian Apr 17 '19 at 15:16