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?
This isn't specific to
dynamic
. You will get the same result if you replacedynamic
withobject
. You would even get that for custom value types that implement an interface and you want to return them from aFunc<IImplementedInterface>
.The reason for this is the fact that when you want to return an
int
asobject
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: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:
In Code:
BuildDynamicExpression
looks like this:Usage would be like this:
The important part here is the cast to
dynamic
before passing the expression toSetup
.