Dynamisch eine Aktion generieren

.net c# expression-trees

Frage

Ich versuche herauszufinden, wie man eine Action zur Laufzeit dynamisch erstellt, aber zu kurz kommt.

Nehmen wir an, ich möchte eine Methode aufrufen und eine dynamisch erstellte Aktion übergeben, damit ich verfolgen kann, ob die Aktion (aus welchen Gründen auch immer) aufgerufen wurde.

void DoSomething(Action<string> action);

Das ist die Methode, die ich aufrufen werde, und ich möchte irgendwie dynamisch eine Aktion erstellen, die den Parameter erfüllt.

Ich weiß, ich könnte nur eine new Action<string>((s) => { }); erstellen new Action<string>((s) => { });

Aber für diesen Fall weiß ich zur Kompilierzeit nicht die Signatur der Aktion und alles, was ich will, ist eine super-generische Aktion, die mich wissen lässt, ob sie aufgerufen wurde.

Dies ist Teil eines Kommunikationssystems für ein Projekt, und ich möchte in der Lage sein, die Verwendung von Aktionen zu unterstützen (denke an einen OnCompleted Callback).

Proxy.DoSomething((s) => Console.WriteLine("The server said: " + s);

Ich möchte in der Lage sein, eine Repräsentation zu erzeugen, über die Leitung zu schießen, eine Aktion dynamisch auf dem Server zu erstellen, die Methode auf dem Objekt aufzurufen und meine dynamische Aktion zu übergeben, das Ergebnis zurück an den Client zu senden und dort die eigentliche Aktion aufzurufen .

Eine kleine Erläuterung:

Clientseite:

var proxy = GetProxyObject(); // Comms proxy
proxy.DoSomething((reply) => Console.WriteLine("Server said: " + reply));

Unterhalb:

  1. Entdecken Sie die Signatur der Aktion
  2. Erstellen Sie ein internes Repräsentationsobjekt (einfach genug)
  3. Senden Sie das über den Draht an den Server

Serverseite:

void ReceivedMessage(msg)
{
   var actParam = msg.Parameters[0]; // This is obviously just for demonstration
   var action = BuildActionWrapper(actParam);
   var result = target.InvokeMethod("DoSomething", action.UnderlyingAction);

   // Send result and Action result back to client
   ReplyToClient(...);
}

void DoSomething(Action<string> act)
{
   act("HELLO!");
}

Dann werden auf der Client-Seite die Parameter in die dynamisch generierte Aktion auf dem Server übertragen, die echte Aktion wird nur mit diesen aufgerufen.

Akzeptierte Antwort

OK,

Also habe ich das Problem anscheinend bis zu einem gewissen Grad gelöst, es ist eklig und wahrscheinlich ziemlich weit weg davon, wie es aussehen sollte.

    var mi = this.GetType().GetMethod("DoSomething");
    var actArg = mi.GetParameters()[0];
    var args = actArg.ParameterType.GenericTypeArguments;

    var lt = Expression.Label();

    List<object> values = new List<object>();


    var valVar = Expression.Variable(typeof(List<object>), "vals");
    var para = args.Select(a => Expression.Parameter(a))
        .ToArray();

    var setters = new List<Expression>();

    foreach (var p in para)
    {
        setters.Add(Expression.Call(valVar,
            typeof(List<object>).GetMethod("Add", new[] { typeof(object) }), p));
    }

    var block = Expression.Block(
        variables: new ParameterExpression[]
        {
            valVar,
        },

        expressions: Enumerable.Concat(para,
        new Expression[]
        {
            Expression.Assign(valVar, Expression.Constant(values)),
        }.Concat(setters)
        .Concat(new Expression[]
        {
            Expression.Return(lt),
            Expression.Label(lt),
        })));
    var l = Expression.Lambda(block, para).Compile();
    mi.Invoke(this, new object[] { l });

Im Grunde erstellt das eine Liste, die alle Aktionsparameter enthält, die die Methode festlegt:

public void DoSomething(Action<string> act)
{
    act("Hello");
}

Die Parameter des Ausdrucks werden mit den Typargumenten der Aktion erstellt, und eine Reihe von Setter-Ausdrücken wird erstellt, um diesen bestimmten Parameter zur Werteliste hinzuzufügen.

Wenn der Ausdruck ausgeführt wird, ordnet er die Werteliste als Konstante einer Variablen zu, die er hat. Die Setter-Ausdrücke fügen dann ihre Werte in die Liste ein und der Ausdruck kehrt zurück.

Das Endergebnis ist, dass jede Aktion eine Aktion dynamisch generieren kann und die Ergebnisse einfach in einer Liste gespeichert werden, die Liste könnte dann über die Leitung von Server zu Client gesendet werden und die Werte würden auf dem Client als Argumente für die tatsächlichen verwendet Aktion.


Beliebte Antwort

Hier ist ein Beispiel, wie Sie einen solchen Ausdrucksbaum erstellen können:

var mi = typeof(Console).GetMethod("WriteLine", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(string) }, null);
var parameter = Expression.Parameter(typeof(string));
var body = Expression.Call(null, mi, new[] { parameter });
Expression<Action<string>> expression = Expression.Lambda<Action<string>>(body, new[] { parameter });

expression.Compile()("test");

Als Alternative könnten Sie Reflection.Emit , um zur Laufzeit einen Delegaten zu generieren.

Beispielsweise:

var dynMethod = new DynamicMethod("", null, new[] { typeof(string) });
var il = dynMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
var mi = typeof(Console).GetMethod("WriteLine", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(string) }, null);
il.Emit(OpCodes.Call, mi);
il.Emit(OpCodes.Ret);

var dynDelegate = (Action<string>)dynMethod.CreateDelegate(typeof(Action<string>));
dynDelegate("test");

Dies wird den folgenden Delegaten generieren:

(string s) => Console.WriteLine(s)


Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum