I have a 3rd party scripting engine contained in a session in my code. The engine takes any delegate and makes it available to it's script with the same signature.
Now I want to have plugins that provide these delegates for the engine, but I also want extra data from the session without it showing up in the script.
The script consuming the delegate should have no idea about the session, but the plugin implementing it does. The plugin writer should be free to use any number or types of arguments for the plugin delegates, so I need to do this dynamically at run time.
For example:
//from available plugin delegates
delegate bool SendMessage(Session info, string ip, int port, string message);
delegate void LogMessage(Session info, string message);
//to create script delegates
delegate bool SendMessage(string ip, int port, string message);
delegate void LogMessage(string message);
So when the script engine calls LogMessage("Test")
it should invoke LogMessage(mysession, "Test")
in the plugin.
I found information on curry for adding defaults to delegates and Reflection could create the delegates, but how can they be fit together to accomplish this?
EDIT: full length example
public class Session
{
//Some metadata here
}
public class Plugin
{
private delegate bool SendMessage(Session info, string ip, int port, string message);
private delegate void LogMessage(Session info, string message);
public Delegate[] GetFunctions()
{
return new Delegate[] { new SendMessage(HandleSendMessage), new LogMessage(HandleLogMessage) };
}
private bool HandleSendMessage(Session info, string ip, int port, string message)
{
Console.WriteLine($"SEND {ip}:{port} >> \"{message}\"");
return true;
}
private void HandleLogMessage(Session info, string message)
{
Console.WriteLine($"LOG \"{message}\"");
}
}
//stand-in for 3rd party code
public class Engine
{
private IEnumerable<Delegate> _functions = null;
public void Add(IEnumerable<Delegate> functions)
{
//ignore this code, just simulating 3rd party behavior
_functions = functions;
}
public void Execute()
{
//ignore this code, just simulating 3rd party behavior
foreach (Delegate function in _functions)
{
ParameterInfo[] fparams = function.Method.GetParameters();
int n = fparams.Count();
object[] args = new object[n];
for (int i = 0; i < n; i++)
{
if (string.Compare(fparams[i].Name, "ip") == 0)
{
args[i] = "127.0.0.1";
}
else if (string.Compare(fparams[i].Name, "port") == 0)
{
args[i] = 80;
}
else if (string.Compare(fparams[i].Name, "message") == 0)
{
args[i] = "Some message";
}
else if (string.Compare(fparams[i].Name, "info") == 0)
{
Console.WriteLine("Error this should not be here");
args[i] = null;
}
}
function.DynamicInvoke(args);
}
}
}
class Program
{
static void Main(string[] args)
{
Plugin p = new Plugin(); //assume this instead comes from Assembly.Load(..) and Activator.CreateInstance(..)
Engine e = new Engine(); //stand-in for 3rd party code
List<Delegate> newDelegates = new List<Delegate>();
foreach (Delegate d in p.GetFunctions())
{
//QUESTION: create a new delegate same as (d) minus the first param (Session info)
//QUESTION: link the new delegate to (d) and set (Session info) to some value
newDelegates.Add(d); //add new delegate instead of (d)
}
e.Add(newDelegates);
e.Execute();
}
}
EDIT 2: Progress update
I can now create a delegate type with less variables then the original
/// <summary>
/// Based on code from user svick [https://stackoverflow.com/questions/9505117/creating-delegates-dynamically-with-parameter-names]
/// </summary>
class DelegateTypeFactory
{
private readonly ModuleBuilder _module;
public DelegateTypeFactory()
{
//Build in-memory assembly to contain the new types
AssemblyBuilder assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("DelegateTypeFactory"), AssemblyBuilderAccess.RunAndCollect);
_module = assembly.DefineDynamicModule("DelegateTypeFactory");
}
public Type CreateDelegateType(MethodInfo method)
{
//Create new name for the type to avoid clashes
string nameBase = string.Format("{0}{1}", method.DeclaringType.Name, method.Name);
string name = GetUniqueName(nameBase);
//Create the toolset to make the new type
TypeBuilder builder = _module.DefineType(name, TypeAttributes.Sealed | TypeAttributes.Public, typeof(MulticastDelegate));
ConstructorBuilder constructor = builder.DefineConstructor(MethodAttributes.RTSpecialName | MethodAttributes.HideBySig | MethodAttributes.Public, CallingConventions.Standard, new[] { typeof(object), typeof(IntPtr) });
constructor.SetImplementationFlags(MethodImplAttributes.CodeTypeMask);
//define the methods params and filter unwanted param
ParameterInfo[] parameters = method.GetParameters();
parameters = parameters.Where(p => p.ParameterType != typeof(Session)).ToArray();
//design the method signature
MethodBuilder invokeMethod = builder.DefineMethod("Invoke", MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.Public, method.ReturnType, parameters.Select(p => p.ParameterType).ToArray());
invokeMethod.SetImplementationFlags(MethodImplAttributes.CodeTypeMask);
for (int i = 0; i < parameters.Length; i++)
{
invokeMethod.DefineParameter(i + 1, ParameterAttributes.None, parameters[i].Name);
}
//Return the newly created delegate type
return builder.CreateType();
}
private string GetUniqueName(string nameBase)
{
int number = 2;
string name = nameBase;
while (_module.GetType(name) != null)
{
name = $"{nameBase}{number++}";
}
return name;
}
}
Usage:
DelegateTypeFactory factory = new ConsoleApplication1.DelegateTypeFactory();
Type newDelegateType = factory .CreateDelegateType(originalDelegate.Method);
However how one could instantiating the new delegate and make it call the original delegate with the default session value eludes me