2009-12-02 6 views
55

Estoy tratando de dar un pequeño ejemplo de IDynamicMetaObjectProvider para la segunda edición de C# en profundidad, y estoy teniendo problemas.¿Cómo se expresa una llamada al método void como resultado de DynamicMetaObject.BindInvokeMember?

Quiero expresar una llamada anulada, y estoy fallando. Estoy seguro de que es posible, porque si llamo dinámicamente a un método nulo usando la carpeta de 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 arroja una excepción:

Excepción no controlada: System.InvalidCastException: El tipo resultado 'System.Void' de la Enlace dinámico producido por el objeto con tipo 'DynamicDemo' para la carpeta '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 bien para la carpeta de reflexión (ver la primera llamada en Main) pero falla para mi objeto dinámico. Quiero que funcione como la carpeta de reflexión: está bien llamar al método, siempre y cuando no intentes usar el resultado.

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

+1

Hablando de tirar un guante ... –

+14

Jon Skeet hizo una pregunta ... y todavía no se ha respondido correctamente. Es el fin del mundo tal como lo conocemos. –

+1

@Marc: Mi última pregunta con suerte terminará con un error solucionado para el lanzamiento de .NET 4.0 ... ¿quién sabe lo que sucederá esta vez? :) –

Respuesta

25

Esto es similar a:

DLR return type

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 nulo (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 eso porque no queríamos proporcionar un conjunto de convensions que pueden o no tener sentido para cada idioma.

suena como desea 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.

+0

Gracias. Parece un poco extraño que la carpeta basada en la reflexión de alguna manera puede hacer esto, pero no puedo, pero no es gran cosa. Si sé que no podemos hacerlo, eso es algo de lo que debo advertir a mis lectores :) –

11

No me gusta esto, pero parece funcionar; 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); 
+0

Hmm ... eso aún terminará volviendo nulo. Idealmente, me gustaría el mismo comportamiento que la carpeta de reflexión, que sabe que es nula (y descartará si asigna el resultado a una variable), pero no importa si solo la está invocando. +1 de todos modos porque no estaba al tanto de Expression.Empty :) –

+1

Estoy de acuerdo - aquí hay algo raro - pero con 'binder.ReturnType' of' object' (y es quisquilloso) ... (y empecé) "No me gusta esto" ...) –

5

Quizás el callsite espera nula a ser devuelto, 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:

Más consejos se puede extraer de Microsoft/CSharp/RuntimeBinder/DynamicMetaObjectProviderDebugView que se usa (supongo) como un visualizador para depuradores. Los TryEvalMethodVarArgs método examina el delegado y crea una carpeta con la bandera desechado 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 es un poco extraño ya que el método TryEvalMethodVarArgs en sí mismo espera un objeto como un tipo de devolución, y la línea final devuelve el resultado de la invocación dinámica. Probablemente esté ladrando el árbol [de expresión] incorrecto.

-Oisin

5

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

Parece que el mecanismo de enlace 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 un código que arroja si la propiedad antes mencionada no devuelve verdadero si el tipo de la expresión es nulo.

Así que no me parece que haya una manera fácil de descubrir el hecho de que el resultado de la expresión se elimina del enlazador C#, en Beta 2 como mínimo.

Cuestiones relacionadas