dynamic - programacion - monodevelop en español
¿Cómo expreso una llamada al método void como resultado de DynamicMetaObject.BindInvokeMember? (4)
Estoy tratando de dar un pequeño ejemplo de IDynamicMetaObjectProvider
para la segunda edición de C # en profundidad, y estoy IDynamicMetaObjectProvider
problemas.
Quiero poder expresar una llamada vacía, 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. Aquí hay 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 arroja una excepción:
Excepción no controlada: System.InvalidCastException: el tipo de resultado ''System.Void'' de la vinculación dinámica producida por el objeto con el tipo ''DynamicDemo'' para la carpeta ''Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder'' no es compatible con el tipo de resultado ''System. Objeto ''esperado por el sitio de llamada.
Si cambio el método para devolver el objeto y devuelvo nulo, funciona bien ... pero no quiero que el resultado sea nulo, quiero que sea nulo. 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?
Esto es similar a:
Debe coincidir con el tipo de devolución 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 sabes que estás haciendo una llamada vacía, te sugiero que la envuelvas en:
Expression.Block(
call,
Expression.Default(typeof(object))
);
El DLR solía ser bastante permisivo sobre lo que permitiría y proporcionaría una cantidad mínima de coerción de forma automática. Nos deshicimos de eso porque no queríamos proporcionar un conjunto de convensions que pueden o no tener sentido para cada idioma.
Parece que quieres prevenir:
dynamic x = obj.SomeMember();
No hay forma de hacerlo, siempre habrá un valor que el usuario puede intentar continuar interactuando dinámicamente.
La carpeta C # (en Microsoft.CSharp.dll) sabe si el resultado se usa o no; 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 ResultDiscarded
mencionada ResultDiscarded
no devuelve verdadero si el tipo de la expresión es nulo.
Por lo tanto, 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 #, al menos en Beta 2.
No me gusta esto, pero parece funcionar; el problema real parece ser la binder.ReturnType
entra extrañamente (y no se descarta ("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);
Quizás el callsite 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
}
Comida para el pensamiento...
ACTUALIZAR:
Se pueden obtener más sugerencias de Microsoft / CSharp / RuntimeBinder / DynamicMetaObjectProviderDebugView que se usa (supongo) como un visualizador para los depuradores. El método TryEvalMethodVarArgs examina al delegado y crea un enlazador con el indicador descartado de 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 método TryEvalMethodVarArgs en sí mismo espera un objeto como un tipo de retorno, y la línea final devuelve el resultado de la invocación dinámica . Probablemente esté ladrando el árbol [de expresión] incorrecto.
-Oisin