C#:动态创建部分函数

时间:2017-08-25 08:37:41

标签: c# reflection functional-programming delegates

我的代码中的会话中包含第三方脚本引擎。引擎接受任何委托并使其具有相同签名的脚本。

现在我想拥有为引擎提供这些委托的插件,但是我还希望会话中有额外的数据,而不会出现在脚本中。

使用委托的脚本应该不知道会话,但实现它的插件可以。插件编写者可以自由地为插件委托使用任何数量或类型的参数,因此我需要在运行时动态地执行此操作。

例如:

//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 个答案:

答案 0 :(得分:1)

好像你有插件将代理传递给引擎。

然后引擎动态调用插件。

您可以使用闭包执行此操作,但插件必须创建闭包,因为它创建了委托。 所以第三方开发者也可以使用这种技术,这取决于他们。如果他们不需要代表中可用的任何额外对象,他们就不必这样做。

代理捕获其他变量对引擎是透明的。

我在你的main中看到你的评论表明你正在考虑改变那里的插件功能。 我不知道你会怎么做,因为你不知道Plugin作者想要在/可见的参数。

所以我写这篇文章是为了让插件能够决定隐藏它的内容。

我按照你编写它们的方式保留了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();

    }
}