2017-08-25 106 views
0

我的代码中有一个会话中包含第三方脚本引擎。引擎接受任何委托并使其可用于具有相同签名的脚本。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://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; 
    } 
} 

用法:

DelegateTypeFactory factory = new ConsoleApplication1.DelegateTypeFactory(); 
Type newDelegateType = factory .CreateDelegateType(originalDelegate.Method); 

但是人们可以如何实例化新的委托,并使其与调用默认的会话值的原始委托躲开我

回答

1

好像你有插件传入委托的引擎。

引擎然后动态调用插件。

您可以使用闭包来完成此操作,但插件必须创建闭包,因为它正在创建委托。因此,第三方开发者也可以使用这种技术,这取决于他们。如果他们不需要委托中可用的任何额外对象,则他们不需要。

对于委托人捕获其他变量的引擎,它将是透明的。

我在您的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(); 

    } 
} 
+0

我加了一个全长的例子,希望澄清一下这个问题。我没有发现关闭,看起来有趣,但它可以让我一路?我不能输入类似'Func '的东西,因为我不知道委托插件具有什么(第三方代码),所以需要基于反射来生成签名。 –

+0

这是有效的,但对于我们的目标插件 - 作者观众来说有点太高级了:(我们最终为脚本引擎构建了一个通用的'invoke(string function,string [] params)'委托,它被转换为普通的插件委托然后我们将脚本预加载到引擎中,并在调用周围生成包装函数以匹配插件委托(无需会话)。 –