Como posso expressar uma chamada de método vazio como o resultado de DynamicMetaObject.BindInvokeMember?

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

Pergunta

Eu estou tentando dar um pequeno exemplo de IDynamicMetaObjectProvider para a segunda edição do C # em profundidade, e eu estou correndo em problemas.

Eu quero ser capaz de expressar uma chamada vazio, e eu estou falhando. Eu tenho certeza que é possível, porque se eu dinamicamente chamar um método vazio utilizando o ligante reflexão, tudo está bem. Aqui está uma pequena mas completa exemplo:

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
    }
}

Este lança uma exceção:

Exceção não tratada: System.InvalidCastException: O tipo 'System.Void' do A ligação dinâmica produzida pelo objeto com tipo 'DynamicDemo' para o ligante 'Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder' não é compatível com o tipo de resultado 'System.Object' esperado pelo site de chamada.

Se eu alterar o método de objeto de retorno e nulos retorno, ele funciona bem ... mas eu não quero o resultado para ser nulo, eu quero que ele seja vazio. Esta coima trabalha para o ligante de reflexão (ver a primeira chamada em Main), mas não para o meu objeto dinâmico. Eu quero que ele funcione como o ligante reflexão - É bom para chamar o método, contanto que você não tente usar o resultado

.

Eu perdi um tipo particular de expressão que eu posso usar como alvo?

Foi útil?

Solução

Este é semelhante a:

DLR tipo de retorno

Você precisa coincidir com o tipo de retorno especificado pela propriedade ReturnType. Para todos os binários padrão esta é fixado em objeto para quase tudo ou void (para as operações de eliminação). Se você sabe que está fazendo uma chamada vazio Eu sugiro envolvê-lo em:

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

O DLR costumava ser bastante frouxa sobre o que permitiria e que iria fornecer alguma quantidade mínima de coerção automaticamente. Nós nos livramos de que porque nós não queria fornecer um conjunto de convensions que pode ou não pode ter feito sentido para cada idioma.

Parece que você quer evitar:

dynamic x = obj.SomeMember();

Não há nenhuma maneira de fazer isso, sempre haverá um valor devolvido que o usuário pode tentar continuar a interagir com dinamicamente.

Outras dicas

Eu não gosto disso, mas parece-obra; o verdadeiro problema parece ser a binder.ReturnType chegando curiosamente (e não ser descartado ( "pop") automaticamente), mas:

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

Talvez a callsite espera nulo para ser devolvido, mas descarta o resultado - Esta enumeração parece interessante, particularmente a "ResultDiscarded" flag ...

[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
}

Alimento para o pensamento ...

UPDATE:

Mais dicas podem ser recolhidos a partir de Microsoft / CSharp / RuntimeBinder / DynamicMetaObjectProviderDebugView que é usado (presumo) como um visualizador para depuradores. Os TryEvalMethodVarArgs método examina o delegado e cria um ligante com a bandeira descartado resultado (???)

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

... Eu estou no fim da minha refletor-foo aqui, mas o enquadramento deste código parece um pouco estranho, já que o método TryEvalMethodVarArgs si espera um objeto como um tipo de retorno, e a linha final retorna o resultado de invocação dinâmica. Provavelmente estou latindo para a árvore errada [expressão].

-Oisin

A C # ligante (em Microsoft.CSharp.dll) sabe se ou não o resultado é usado; como x0n (+1) diz, ele mantém o controle do mesmo em uma bandeira. Infelizmente, a bandeira é enterrado dentro de uma instância CSharpInvokeMemberBinder, que é um tipo particular.

Parece que o C de ligação mecanismo usos ICSharpInvokeOrInvokeMemberBinder.ResultDiscarded (uma propriedade em uma interface interna) para lê-lo para fora #; implementos CSharpInvokeMemberBinder a Interface (e propriedade). O trabalho parece ser feito em Microsoft.CSharp.RuntimeBinder.BinderHelper.ConvertResult(). Esse método tem o código que lança se a propriedade ResultDiscarded acima mencionada não retorna verdadeiro se o tipo da expressão é nula.

Por isso, não me parece que há uma maneira fácil trazer à tona o fato de que o resultado da expressão é retirado do C # aglutinante, em Beta 2, pelo menos.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top