问题
I almost understand why this particular problem arises (though I more than welcome a layman's explanation if you can find the time!), it I'm sure involves boxing/unboxing which I won't attempt to incorrectly explain..
With my current knowledge (or lack thereof), of the situation, I'm not sure how best to proceed to resolve it.
Here is a fairly simplified console app showing my issue:
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();
}
}
The above will output
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>
As is often the case with these issues, it's better to examine where it was you started before getting into this terrible mess.
I'm using Moq. I have a common interface used widely across my application by other interfaces. I have a need to test that my interface consumers first call a particular method on the common interface, before calling any methods on the various interfaces (this to me sounds like a bad design in hindsight, I may address that later but for purely academic reasons, I'd like to solve this issue first).
In order to this, I dynamically generate expressions for each method on my interfaces with It.IsAny<T>()
arguments, which I can then pass to a mock.Setup(generatedExpression).Callback(doSomethingCommonHere)
.
It may well be that there is a easier way to do this instead of my expression building...?
If not however, my question is, what is the best way to amend my expression building to allow for value types?
回答1:
This isn't specific to dynamic
. You will get the same result if you replace dynamic
with object
. You would even get that for custom value types that implement an interface and you want to return them from a Func<IImplementedInterface>
.
The reason for this is the fact that when you want to return an int
as object
it has to be boxed - as you correctly guessed.
The expression that is used for boxing is Expression.Convert
. Adding that to your code will fix the exception:
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: As you can see, I removed the args
array. It is not necessary.
To work around the problem with Moq I think you need to change the approach a bit.
The idea is the following:
- Create an expression with the exact return type of the called method.
- Let the DLR (Dynamic Language Runtime) figure out the type of the expression.
In Code:
IEnumerable<Expression> results =
typeof(ITestCase2).GetMethods()
.Select(m => BuildDynamicExpression(
new TestCase2Impl(), m));
BuildDynamicExpression
looks like this:
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 });
}
Usage would be like this:
foreach(var expression in results)
{
mock.Setup((dynamic)expression);
// ...
}
The important part here is the cast to dynamic
before passing the expression to Setup
.
来源:https://stackoverflow.com/questions/16148924/value-types-inferred-as-object-at-runtime-when-using-dynamic