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

StackOverflow https://stackoverflow.com/questions/1835912

Вопрос

Я пытаюсь привести краткий пример 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.csharpinvokemberbinder» не совместим с системой типа результата. Полем

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

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

Это было полезно?

Решение

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

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

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

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

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

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

dynamic x = obj.SomeMember();

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

Другие советы

Мне это не нравится, но, похоже, это работает;настоящая проблема, похоже, в том, 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);

Возможно, сайт вызова ожидает возврата 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
}

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

ОБНОВЛЯТЬ:

Дополнительные подсказки можно почерпнуть из 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 ожидает объект в качестве возвращаемого типа, а последняя строка возвращает результат динамического вызова.Вероятно, я лаю не на то дерево [выражений].

-Ойсин

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top