Указатель функции С# в перегруженной функции

У меня есть 2 перегруженные функции С#:

private void _Insert(Hashtable hash, string tablename, Func<string, object[], SqlCommand> command)
private void _Insert(Hashtable hash, string tablename, Func<string, object[], OleCommand> command)

В основном используется OleCommand и другой SqlCommand для возвращаемого значения функции.

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

class RemoteDatabase
{    
      public SqlCommand GetCommand(string query, object[] values);
}

_Insert(enquiry, "Enquiry", (Func<string, object[], SqlCommand>)(_RemoteDatabase.GetCommand));

Есть ли способ сказать компилятору умнее, чтобы мне не пришлось делать литье типов? Или я сделал что-то неправильно?

EDIT: Добавлена ​​щедрость, потому что мне очень интересно учиться. Спасибо за любой совет.

Ответ 1

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

class RemoteDatabase
{
    public int GetCommand(){return 5;}
}

class Program
{

    static void Main(string[] args)
    {
        var rd = new RemoteDatabase();

        // Overloaded(1, rd.GetCommand); // this is a compile error, ambigous

        Overloaded(1, () => rd.GetCommand()); // this compiles and works

        Console.ReadLine();
    }

    static void Overloaded(int paramOne, Func<int> paramFun)
    {
        Console.WriteLine("First {0} {1}", paramOne, paramFun());
    }

    static void Overloaded(int paramOne, Func<string> paramFun)
    {
        Console.WriteLine("Second {0} {1}", paramOne, paramFun());
    }
}

Edit- Я нашел этот пост Эрика Липперта, который отвечает на этот вопрос

Интересный факт: правила преобразования для лямбда типы возврата счета. Если вы скажете Foo (() = > X()), то мы делаем правильный вещь. Тот факт, что группы lambdas и группы имеют разные правила конвертирования довольно неудачны.

Ответ 2

EDIT: Это вызвано процессом, определенным в разделе "Разрешение перегрузки" спецификации С#. Когда он получает набор применимых членов-кандидатов, он не может выбрать "лучшую функцию" , потому что он не принимает тип возвращаемого значения в учетной записи во время разрешения перегрузки. Так как он не может выбрать наилучшую функцию, то двусмысленный метод вызывает ошибку вызова по спецификации.

Однако, если ваша цель - упростить вызовы методов и избежать длительной кастинга для func, вы можете использовать generics для и усложнять метод _Insert немного как это, например:

public  void Main()
    {
        _Insert(new Hashtable(), "SqlTable", F1);
        _Insert(new Hashtable(), "OleTable", F2);
    }

    private static SqlCommand F1(string name, object[] array)
    {
        return new SqlCommand();
    }

    private static OleDbCommand F2(string name, object[] array)
    {
        return new OleDbCommand();
    }

    private void _Insert<T>(Hashtable hash, string tablename, Func<string, object[], T> command) 
    {
        if (typeof(T) == typeof(SqlCommand)) {
            SqlCommand result = command(null, null) as SqlCommand;
        }
        else if (typeof(T) == typeof(OleDbCommand)) {
            OleDbCommand result = command(null, null) as OleDbCommand;
        }
        else throw new ArgumentOutOfRangeException("command");
    }

Обратите внимание на упрощенные вызовы методов

_Insert(new Hashtable(), "OleTable", F1);
_Insert(new Hashtable(), "OleTable", F2);

которые компилируют только тонкие

Ответ 3

Можете ли вы использовать Func<string, object[], DbCommand>? Это также позволит вам избавиться от перегрузки и понадобится только одна функция.

Ответ 4

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

public class RemoteDatabase
{    
      // changed to private, referenced by CommandWay1
      private SqlCommand GetCommand(string query, object[] values)
      {
          /* GetCommand() code */ 
      }

      public Func<string, object[], SqlCommand> CommandWay1
      {
          get
          {
             return (q,v) => GetCommand(q,v);
          }
      }

      // or, you could remove the above and just have this, 
      // with the code directly in the property
      public Func<string, object[], SqlCommand> CommandWay2
      {
          get
          {
             return
                (q,v) =>
                {
                   /* GetCommand() code */ 
                };
      }
}

тогда я смог получить каждый из них для компиляции без кастинга:

_Insert(enquiry, "Enquiry", (q,v) => _RemoteDatabase.GetCommand(q,v));
_Insert(enquiry, "Enquiry", _RemoteDatabase.CommandWay1);
_Insert(enquiry, "Enquiry", _RemoteDatabase.CommandWay2);