使用Type变量调用正确的泛型方法,使用out和ref

时间:2013-02-09 02:30:14

标签: c# generics types lambda invoke

static class Example
{
    public static string Method<T>(ref List<string> p2, out string p3, string p4)
    {
      ...
    }
    public static string Method<T>(ref List<string> p2, out string p3, int p4)
    {
      ...
    }
}

以下显然不起作用,但这就是想法:

public static string MethodCaller(Type theType, ref List<string> p2, out string p3, string p4)
{
    Method<theType>(ref p2, out p3, p4);
}

使用GetMethod?它如何知道使用哪两个重载方法? 我们应该使用Expression.Call吗?我们如何处理ref和out参数?

请帮助:)

2 个答案:

答案 0 :(得分:4)

这可以通过反射来完成,虽然找到正确的重载有点乱:

class Program
{
    static void Main(string[] args)
    {
        List<string> p2 = new List<string>();
        string p3;
        string p4 = "input string";
        string result = MethodCaller(typeof(DateTime), ref p2, out p3, p4);
    }

    public static string MethodCaller(Type theType, ref List<string> p2, out string p3, string p4)
    {
        MethodInfo method = (from m in typeof(Example).GetMethods()
                             let p = m.GetParameters()
                             where m.Name == "Method"
                             && p.Length == 3
                             && p[0].ParameterType.IsByRef
                             && p[0].ParameterType.HasElementType
                             && p[0].ParameterType.GetElementType() == typeof(List<string>)
                             && p[1].ParameterType.IsByRef
                             && p[1].ParameterType.HasElementType
                             && p[1].ParameterType.GetElementType() == typeof(string)
                             && p[2].ParameterType == typeof(string)
                             select m).Single();
        MethodInfo genericMethod = method.MakeGenericMethod(theType);
        object[] parameters = new object[] { null, null, p4 };
        string returnValue = (string)genericMethod.Invoke(null, parameters);
        p2 = (List<string>)parameters[0];
        p3 = (string)parameters[1];
        return returnValue;
    }
}

static class Example
{
    public static string Method<T>(ref List<string> p2, out string p3, string p4)
    {
        p2 = new List<string>();
        p2.Add(typeof(T).FullName);
        p2.Add(p4);
        p3 = "output string";
        return "return value";
    }

    public static string Method<T>(ref List<string> p2, out string p3, int p4)
    {
        p2 = new List<string>();
        p2.Add(typeof(T).FullName);
        p2.Add(p4.ToString());
        p3 = "output string";
        return "return value";
    }
}

答案 1 :(得分:0)

我一直在寻找类似的方法,但很遗憾没有找到它 - 但后来决定自己解决它。但是 - 在原型制作过程中,我发现了&#39; out&#39;和&#39; ref&#39;是互斥的 - 所以你只能支持其中一个。

所以我想支持如下语法:

 object DoCall<A1>( String function, A1 a1 )

这需要生成如下函数:

 object DoCall<A1>( String function, out A1 a1 )
 object DoCall<A1>( String function, ref A1 a1 )

哪个编译器不喜欢。

所以我决定只支持&#39; ref&#39;关键字 - 因为它可以支持进出方向,但是&#39; out&#39;只支持方向。

但另外有人会注意到 - 如果你需要支持所有类型的参数排列 - 简单的编码是不够的 - 你需要编写代码生成器 - 我最后做了什么。

