Pregunta

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ío, y yo estoy fallando. Estoy seguro de que es posible, porque si dinámicamente llamar a un método vacío utilizando el ligante reflexión, todo está bien. He aquí una breve pero completa ejemplo:

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 produce una excepción:

  

Excepción no controlada:   System.InvalidCastException: La   tipo de resultado 'System.Void' de la   Enlace dinámico producido por el objeto   con el tipo 'DynamicDemo' para el aglutinante   'Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder'   no es compatible con el tipo de resultado 'System.Object' esperado por el   sitio de llamada.

Si cambio el método para devolver el objeto y devolver null, que funciona bien ... pero no quiero que el resultado sea nulo, yo quiero que sea nula. Eso funciona muy bien para el aglutinante de la reflexión (ver la primera llamada en la principal) pero falla para mi objeto dinámico. Quiero que funcione como el aglutinante de la reflexión -. Que está bien para llamar al método, siempre y cuando no se intenta utilizar el resultado

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

¿Fue útil?

Solución

Esto es similar a:

DLR tipo de retorno

Es necesario para que coincida con el tipo de retorno especificado por la propiedad ReturnType. Para todos los binarios estándar esto se fija al objeto para casi todo o nula (para las operaciones de eliminación). Si sabe que está haciendo una llamada vacío me gustaría sugerir envolviéndolo en:

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

El DLR solía ser bastante flexible en lo que permitiría y sería proporcionar una cierta cantidad mínima de coerción automáticamente. Nos deshicimos de que debido a que no queríamos para proporcionar un conjunto de convensions que puede o no puede tener sentido para cada idioma.

Parece que usted quiere evitar:

dynamic x = obj.SomeMember();

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

Otros consejos

No me gusta esto, pero parece que funciona; el verdadero problema parece ser la binder.ReturnType que entra por extraño (y no se cayó ( "pop") de forma automática), 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);

Tal vez la callsite espera nula a ser devuelto, pero descarta el resultado - Esta enumeración parece interesante, en particular el "ResultDiscarded" bandera ...

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

Para la reflexión ...

ACTUALIZACIÓN:

Más consejos se puede extraer de Microsoft / CSharp / RuntimeBinder / DynamicMetaObjectProviderDebugView que se utiliza (supongo) como un visualizador para depuradores. Los TryEvalMethodVarArgs método examina el delegado y crea un aglutinante con la bandera 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);

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

-Oisin

El C # aglutinante (en Microsoft.CSharp.dll) sabe si se utiliza o no el resultado; como x0n (1) dice, que mantiene un registro de la misma en una bandera. Por desgracia, la bandera está enterrado dentro de una instancia CSharpInvokeMemberBinder, que es un tipo privado.

Parece que el mecanismo de unión C # utiliza ICSharpInvokeOrInvokeMemberBinder.ResultDiscarded (una propiedad en una interfaz interna) para leer en voz; 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 lanza si la propiedad antes mencionada ResultDiscarded no devuelve verdadero si el tipo de la expresión es nula.

Por lo tanto, no parece a mí que hay una manera fácil de desentrañar el hecho de que el resultado de la expresión se deja caer desde el C # aglutinante, en la versión Beta 2 por lo menos.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top