Is there any way Csharp scripting API could infer types of arguments without having to specify it?

134 Views Asked by At

I have the following piece of code:

string actionString = "(p1,p2)=>p1.HP-=20";

var options = ScriptOptions.Default.AddReferences(typeof(SimplePlayer).Assembly).AddImports("Player");

var script = CSharpScript.Create<Action<SimplePlayer, SimplePlayer>>(actionString, options);

SimpleDeck d = new SimpleDeck(Game.GameController.Cards, 100, 100);

SimplePlayer p1 = new SimplePlayer(4000, 100, 100, d);
SimplePlayer p2 = new SimplePlayer(4000, 100, 100, d);

var del = script.CreateDelegate();

del.DynamicInvoke(p1, p2);
System.Console.WriteLine(p1.HP);

I want to achieve the same thing, but instead of having to specify the type of the expected delegate I want something like this:

string actionString = "(p1,p2)=>p1.HP-=20";

var options = ScriptOptions.Default.AddReferences(typeof(SimplePlayer).Assembly).AddImports("Player");

var script = CSharpScript.Create(actionString, options);

SimpleDeck d = new SimpleDeck(Game.GameController.Cards, 100, 100);

SimplePlayer p1 = new SimplePlayer(4000, 100, 100, d);
SimplePlayer p2 = new SimplePlayer(4000, 100, 100, d);

var del = script.CreateDelegate();

del.DynamicInvoke(p1, p2);

That throws me:

error CS8917: The delegate type could not be inferred.

because i don't specify the type I want to be created. If I change the code to:

string actionString = "(SimplePlayer p1,SimplePlayer p2)=>p1.HP-=20";

Is there a way that I can infer the SimplePlayer type without having to explicitly pass it as an argument? And not having to do the following:

var script = CSharpScript.Create<Action<SimplePlayer, SimplePlayer>>(actionString, options);
1

There are 1 best solutions below

4
On BEST ANSWER

If you need delegates to be resolved at runtime (in other words, without generics), one clusmy but working way would be to procedurally interpolate into the respective Action type around the expression before passing it onto CSharpScript. e.g.

string code = Typed(
    "(p1, p2) => p1.HP - 20",
    nameof(SimplePlayer), // first arg type name
    nameof(SimplePlayer) // second arg type name
);
// ((Action<SimplePlayer, SimplePlayer>)((p1, p2) => p1.HP - 20))

static string Typed(string code, params string[] args)
    => $"((Action<{string.Concat(", ", args)}>)({code}))";

If the return value may or may not exist, you can just as easily swap out the string.

Edit: To use whatever delegate type you wish, and not be constrained to Action's overloads, you can just use Type.Name as the interpolation. Be aware that types with generics will not work in the example below. To do that, refer to this answer.

static string Typed(string code, Type type)
    => $"(({type.Name})({code}))";

Though preferably, if you have the option to use generics, you can just do something like this.

Run<TDelegate>(string actionString, ScriptOptions options)
    where T : Delegate
{
    var del = CSharpScript.Create<TDelegate>(actionString, options);

    // del is now guaranteed to be 'Delegate' and you can treat it as such, and you can use typeof(T) to do whatever reflection APIs you require
}