所以测试代码看起来像这样:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace TestReflection
{
    public class CustomClassAsArg
    {
        public string MyInfo { get; set; }
    }

    public class CallMe
    {
        public void Hello1(String msg, int i)
        {
            Console.WriteLine(msg + ": " + i.ToString());
        }

        public void Hello2(ref String msg)
        {
            msg += "out string";
        }

        public void Hello2(ref int a)
        {
            a += 2;
        }

        public string Hello3(string a)
        {
            return a + "--";
        }

        public static bool MyStaticMethod( int arg, ref String outs )
        {
            outs = "->" + arg.ToString();
            return true;
        }

        public bool? ThreeStateTest( int i )
        {
            switch ( i )
            {
                case 0:
                    return null;
                case 1:
                    return false;
                case 2:
                    return true;
            }

            return null;
        }

        public void UpdateCC( CustomClassAsArg c )
        {
            c.MyInfo = "updated";
        }

    }

    class Program
    {
        static void Main(string[] args)
        {
            ClassCaller.UpdateSourceCodeHelperFunctions(2);

            CallMe m = new CallMe();
            ClassCaller c = new ClassCaller(m);
            string r = "in string ";
            int arg = 1;
            String sx = "";
            object ox = c.DoCall("!MyStaticMethod", 23, ref sx);
            Console.WriteLine(sx);

            c.DoCall("Hello1", "hello world", 1);
            c.DoCall("Hello2", ref r);
            Console.WriteLine(r);
            c.DoCall("Hello2", ref arg);
            Console.WriteLine(arg.ToString());

            bool? rt = (bool?)c.DoCall("ThreeStateTest", 0);
            rt = (bool?)c.DoCall("ThreeStateTest", 1);
            rt = (bool?)c.DoCall("ThreeStateTest", 2);

            CustomClassAsArg ccarg = new CustomClassAsArg();
            c.DoCall("UpdateCC",ccarg);

        } //Main
    }
}

ClassCaller.cs本身:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

/// <summary>
/// Helper class for performing invoke function call in dynamically loaded assembly.
/// </summary>
public class ClassCaller
{
    Type type;
    object o;
    bool throwOnError;

    /// <summary>
    /// Can specify class using only assembly name / class type without
    /// actual class instance - if you intend to call only static methods.
    /// </summary>
    /// <param name="assemblyName">Assembly name</param>
    /// <param name="className">Class name, including namespace</param>
    /// <param name="_throwOnError">true if throw on error</param>
    public ClassCaller(String assemblyName, String className, bool _throwOnError)
    {
        throwOnError = _throwOnError;
        Assembly asm = AppDomain.CurrentDomain.GetAssemblies().Where(x => x.GetName().Name == assemblyName).FirstOrDefault();
        if (asm == null)
        {
            if (_throwOnError)
                throw new NullReferenceException("Assembly with name '" + assemblyName + "' was not found");

            return;
        }

        type = asm.GetType(className, _throwOnError);
    }

    public ClassCaller(object _o)
    {
        type = _o.GetType();
        o = _o;
    }

    /// <summary>
    /// Gets method to invoke. 
    /// </summary>
    /// <param name="func">Function name to get. Use '!' as a prefix if it's static function.</param>
    /// <param name="types">Function argument types.</param>
    /// <returns>Method to be invoked.</returns>
    public MethodInfo GetFunc(String func, Type[] types)
    {
        bool bIsStatic = func.FirstOrDefault() == '!';
        if (bIsStatic) func = func.Substring(1);

        BindingFlags f = BindingFlags.Public | BindingFlags.NonPublic;
        if (!bIsStatic)
            f |= BindingFlags.Instance;
        else
            f |= BindingFlags.Static;

        MethodInfo m = type.GetMethod(func, f, null, types, null);

        if (m == null && throwOnError)
            throw new NotSupportedException("Compatible function '" + func + "' not found");

        return m;
    }

    //Autogenerated code starts (Do not edit)

    public object DoCall(string func)
    {
        Type[] types = new Type[] { };
        object[] args = new object[] { };
        MethodInfo f = GetFunc(func, types);
        if (f == null)
            return null;
        object r = f.Invoke(o, args);
        return r;
    }

    public object DoCall<A1>(string func, A1 a1)
    {
        Type[] types = new Type[] { typeof(A1) };
        object[] args = new object[] { a1 };
        MethodInfo f = GetFunc(func, types);
        if (f == null)
            return null;
        object r = f.Invoke(o, args);
        return r;
    }

