Using Expression to 'Cast' Func<object, object> to Func<T, TRet>

淺唱寂寞╮ 提交于 2019-12-24 02:14:57

问题


I've written a little function that attempts to do the following dynamically:

        Func<object, object> fa = i => Convert.ChangeType(i, typeof (string));
        Func<int, string> fb = o => (string) fa((int)o);

The func is as follows:

    /// <summary>
    ///     Converts <see cref="Func{object, object}" /> to <see cref="Func{T, TResult}" />.
    /// </summary>
    public static Delegate Convert(Func<object, object> func, Type argType, Type resultType)
    {
        Contract.Requires(func != null);
        Contract.Requires(resultType != null);

        var param = Expression.Parameter(argType);

        var converted = Expression.Convert(
            Expression.Call(func.Method, Expression.Convert(param, typeof (object))),
            resultType);

        var delegateType = typeof (Func<,>).MakeGenericType(argType, resultType);
        return Expression.Lambda(delegateType, converted, param).Compile();
    }

Now this works ok when there is no closure involved - this test passes:

    [Test]
    public void When_Converting_Without_Closure_Then_Suceeds()
    {
        // Arrange
        Func<object, object> f = i => Convert.ChangeType(i, typeof(string));            
        var sut = FuncConversion.Convert(f, typeof(int), typeof(string));

        // Act
        var res = (string) sut.DynamicInvoke(10);

        // Assert
        Assert.AreEqual(typeof(Func<int, string>), sut.GetType());
        Assert.AreEqual("10", res);
    }

but when a closure is involved, this test fails:

    [Test]
    public void When_Converting_With_Closure_Then_Succeeds()
    {
        // Arrange
        var typeTo = typeof (string);
        Func<object, object> f = i => Convert.ChangeType(i, typeTo);            
        var sut = FuncConversion.Convert(f, typeof(int), typeof(string));

        // Act
        var res = (string)sut.DynamicInvoke(10);

        // Assert
        Assert.AreEqual(typeof(Func<int, string>), sut.GetType());
        Assert.AreEqual("10", res);
    }

System.ArgumentException : Static method requires null instance, non-static method requires non-null instance. Parameter name: method at System.Linq.Expressions.Expression.ValidateStaticOrInstanceMethod(Expression instance, MethodInfo method) at System.Linq.Expressions.Expression.Call(MethodInfo method, Expression arg0)

Any idea what is wrong?


回答1:


Ok fixed it. The problem is that with a closure, a func which is normally a static method, has its first param, which would be the target instance on an instance method, used to hold the closure state. So I need to check if that state is there and invoke with it if it is.

Et voila:

    /// <summary>
    ///     Converts <see cref="Func{object, object}" /> to <see cref="Func{T, TResult}" />.
    /// </summary>
    public static Delegate Convert(Func<object, object> func, Type argType, Type resultType)
    {
        // If we need more versions of func then consider using params Type as we can abstract some of the
        // conversion then.

        Contract.Requires(func != null);
        Contract.Requires(resultType != null);

        var param = Expression.Parameter(argType);
        var convertedParam = new Expression[] {Expression.Convert(param, typeof (object))};

        // This is gnarly... If a func contains a closure, then even though its static, its first
        // param is used to carry the closure, so its as if it is not a static method, so we need
        // to check for that param and call the func with it if it has one...
        Expression call;
        call = Expression.Convert(
            func.Target == null
            ? Expression.Call(func.Method, convertedParam) 
            : Expression.Call(Expression.Constant(func.Target), func.Method, convertedParam), resultType);

        var delegateType = typeof (Func<,>).MakeGenericType(argType, resultType);
        return Expression.Lambda(delegateType, call, param).Compile();
    }


来源:https://stackoverflow.com/questions/16590685/using-expression-to-cast-funcobject-object-to-funct-tret

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!