Как выразить вызов метода void как результат DynamicMetaObject.BindInvokeMember? - PullRequest
56 голосов
/ 03 декабря 2009

Я пытаюсь привести краткий пример IDynamicMetaObjectProvider для второго издания C # in Depth, и у меня возникают проблемы.

Я хочу быть в состоянии выразить пустой вызов, и мне не удается. Я уверен, что это возможно, потому что, если я динамически вызываю метод void, используя связыватель отражения, все в порядке. Вот короткий, но полный пример:

using System;
using System.Dynamic;
using System.Linq.Expressions;

class DynamicDemo : IDynamicMetaObjectProvider
{
    public DynamicMetaObject GetMetaObject(Expression expression)
    {
        return new MetaDemo(expression, this);
    }

    public void TestMethod(string name)
    {
        Console.WriteLine(name);
    }

}

class MetaDemo : DynamicMetaObject
{
    internal MetaDemo(Expression expression, DynamicDemo demo)
        : base(expression, BindingRestrictions.Empty, demo)
    {
    }

    public override DynamicMetaObject BindInvokeMember
        (InvokeMemberBinder binder, DynamicMetaObject[] args)
    {
        Expression self = this.Expression;

        Expression target = Expression.Call
            (Expression.Convert(self, typeof(DynamicDemo)),
             typeof(DynamicDemo).GetMethod("TestMethod"),
             Expression.Constant(binder.Name));

        var restrictions = BindingRestrictions.GetTypeRestriction
            (self, typeof(DynamicDemo));

        return new DynamicMetaObject(target, restrictions);
    }
}

class Test
{
    public void Foo()
    {
    }

    static void Main()
    {
        dynamic x = new Test();
        x.Foo(); // Works fine!

        x = new DynamicDemo();
        x.Foo(); // Throws
    }
}

Это исключение:

Необработанное исключение: System.InvalidCastException: тип результата 'System.Void' из динамическое связывание, создаваемое объектом с типом «DynamicDemo» для связующего 'Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder' несовместимо с типом результата «System.Object», ожидаемым сайт вызова.

Если я изменю метод, чтобы вернуть объект и вернуть ноль, он работает нормально ... но я не хочу, чтобы результат был нулевым, я хочу, чтобы он был пустым. Это прекрасно работает для связывателя отражений (см. Первый вызов в Main), но не работает для моего динамического объекта. Я хочу, чтобы он работал как средство связывания отражений - метод можно вызывать, если вы не пытаетесь использовать результат.

Я пропустил какой-то конкретный тип выражения, которое могу использовать в качестве цели?

Ответы [ 4 ]

25 голосов
/ 03 декабря 2009

Это похоже на:

Тип возврата DLR

Вам необходимо соответствовать типу возврата, указанному в свойстве ReturnType. Для всех стандартных двоичных файлов это фиксируется для объекта практически для всего или для void (для операций удаления). Если вы знаете, что делаете пустой звонок, я бы посоветовал заключить его в:

Expression.Block(
    call,
    Expression.Default(typeof(object))
);

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

Звучит так, как будто вы хотите предотвратить:

dynamic x = obj.SomeMember();

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

11 голосов
/ 03 декабря 2009

Мне не нравится это, но, похоже, работает; реальная проблема, по-видимому, заключается в том, что binder.ReturnType появляется странным образом (и не удаляется автоматически ("pop")), но:

if (target.Type != binder.ReturnType) {
    if (target.Type == typeof(void)) {
        target = Expression.Block(target, Expression.Default(binder.ReturnType));
    } else if (binder.ReturnType == typeof(void)) {
        target = Expression.Block(target, Expression.Empty());
    } else {
        target = Expression.Convert(target, binder.ReturnType);
    }
}
return new DynamicMetaObject(target, restrictions);
5 голосов
/ 03 декабря 2009

Связыватель C # (в Microsoft.CSharp.dll) знает, используется ли результат; как говорит x0n (+1), он отслеживает это во флаге. К сожалению, флаг скрыт внутри экземпляра CSharpInvokeMemberBinder, который является закрытым типом.

Похоже, что механизм связывания C # использует ICSharpInvokeOrInvokeMemberBinder.ResultDiscarded (свойство внутреннего интерфейса) для его считывания; CSharpInvokeMemberBinder реализует интерфейс (и свойство). Похоже, что работа выполнена в Microsoft.CSharp.RuntimeBinder.BinderHelper.ConvertResult(). Этот метод имеет код, который выдает, если вышеупомянутое свойство ResultDiscarded не возвращает true, если тип выражения void.

Так что мне не кажется, что есть простой способ выявить тот факт, что результат выражения отбрасывается из подшивки C #, по крайней мере в бета-версии 2.

5 голосов
/ 03 декабря 2009

Возможно, сайт вызова ожидает возврата значения null, но отбрасывает результат. Это перечисление выглядит интересно, особенно флаг ResultDiscarded ...

[Flags, EditorBrowsable(EditorBrowsableState.Never)]
public enum CSharpBinderFlags
{
    BinaryOperationLogical = 8,
    CheckedContext = 1,
    ConvertArrayIndex = 0x20,
    ConvertExplicit = 0x10,
    InvokeSimpleName = 2,
    InvokeSpecialName = 4,
    None = 0,
    ResultDiscarded = 0x100,
    ResultIndexed = 0x40,
    ValueFromCompoundAssignment = 0x80
}

Пища для размышлений ...

UPDATE:

Дополнительные подсказки можно почерпнуть из Microsoft / CSharp / RuntimeBinder / DynamicMetaObjectProviderDebugView, который используется (я полагаю) в качестве визуализатора для отладчиков. Метод TryEvalMethodVarArgs проверяет делегат и создает подшивку с флагом сброшенного результата (???)

 Type delegateType = Expression.GetDelegateType(list.ToArray());
    if (string.IsNullOrEmpty(name))
    {
        binder = new CSharpInvokeBinder(CSharpCallFlags.ResultDiscarded, AccessibilityContext, list2.ToArray());
    }
    else
    {
        binder = new CSharpInvokeMemberBinder(CSharpCallFlags.ResultDiscarded, name, AccessibilityContext, types, list2.ToArray());
    }
    CallSite site = CallSite.Create(delegateType, binder);

... Я нахожусь в конце моего Reflector-foo здесь, но создание этого кода кажется немного странным, так как сам метод TryEvalMethodVarArgs ожидает объект в качестве возвращаемого типа, а последняя строка возвращает результат динамический вызов. Я, вероятно, лаю не на то дерево [выражения].

-Oisin

...