    public object DoCall<A1>(string func, ref A1 a1)
    {
        Type[] types = new Type[] { typeof(A1).MakeByRefType() };
        object[] args = new object[] { a1 };
        MethodInfo f = GetFunc(func, types);
        if (f == null)
            return null;
        object r = f.Invoke(o, args);
        a1 = (A1)args[0];
        return r;
    }

    public object DoCall<A1, A2>(string func, A1 a1, A2 a2)
    {
        Type[] types = new Type[] { typeof(A1), typeof(A2) };
        object[] args = new object[] { a1, a2 };
        MethodInfo f = GetFunc(func, types);
        if (f == null)
            return null;
        object r = f.Invoke(o, args);
        return r;
    }

    public object DoCall<A1, A2>(string func, ref A1 a1, A2 a2)
    {
        Type[] types = new Type[] { typeof(A1).MakeByRefType(), typeof(A2) };
        object[] args = new object[] { a1, a2 };
        MethodInfo f = GetFunc(func, types);
        if (f == null)
            return null;
        object r = f.Invoke(o, args);
        a1 = (A1)args[0];
        return r;
    }

    public object DoCall<A1, A2>(string func, A1 a1, ref A2 a2)
    {
        Type[] types = new Type[] { typeof(A1), typeof(A2).MakeByRefType() };
        object[] args = new object[] { a1, a2 };
        MethodInfo f = GetFunc(func, types);
        if (f == null)
            return null;
        object r = f.Invoke(o, args);
        a2 = (A2)args[1];
        return r;
    }

    public object DoCall<A1, A2>(string func, ref A1 a1, ref A2 a2)
    {
        Type[] types = new Type[] { typeof(A1).MakeByRefType(), typeof(A2).MakeByRefType() };
        object[] args = new object[] { a1, a2 };
        MethodInfo f = GetFunc(func, types);
        if (f == null)
            return null;
        object r = f.Invoke(o, args);
        a1 = (A1)args[0];
        a2 = (A2)args[1];
        return r;
    }


    //Autogenerated code ends

