【问题标题】:invoking correct generic method using Type variable, with out and ref使用类型变量调用正确的泛型方法,不带 out 和 ref
【发布时间】:2013-02-09 02:30:13
【问题描述】:
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 参数?

请帮忙:)

【问题讨论】:

  • 你想达到什么目的?泛型是一个编译时特性。当您将类型的实例作为类型参数传递时,您希望编译器做什么?直到运行时才会解析为类型。
  • @HamishSmith MethodCaller 是通过 ajax 请求间接调用的,因此我只能将类型作为字符串参数传递。必须调用正确的通用函数。原始实现有一个非常长且丑陋的 switch 语句,我不想​​在每次引入新类型时都维护它。
  • 反思一下吧。

标签: c# generics types lambda invoke


【解决方案1】:

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

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";
    }
}

【讨论】:

    【解决方案2】:

    我一直在寻找某种类似的方法,但不幸的是没有找到它 - 但后来决定自己解决它。但是 - 在原型设计过程中,我发现 'out' 和 'ref' 是相互排斥的 - 所以你只能支持其中一个。

    所以我想支持如下语法:

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

    这将需要生成如下函数:

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

    哪个编译器不喜欢。

    所以我决定只支持'ref'关键字——因为它可以支持进出方向,但'out'只支持出方向。

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

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

    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# 的“动态”关键字也可用于动态调用特定方法,而无需了解反射方法调用详细信息,但动态仅对实例进行操作,使用 ClassCaller 仍然更容易进行静态方法调用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-14
      • 1970-01-01
      • 1970-01-01
      • 2012-01-11
      相关资源
      最近更新 更多