Domanda

Sto cercando di dare un breve esempio di IDynamicMetaObjectProvider per la seconda edizione di C # in profondità, e sto correndo in problemi.

Voglio essere in grado di esprimere una chiamata vuoto, e sto fallendo. Sono sicuro che è possibile, perché se io dinamicamente chiamare un metodo vuoto utilizzando il legante riflessione, tutto va bene. Ecco un esempio breve ma 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
    }
}

Questo genera un'eccezione:

  

Eccezione non gestita:   System.InvalidCastException: Il   tipo di risultato 'System.Void' del   dinamica legame prodotta dall'oggetto   di tipo 'DynamicDemo' per il legante   'Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder'   non è compatibile con il tipo di risultato 'System.Object' previsto dal   sito di chiamata.

Se cambio il metodo per restituire oggetto e restituire null, funziona benissimo ... ma non voglio che il risultato sia nullo, io voglio che sia vuoto. Che funziona bene per il legante di riflessione (vedi la prima chiamata in Main), ma non riesce per il mio oggetto dinamico. Voglio farlo funzionare come legante riflessione -. Va bene per chiamare il metodo, fintanto che non si tenta di utilizzare il risultato

Ho perso un particolare tipo di espressione che posso utilizzare come destinazione?

È stato utile?

Soluzione

Questo è simile a:

DLR tipo di ritorno

Si ha bisogno di corrispondere al tipo di ritorno specificato dalla proprietà ReturnType. Per tutti i binari standard, questo è fissato all'oggetto per quasi tutto o nullo (per le operazioni di cancellazione). Se sai che stai facendo una chiamata vuoto suggerirei avvolgendolo in:

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

Il DLR usato per essere piuttosto lassista su ciò che avrebbe permesso e sarebbe fornire una certa quantità minima di coercizione automaticamente. Ci siamo sbarazzati di che perché non volevamo per fornire un insieme di convensions che può o non può avere fatto il senso per ogni lingua.

Sembra che si vuole evitare:

dynamic x = obj.SomeMember();

Non c'è un modo per farlo, ci sarà sempre un valore restituito che l'utente può tentare di continuare a interagire con dinamicamente.

Altri suggerimenti

Non mi piace questo, ma sembra funzionare; il vero problema sembra essere la binder.ReturnType in arrivo stranamente (e non essere caduto ( "pop") in modo automatico), ma:

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

Forse il callsite si aspetta nulla da restituire, ma scarta il risultato - Questo enum sembra interessante, in particolare la "ResultDiscarded" bandiera ...

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

Cibo per la mente ...

UPDATE:

Ulteriori suggerimenti possono essere raccolte da Microsoft / CSharp / RuntimeBinder / DynamicMetaObjectProviderDebugView che viene utilizzato (presumo) come un visualizzatore per il debugger. I TryEvalMethodVarArgs metodo esamina il delegato e crea un legante con la bandiera scartato risultato (???)

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

... Sono alla fine della mia Reflector-foo qui, ma l'elaborazione di questo codice sembra un po 'strano in quanto il metodo TryEvalMethodVarArgs si aspetta un oggetto come un tipo di ritorno, e la riga finale restituisce il risultato l'invoke dinamica. Sono probabilmente abbaiare contro l'sbagliata [espressione] albero.

-Oisin

Il C # legante (in Microsoft.CSharp.dll) sa se il risultato viene utilizzato; come x0n (+1) dice, tiene traccia di esso in una bandiera. Purtroppo, il flag è sepolto in un'istanza CSharpInvokeMemberBinder, che è un tipo privato.

Sembra che il meccanismo di associazione C # utilizza ICSharpInvokeOrInvokeMemberBinder.ResultDiscarded (una proprietà su un'interfaccia interna) per leggere fuori; CSharpInvokeMemberBinder implementa l'interfaccia (e la proprietà). Il lavoro sembra essere fatto in Microsoft.CSharp.RuntimeBinder.BinderHelper.ConvertResult(). Questo metodo ha il codice che genera se la proprietà ResultDiscarded citato non restituisce vero se il tipo dell'espressione è nullo.

Quindi, non sembra a me come se ci fosse un modo semplice per prendere in giro il fatto che il risultato dell'espressione è sceso dal C # legante, in versione Beta 2 di almeno.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top