    public static void UpdateSourceCodeHelperFunctions( int nParametersToSupport)
    {
        String srcFilename = new StackTrace(true).GetFrame(0).GetFileName();
        String src = File.ReadAllText(srcFilename, Encoding.UTF8);

        String autogenRegex = "(Autogenerated\\scode\\sstarts.*?[\r\n]{2})(.*)([\r\n]{2}\\s+//Autogenerated\\scode\\sends)";

        if (!Regex.Match(src, autogenRegex, RegexOptions.Singleline).Success)
        {
            Console.WriteLine("Error: Invalid source code");
            return;
        }

        string[] argType = new String[] { "", "ref" };

        String s = "";
        string lf = "\r\n";
        string headSpace = "    ";
        for (int callArgs = 0; callArgs <= nParametersToSupport; callArgs++)
        {
            int[] argTypes = new int[callArgs];
            int iterations = (int)Math.Pow(2, callArgs);
            for (int i = 0; i < iterations; i++)
            {
                //public object DoCall<A1, A2>(String func, A1 a1, A2 a2)
                s += headSpace;
                s += "public object DoCall" + ((callArgs != 0) ? "<" : "");
                s += String.Join(", ", Enumerable.Range(1, callArgs).Select(n => "A" + n));
                s += (callArgs != 0) ? ">" : "";
                s += "(string func";

                String types = "";
                String paramsList = "";

                bool[] isRefType = new bool[callArgs];

                for (int iArg = 0; iArg < callArgs; iArg++)
                {
                    isRefType[iArg] = (((1 << iArg) & i) != 0);
                    String isRef = isRefType[iArg] ? "ref " : "";
                    String argTypeName = "A" + (iArg + 1);
                    String argName = "a" + (iArg + 1);
                    s += ", ";
                    s += isRef;
                    s += argTypeName + " " + argName;

                    if (iArg != 0)
                    {
                        types += ", ";
                        paramsList += ", ";
                    }

                    types += "typeof(" + argTypeName + ")";
                    if (isRefType[iArg])
                        types += ".MakeByRefType()";

                    paramsList += argName;
                } //for

                s += ")";
                s += lf;
                s += headSpace + "{" + lf;

                //Type[] types = new Type[] { typeof(A1).MakeByRefType() };
                s += headSpace + "    ";
                if( types.Length != 0 ) types += " ";
                s += "Type[] types = new Type[] { " + types + "};";
                s += lf;

                //object[] args = new object[] { a1 };
                s += headSpace + "    ";
                if( paramsList.Length != 0 ) paramsList += " ";
                s += "object[] args = new object[] { " + paramsList + "};";
                s += lf;

                //MethodInfo f = GetFunc(func, types);
                //if (f == null)
                //    return null;
                //object r = f.Invoke(o, args);
                s += headSpace + "    MethodInfo f = GetFunc(func, types);" + lf;
                s += headSpace + "    if (f == null)" + lf;
                s += headSpace + "        return null;" + lf;
                s += headSpace + "    object r = f.Invoke(o, args);" + lf;

                for (int iArg = 0; iArg < callArgs; iArg++)
                {
                    if (!isRefType[iArg])
                        continue;
                    // a1 = (A1)args[0];
                    String argTypeName = "A" + (iArg + 1);
                    String argName = "a" + (iArg + 1);

                    s += headSpace + "    ";
                    s += argName + " = (" + argTypeName + ")args[" + iArg + "];";
                    s += lf;
                }

                s += headSpace + "    return r;" + lf;
                s += headSpace + "}" + lf;
                s += lf;
            }

        } //for

        String oldautogenCode = Regex.Match(src, autogenRegex, RegexOptions.Singleline).Groups[2].Value;

        //
        // Visual studio text editor configuration affects spacing. We trim here everything so we can compare output.
        //
        oldautogenCode = oldautogenCode.Replace(" ", "").TrimStart('\r','\n');
        String newautogenCode = s.Replace(" ", "").TrimStart('\r', '\n');

        String newSrc = Regex.Replace(src, autogenRegex, "$1\r\n" + s + "$3", RegexOptions.Singleline);

        if (oldautogenCode == newautogenCode)
        {
            Console.WriteLine("Source code is up-to-date.");
        }
        else
        {
            File.WriteAllText(srcFilename, newSrc, Encoding.UTF8);
        }
    } //UpdateSourceCodeHelperFunctions
} //class ClassCaller

这样:

ClassCaller.UpdateSourceCodeHelperFunctions(2);

函数重新生成自动生成的代码部分以支持 - 出于演示目的,我现在只支持调用2个参数,但通常需要更多参数 - 但这会增加自动生成的代码大小。

自动生成的代码只能在调试模式下更新,而不能在发布配置中更新。 (但在发布中根本不需要这样做。)

也许这不是你问题的直接答案,但我认为这符合你的想法。

反射调用要求所有参数类型正确匹配100% - 否则反射将无法找到所需的方法。

此解决方案也有局限性 - 例如,如果某些参数是可选的,它就无法找到正确的方法 - 例如:

void DoMethod( int a, int b = 0 );

所以你可以打电话:

DoMethod(5);

但不是:

DoCall("DoMethod", 5);

它必须是完整的参数集:

DoCall("DoMethod", 5, 0);

我知道通过反射调用的方法在消耗时间方面可能很昂贵,所以在使用之前要三思而后行。

更新31.5.2016 我还发现C#&#39;动态&#39;关键字也可以用于特定方法的动态调用,而不需要知道反射方法调用细节,但动态只对实例进行操作,使用ClassCaller更容易进行静态方法调用。