Словарь методов с разными определениями

#.net #dictionary #delegates

#.net #словарь #делегаты

Вопрос:

У меня есть ряд сторонних классов API (23 и более), которые мне нужны для создания некоторых тестовых вызовов для использования существующих вызовов в качестве прокси (поскольку нет определенных выделенных тестовых вызовов), например GetTitles() , GetCountries() , и т.д.

На сегодняшний день я скопировал около 20 строк кода в каждом из пространств имен API, что, очевидно, создает много проблем с обслуживанием. Но я хотел бы свести это к одному методу, находящемуся в классе, наследуемом каждым (частичным) классом API.

Это означает, что каждый API / класс добавил бы к нему унаследованный Helper TestConnection() метод класса A. Затем TestConnection() методу необходимо знать, какой тестовый вызов прокси вызывать. (Я определил вызывающий класс и, следовательно, могу решить, какой вызов выполнить, используя this.GetType() etc, выдавая «Api1», «Api2» и т. Д.)

Я также читал об использовании делегатов для динамического выполнения тестового вызова прокси.

НО …

Я хотел поместить эти делегаты в TestCalls словарь (вызывающего абонента string, делегата TestCall), где TestCall есть определенный делегат, и вызвать их следующим образом:

TestCalls[caller].Invoke()

Однако проблема двоякая:

  1. Не все прокси-вызовы имеют одинаковую сигнатуру в терминах аргументов и возвращаемого типа *, и
  2. По крайней мере, для одного прокси-вызова (пока) требуется один строковый аргумент (остальные, хотя у них либо нет, либо необязательные аргументы, я не выполняю вызовы, используя их).

(* Поскольку меня не интересует возвращаемое значение, а только то, завершится ли вызов успешно или нет, я могу использовать object в качестве возвращаемого типа для всех вызовов.)

Изначально у меня было это:

 private delegate object TestCall();
private static Dictionary<string, TestCall> TestCalls = new Dictionary<string, TestCall>();

TestCalls.Add("Api1", Api1.GetTitles);
TestCalls.Add("Api2", Api2.GetCountries);
TestCalls.Add("Api3", Api3.GetCurrencies);
TestCalls.Add("Api4", Api4.GetNameFormats);
  

Но это не компилируется, потому что сигнатуры методов не совпадают (и я не могу их контролировать).

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

 private delegate object TestCall1();
private delegate object TestCall2(bool? a1);
private delegate object TestCall3(int? a1, string a2, bool? a3);
private static Dictionary<string, Delegate> TestCalls = new Dictionary<string, Delegate>();

TestCalls.Add("Api1", new TestCall1(Api1.GetTitles));
TestCalls.Add("Api2", new TestCall1(Api2.GetCountries));
TestCalls.Add("Api3", new TestCall2(Api3.GetCurrencies));
TestCalls.Add("Api4", new TestCall3(Api4.GetNameFormats));
  

Это работает (помимо необходимости добавлять значение аргумента к одному вызову, чего я действительно хотел бы избежать).

Но это намного сложнее, чем я надеялся.

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

Есть ли у кого-нибудь какие-либо предложения по «обобщению» делегатов прокси-вызова? Или совершенно другой подход?

В идеале я мог бы иметь словарь явных прокси-вызовов, включая любые аргументы, такие как Api5.GetSomething("StringArg") или Api6.GetAnother(IntArg) , чтобы мне не приходилось условно добавлять это в блок вызова.

Кроме того, для меня нет смысла применять предоставление / число аргументов, если аргументы являются необязательными; когда я вручную вызываю метод с необязательными аргументами и не предоставляю их, все в порядке. Зачем вводить ограничение с помощью делегата??

Ответ №1:

Вот что у меня получилось:

 class ApiHelper
{
    private static Dictionary<string, string> TestCalls= new Dictionary<string, string>();

    static ApiHelper()
    {
        TestCalls.Add("Api1", "GetTitles");
        TestCalls.Add("Api2", "GetCountries");
        TestCalls.Add("Api3", "GetCurrencies");
        TestCalls.Add("Api4", "GetNameFormats");
        ...

    bool TestConnection()
    {
        string caller = GetType().FullName.Split(new char[] {'.'})[1]; // Api1, Api2, ...
        try
        {
            MethodInfo oMethod = GetType().GetMethod(TestCalls[caller]);
            object[] params = new object[method.GetParameters().Length];
            if (caller == "Api2") params[0] = "AnArg"; // Add an argument to this call
            var result = oMethod.Invoke(this, params);
            TestConnectionMsg = $"The API connection test passed.";
            return true;
        }
        catch (Exception x)
        {
            TestConnectionMsg = $"The API connection test failed: {x.Message}.";
            return false;
        }
    }
}
  

Этот код выводит обязательный тестовый вызов — хотя и необязательный! — список параметров, которые ожидает вызов и выполняет вызов, завершается успешно или завершается неудачей в блоке catch.

Это не самое элегантное решение, которое я искал, но оно отлично работает и достаточно надежно.