Типы значений, выведенные как объект во время выполнения при использовании динамического

Я почти понимаю, почему возникает эта конкретная проблема (хотя я более чем приветствую объяснение непрофессионала, если вы найдете время!), Я уверен, что это связано с боксом/распаковкой, которые я не буду пытаться объяснить неправильно.

С моим текущим знанием (или его отсутствием) ситуации я не уверен, как лучше всего действовать, чтобы разрешить ее.

Вот довольно упрощенное консольное приложение, показывающее мою проблему:

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>

Как это часто бывает с этими проблемами, лучше изучить, с чего вы начали, прежде чем попасть в этот ужасный беспорядок.

Я использую Мок. У меня есть общий интерфейс, широко используемый в моем приложении другими интерфейсами. Мне нужно проверить, что мои потребители интерфейса сначала вызывают определенный метод в общем интерфейсе, прежде чем вызывать какие-либо методы в различных интерфейсах (мне это кажется плохим дизайном в ретроспективе, я могу решить это позже, но для чисто академические причины, я хотел бы сначала решить этот вопрос).

Для этого я динамически генерирую выражения для каждого метода в своих интерфейсах с It.IsAny<T>() аргументами, которые затем могу передать в mock.Setup(generatedExpression).Callback(doSomethingCommonHere).

Вполне может быть, что есть более простой способ сделать это вместо моего построения выражения...?

Однако, если нет, мой вопрос: как лучше всего изменить построение моего выражения, чтобы разрешить типы значений?


person Smudge202    schedule 22.04.2013    source источник
comment
Действительно, принудительно вызывать какой-либо метод Initialize — плохое дизайнерское решение. Это называется временной связью, и у Марка Симанна есть статья о ней: blog.ploeh.dk /2011/05/24/DesignSmellTemporalCoupling   -  person Daniel Hilgarth    schedule 22.04.2013
comment
Спасибо за ссылку. Я займусь исправлением временной связи, теперь я знаю, как она называется. :)   -  person Smudge202    schedule 22.04.2013


Ответы (1)


Это не относится к dynamic. Вы получите тот же результат, если замените dynamic на object. Вы бы даже получили это для пользовательских типов значений, которые реализуют интерфейс, и вы хотите вернуть их из 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 });
}

Кстати: как видите, я удалил массив args. Это не обязательно.


Чтобы обойти проблему с Moq, я думаю, вам нужно немного изменить подход.
Идея заключается в следующем:

  • Создайте выражение с точным типом возвращаемого значения вызываемого метода.
  • Пусть DLR (Dynamic Language Runtime) определит тип выражения.

В коде:

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);
    // ...
}

Важной частью здесь является приведение к dynamic перед передачей выражения в Setup.

person Daniel Hilgarth    schedule 22.04.2013
comment
Я знал, что это будет просто, спасибо Даниэль. Будет ли это так же просто, как проверить method.ReturnType.IsValueType и использовать мой оригинал, когда он неверен, а ваш, когда он верен? И да, справедливое замечание. Исходный код содержал несколько битов для генерации аргументов, я не урезал их достаточно, моя ошибка. - person Smudge202; 22.04.2013
comment
@ Smudge202: Вы можете использовать эту версию, даже если тип возвращаемого значения метода является ссылочным, поэтому на самом деле нет необходимости использовать две разные версии. Спасибо, что заметили ошибку в моем рефакторинге на месте :) - person Daniel Hilgarth; 22.04.2013
comment
Хотя ваше предложение работает, оно нарушает Moq. :( System.ArgumentException: Expression is not a method invocation <param> => (Object)<param>.SomeMethod(). Я/мы поместили преобразование в неправильное место? Кажется, мне нужно обернуть все это в другой вызов метода, чтобы он понравился Moq, но тогда я подозреваю, что он действительно изо всех сил пытается найти новое определение в качестве сигнатуры метода в имитируемых классах, что объясняет требование, чтобы корневой узел был MethodCall. - person Smudge202; 22.04.2013
comment
@Smudge202: Какая подпись у mock.Setup? Какой точный тип параметра он ожидает? Существуют ли перегрузки этого метода? - person Daniel Hilgarth; 22.04.2013
comment
Setup(Expression<Action<T>> expression) и перегружен для методов с возвратом; Setup(Expression<Func<T, TResult>> expression). Некоторую основную информацию можно найти на их странице быстрого старта на случай, если там будет больше информации. полезная информация. - person Smudge202; 22.04.2013
comment
Интересно, что это ломает мой mock.Setup((dynamic)exp).Callback(() => doSomethingThatRequiresCapture(aVariable)). Учитывая, что все это произошло из-за временной связи, как вы указали, и вам удалось решить первые несколько проблем, я бы сказал, что давайте на этом закончим. Академическое любопытство удовлетворено, большое спасибо за помощь. :) - person Smudge202; 22.04.2013
comment
@Smudge202: Хе-хе, хорошо :) Просто из академического любопытства: каким образом это нарушает обратные вызовы? - person Daniel Hilgarth; 22.04.2013
comment
У меня больше академического любопытства в отношении временных связей и единиц шаблонов работы (и, насколько я знаю, временных требований к фиксации/откату, как все это влияет на мои интерфейсы) . Ошибка под рукой, учитывая ваши изменения, в Callback, это: Cannot use a lambda expression as an argument to a dynamically dispatched operation without first casting it to a delegate or expression tree type. Интересно, что с этой проблемой я столкнулся два дня назад, поэтому кажется, что я прошел полный круг. :) - person Smudge202; 22.04.2013
comment
@ Smudge202: Хорошо, я никогда раньше не слышал об этом сообщении об ошибке. О связи временной связи и паттернов UoW: это должно стать новым вопросом. - person Daniel Hilgarth; 22.04.2013
comment
Я согласен и приветствую любой вклад :) stackoverflow. ком/вопросы/16150588/ - person Smudge202; 22.04.2013
comment
К вашему сведению, я могу избавиться от странного сообщения об ошибке, разбив оператор на `ISetup‹T› x = mock.Setup((dynamic)exp); x.Callback(() => doSomethingThatRequiresCapture(aVariable)). И, прочитав его, становится совершенно понятным, почему это так, если вы считаете, что лямбда разрешается во время компиляции, не может полагаться на привязку типа во время выполнения. В любом случае, на обсуждение UoW! Спасибо еще раз. - person Smudge202; 22.04.2013
comment
@ Smudge202: Да, действительно. Это имеет смысл. Хорошая работа выяснить это. - person Daniel Hilgarth; 22.04.2013