值类型在运行时被推断为对象

本文关键字:对象 类型 运行时 | 更新日期: 2023-09-27 18:10:16

我几乎理解为什么会出现这个特殊的问题(尽管我非常欢迎外行的解释,如果你有时间的话!),如果我确定涉及到装箱/拆箱,我不会试图错误地解释…

以我目前对这种情况的了解(或缺乏了解),我不知道如何最好地解决它。

这是一个相当简化的控制台应用程序显示我的问题:

static void Main(string[] args)
{
    try
    {
        // succeeds
        IEnumerable<Expression<Func<TestCase1Impl, dynamic>>> results1 =
            typeof(ITestCase1).GetMethods().Select(m => buildDynamicExpression(new TestCase1Impl(), m));
        Console.WriteLine("{0} methods processed on ITestCase1", results1.Count().ToString());
        // succeeds
        IEnumerable<Expression<Func<TestCase2Impl, int>>> results2 =
            typeof(ITestCase2).GetMethods().Select(m => buildTypedExpression(new TestCase2Impl(), m));
        Console.WriteLine("{0} methods processed on ITestCase2", results2.Count().ToString());
        // fails
        IEnumerable<Expression<Func<TestCase2Impl, dynamic>>> results3 =
            typeof(ITestCase2).GetMethods().Select(m => buildDynamicExpression(new TestCase2Impl(), m));
        Console.WriteLine("{0} methods processed on ITestCase2", results3.Count().ToString());
    }
    catch (Exception ex)
    {
        Console.WriteLine("Failed: {0}", ex.ToString());
    }
    Console.ReadKey();
}
private static Expression<Func<T, dynamic>> buildDynamicExpression<T>(T arg, MethodInfo method)
{
    ParameterExpression param = Expression.Parameter(typeof(T));
    MethodCallExpression[] args = new MethodCallExpression[0]; // none of the methods shown take arguments
    return Expression.Lambda<Func<T, dynamic>>(Expression.Call(param, method, args), new ParameterExpression[] { param });
}
private static Expression<Func<T, int>> buildTypedExpression<T>(T arg, MethodInfo method)
{
    ParameterExpression param = Expression.Parameter(typeof(T));
    MethodCallExpression[] args = new MethodCallExpression[0]; // none of the methods shown take arguments
    return Expression.Lambda<Func<T, int>>(Expression.Call(param, method, args), new ParameterExpression[] { param });
}
public interface ITestCase1
{
    string Method1();
    List<int> Method2();
    Program Method3();
}
public interface ITestCase2
{
    int Method4();
}
private class TestCase1Impl : ITestCase1
{
    public string Method1()
    {
        throw new NotImplementedException();
    }
    public List<int> Method2()
    {
        throw new NotImplementedException();
    }
    public Program Method3()
    {
        throw new NotImplementedException();
    }
}
private class TestCase2Impl : ITestCase2
{
    public int Method4()
    {
        throw new NotImplementedException();
    }
}

上面将输出

3 methods processed on ITestCase1
1 methods processed on ITestCase2
Failed: System.ArgumentException: Expression of type 'System.Int32' cannot be used for return type 'System.Object' <irrelevant stack trace follows>

就像这些问题经常发生的那样,在陷入这种可怕的混乱之前,最好检查一下你是从哪里开始的。

我使用Moq。我有一个公共接口,在我的应用程序中被其他接口广泛使用。我需要测试我的接口消费者首先调用公共接口上的特定方法,然后再调用各种接口上的任何方法(事后看来,这听起来像是一个糟糕的设计,我可能稍后会解决这个问题,但纯粹出于学术原因,我想先解决这个问题)

为了做到这一点,我动态地为我的接口上带有It.IsAny<T>()参数的每个方法生成表达式,然后我可以将其传递给mock.Setup(generatedExpression).Callback(doSomethingCommonHere)

很可能有一种更简单的方法来做到这一点,而不是我的表达式构建…?

如果不是,那么我的问题是,修改我的表达式构建以允许值类型的最佳方法是什么?

值类型在运行时被推断为对象

这不是dynamic特有的。如果你用object代替dynamic,你会得到同样的结果。对于实现接口的自定义值类型,您甚至可以从Func<IImplementedInterface>返回它们。
这样做的原因是,当您想要将int返回为object时,它必须被装箱-正如您正确猜测的那样。
用于装箱的表达式是Expression.Convert。将它添加到代码中将修复异常:

private static Expression<Func<T, dynamic>> BuildDynamicExpression<T>(
    T arg,
    MethodInfo method)
{
    ParameterExpression param = Expression.Parameter(typeof(T));
    var methodCall = Expression.Call(param, method);
    var conversion = 
    return Expression.Lambda<Func<T, dynamic>>(
        Expression.Convert(methodCall, typeof(object)),
        new ParameterExpression[] { param });
}

BTW:正如你所看到的,我删除了args数组。这是不必要的。


要解决Moq的问题,我认为你需要稍微改变一下方法。
思路如下:

  • 使用被调用方法的确切返回类型创建表达式。
  • 让DLR(动态语言运行时)找出表达式的类型。
在代码:

IEnumerable<Expression> results =
    typeof(ITestCase2).GetMethods()
                      .Select(m => BuildDynamicExpression(
                                       new TestCase2Impl(), m));

BuildDynamicExpression看起来像这样:

private static Expression BuildDynamicExpression<T>(T arg, MethodInfo method)
{
    ParameterExpression param = Expression.Parameter(typeof(T));
    return Expression.Lambda(Expression.Call(param, method),
                             new ParameterExpression[] { param });
}

的用法如下:

foreach(var expression in results)
{
    mock.Setup((dynamic)expression);
    // ...
}
这里的重要部分是在将表达式传递给Setup之前将其强制转换为dynamic