Подтвердить что ты не робот

Как выразить вызов метода void как результат DynamicMetaObject.BindInvokeMember?

Я пытаюсь привести короткий пример IDynamicMetaObjectProvider для второго выпуска С# в 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", ожидаемым вызывать сайт.

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

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

4b9b3361

Ответ 1

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

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

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

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

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

Похоже, вы хотите предотвратить:

dynamic x = obj.SomeMember();

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

Ответ 2

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

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);

Ответ 3

Возможно, callsite ожидает, что 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

Ответ 4

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

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

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