C#:动态创建部分函数
问题描述:
我的代码中有一个会话中包含第三方脚本引擎。引擎接受任何委托并使其可用于具有相同签名的脚本。C#:动态创建部分函数
现在我想拥有为引擎提供这些委托的插件,但是我还希望会话中的额外数据没有显示在脚本中。
使用委托的脚本应该不知道会话,但实现它的插件确实不知道。插件编写者应该可以*地为插件代理使用任意数量或类型的参数,所以我需要在运行时动态地执行此操作。
例如:
//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);
所以当脚本引擎调用LogMessage("Test")
应该在插件调用LogMessage(mysession, "Test")
。
我在curry上找到了有关向代表添加默认设置的信息,Reflection可以创建代表,但他们如何才能完成此操作?
编辑:全长例如
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();
}
}
编辑2:进度更新
我现在可以创建具有较少变量委托类型则原始
/// <summary>
/// Based on code from user svick [https://*.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 *es
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;
}
}
用法:
DelegateTypeFactory factory = new ConsoleApplication1.DelegateTypeFactory();
Type newDelegateType = factory .CreateDelegateType(originalDelegate.Method);
但是人们可以如何实例化新的委托,并使其与调用默认的会话值的原始委托躲开我
答
好像你有插件传入委托的引擎。
引擎然后动态调用插件。
您可以使用闭包来完成此操作,但插件必须创建闭包,因为它正在创建委托。因此,第三方开发者也可以使用这种技术,这取决于他们。如果他们不需要委托中可用的任何额外对象,则他们不需要。
对于委托人捕获其他变量的引擎,它将是透明的。
我在您的main
中看到您有意见表明您正在考虑改变那里的插件功能。 我不知道你会如何做到这一点,因为你不知道什么参数插件作者打算在/可见。
所以我写了这个让插件决定它想隐藏什么。
我以你写的方式离开了你的Handle *方法,但是如果需要的话,他们确实可以访问Session对象。
public class Session
{
//Some metadata here
}
public class Plugin
{
private delegate bool SendMessage(string ip, int port, string message);
private delegate void LogMessage(string message);
public Delegate[] GetFunctions()
{
var sessionInfo = new Session();
return new Delegate[] { new SendMessage(HandleSendMessage(sessionInfo)), new LogMessage(HandleLogMessage(sessionInfo)) };
}
private SendMessage HandleSendMessage(Session info)
{
return delegate (string ip, int port, string message)
{
Console.WriteLine($"SEND {ip}:{port} >> \"{message}\"");
return true;
};
}
private LogMessage HandleLogMessage(Session info)
{
return delegate (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();
}
}
我加了一个全长的例子,希望澄清一下这个问题。我没有发现关闭,看起来有趣,但它可以让我一路?我不能输入类似'Func'的东西,因为我不知道委托插件具有什么(第三方代码),所以需要基于反射来生成签名。 –
这是有效的,但对于我们的目标插件 - 作者观众来说有点太高级了:(我们最终为脚本引擎构建了一个通用的'invoke(string function,string [] params)'委托,它被转换为普通的插件委托然后我们将脚本预加载到引擎中,并在调用周围生成包装函数以匹配插件委托(无需会话)。 –