Cómo puedo expresar una llamada al método void como resultado de DynamicMetaObject.BindInvokeMember?


Estoy tratando de dar un breve ejemplo de IDynamicMetaObjectProvider para la segunda edición de C# en Profundidad, y estoy corriendo en problemas.

Quiero ser capaz de expresar una llamada vacía, y estoy fallando. Estoy seguro de que es posible, porque si llamo dinámicamente a un método vacío usando el aglutinante de reflexión, todo está bien. He aquí un ejemplo corto pero completo:

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

Esto lanza una excepción:

Excepción no controlada: Sistema.InvalidCastException: El sistema de tipo de resultado'.Void' de el enlace dinámico producido por el objeto con el tipo 'DynamicDemo' para el aglutinante "Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder" no es compatible con el sistema result type'.Objeto ' esperado por el llame al sitio.

Si cambio el método a return object y return null, funciona bien... pero no quiero que el resultado sea nulo, quiero que sea nula. Eso funciona bien para la carpeta de reflexión (ver la primera llamada en Main) pero falla para mi objeto dinámico. Me si quieres que funcione como el aglutinante de reflexión, está bien llamar al método, siempre y cuando no intentes usar el resultado.

¿Me he perdido un tipo particular de expresión que puedo usar como objetivo?

Author: Jon Skeet, 2009-12-03

4 answers

Esto es similar a:

Tipo de retorno DLR

Necesita coincidir con el tipo de retorno especificado por la propiedad ReturnType. Para todos los binarios estándar, esto se fija en object para casi todo o void (para las operaciones de eliminación). Si sabes que estás haciendo una llamada nula te sugiero que la envuelvas en:

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

El DLR solía ser bastante laxo sobre lo que permitiría y proporcionaría una cantidad mínima de coerción automáticamente. Nos deshicimos de eso. porque no queríamos proporcionar un conjunto de convenciones que pudieran o no tener sentido para cada idioma.

Parece que quieres prevenir:

dynamic x = obj.SomeMember();

No hay manera de hacer eso, siempre habrá un valor devuelto con el que el usuario puede intentar continuar interactuando dinámicamente.

 25
Author: Dino Viehland,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:20

No me gusta esto, pero parece funcionar; el verdadero problema parece ser que binder.ReturnType entra extrañamente (y no se deja caer ("pop") automáticamente), pero:

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);
 11
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-02 22:11:31

Quizás el sitio de llamada espera que se devuelva null pero descarta el resultado - Esta enumeración parece interesante, particularmente la bandera "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
}

Alimento para el pensamiento...

ACTUALIZACIÓN:

Se pueden obtener más sugerencias de Microsoft / CSharp / RuntimeBinder / DynamicMetaObjectProviderDebugView que se usa (supongo) como visualizador para depuradores. El método TryEvalMethodVarArgs examina el delegado y crea un aglutinante con la bandera descartada 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);

... Estoy al final de mi Reflector-foo aquí, pero el encuadre de este código parece un poco extraño ya que el propio método TryEvalMethodVarArgs espera un objeto como un tipo de retorno, y la línea final devuelve el resultado de la invocación dinámica. Probablemente estoy ladrando el árbol [expresión] equivocado.

- Oisin

 5
Author: x0n,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-02 22:32:58

La carpeta de C# (en Microsoft.CSharp.dll) sabe si se usa o no el resultado; como dice x0n (+1), lo rastrea en una bandera. Desafortunadamente, el flag está enterrado dentro de una instancia CSharpInvokeMemberBinder, que es un tipo privado.

Parece que el mecanismo de enlace de C# usa ICSharpInvokeOrInvokeMemberBinder.ResultDiscarded (una propiedad en una interfaz interna) para leerlo; CSharpInvokeMemberBinder implementa la interfaz (y la propiedad). El trabajo parece estar hecho en Microsoft.CSharp.RuntimeBinder.BinderHelper.ConvertResult(). Ese método tiene código que arroja si la propiedad ResultDiscarded antes mencionada no devuelve true si el tipo de la expresión es void.

Así que no me parece que hay una manera fácil de burlarse del hecho de que el resultado de la expresión se elimina de la carpeta de C#, en Beta 2 al menos.

 5
Author: Barry Kelly,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-02 23:21:51