Вызов правильного общего метода с использованием переменной типа, без вывода и ссылки

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?

Пожалуйста, помогите:)

Ответ 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

Я искал какой-то подобный подход, но, к сожалению, его не нашел - но потом решил решить его сам. Однако - во время прототипирования я обнаружил, что "выход" и "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", поскольку он может поддерживать направление и направление, но "выход" поддерживает только направление.

Но кроме того, как кто-то мог заметить - если вам нужно поддерживать все виды перестановок параметров - простого кодирования недостаточно - вам нужно написать генератор кода - что я сделал в конце.

Таким образом, тестовый код выглядит примерно так:

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 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);

Функция регенерирует автоматически сгенерированную часть кода для поддержки - для демонстрационных целей я поддерживаю теперь только два параметра, которые вызывают, но обычно вам нужно больше параметров, но это увеличит размер автогенерированного кода.

Автогенерируемый код может обновляться только в режиме отладки, а не в конфигурации выпуска. (Но в релизе это вообще не требуется).

Возможно, это не прямой ответ на ваш вопрос, но я думаю, что это следует за вашей идеей.

Отразить вызов требует, чтобы все типы аргументов соответствовали 100% правильно - иначе отражение не сможет найти требуемый метод.

Также это решение имеет ограничение - например, он не сможет найти правильный метод, если некоторые параметры являются необязательными - например:

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

Итак, вы можете позвонить:

DoMethod(5);

Но не:

DoCall("DoMethod", 5);

должен быть полный набор аргументов:

DoCall("DoMethod", 5, 0);

Я понимаю, что метод вызова через отражение может быть дорогостоящим в потребляемом времени, поэтому подумайте дважды, прежде чем использовать его.

Обновление 31.5.2016. Я также узнал, что ключевое слово С# "dynamic" можно использовать также для динамического вызова определенного метода, не зная детали вызова метода отражения, но динамика работает только с экземплярами, статические вызовы метода по-прежнему легче сделать с помощью ClassCaller.