Перегрузка метода в родовом классе

Я работаю с кодом, который содержит следующий перегруженный метод в универсальном классе:

public class A<T>
{
    public void Process(T item) { /*impl*/ }
    public void Process(string item) { /*impl*/ }
}

При параметризации класса для string я теряю возможность вызывать версию с общим параметром?

var a = new A<string>();
a.Process(""); //Always calls the non-generic Process(string)

Ответ 1

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

public static CallerClass
{
    public static CallGenericOverload<T>(GenericClass<T> cls, T val)
    {
        return cls.ProblemOverload(val); 
    }   

    //We can also make an extension method. 
    //We don't have to of course, it just more comfortable this way.
    public static CallGenericOverloadExtension<T>(this GenericClass<T> cls, T val)
    {
        return cls.ProblemOverload(val);
    }

}

public GenericClass<T>
{
     public string ProblemOverload(T val)
     {
         return "ProblemOverload(T val)";
     }
     public string ProblemOverload(string val)
     {
         return "ProblemOverload(string val)";
     }
}

Теперь, если мы сделаем следующее:

var genClass = new GenericClass<string>();
Console.WriteLine(genClass.ProblemOverload("")); //output: ProblemOverload(string val)
Console.WriteLine(CallerClass.CallGenericOverload(genClass, "")); //output: ProblemOverload(T val)
Console.WriteLine(genClass.CallGenericOverloadExtension("")); //output: ProblemOverload(T val)

Вы можете использовать подобный трюк, если вы определяете общий класс вместо общего метода. Важно то, что параметр, который вы передаете на ProblemOverload, должен иметь тип T, а не тип string в вызове. В конце концов, метод CallGenericOverload знает, что он получает значение T во время сборки, поэтому он будет привязан к перегрузке, принимающей параметр. Не имеет значения, что на самом деле он получит string во время выполнения.

Ответ 2

Конкретные типы имеют приоритет над родовыми типами.

Например, это то, что я тестировал в LINQPad.

void Main()
{
    new A<string>().Process("Hello");
}

public class A<T>
{
    public void Process(T item) { Console.WriteLine("T"); }
    public void Process(string item) { Console.WriteLine("string"); }
}

// Output: string

Если у вас есть проблема со скрытием общего метода, вам нужно что-то переосмыслить. Перегружая общий метод конкретными типами, вы фактически говорите: "Используйте общую перегрузку, если вам нужно, но если можете, используйте конкретную версию, потому что она должна знать, что лучше".

Ответ 3

Да. Это описано в спецификации С#, раздел 7.5.3, разрешение перегрузки.

Из 7.5.3.6:

"Хотя объявленные подписи должны быть уникальными, возможно, что подстановка аргументов типа приводит к идентичным подписям. таких случаях, правила ограничения перегрузки выше, выберите наиболее конкретного участника.

В приведенном ниже примере говорится, что в приведенном ниже случае разрешение перегрузки для G<int>.F1 будет выбирать не общий

class G1<U>
{
    int F1(U u);
    int F1(int i);
}

Используемое здесь правило размыкания связи приведено в 7.5.3.2, "Элемент более эффективной функции":

В случае, когда последовательности типов параметров {P1, P2,..., PN} и {Q1, Q2,..., QN} эквивалентны (т.е. каждый Pi имеет преобразование идентичности в соответствующая Qi), применяются следующие правила tie-break, в порядок, чтобы определить лучший член функции.

  • Если MP - это не общий метод, а MQ - общий метод, то MP лучше, чем MQ.

Ответ 4

Сделав это раньше, я склонен сказать "нет", но всегда есть люди с более узнаваемыми людьми, которые будут спорить иначе.

Если используется память, компилятор runtime выбирает наиболее строго типизированную перегрузку для выполнения.

РАЗЪЯСНЕНИЕ

Мой ответ плохо сформулирован, и я заслуживаю понижения.

ОП спросил: "При параметризации класса для строки я теряю возможность вызывать версию с общим параметром?" Я не отвечал, что "Нет, вы не можете этого сделать", но "Нет, вы не теряете способность вызывать версию с общим параметром".

Я должен был быть более ясным.

Ответ 5

Вы также можете выводить классы из A, такие как AString, которые не являются общими, но все еще имеют общий метод....

public class A<T>
{

    public void Process<t>(t item)
    {

        if (typeof(t) == typeof(string))
        {
            this.Process(item.ToString());
            return;
        }

        /*impl*/

    }

    public void Process(string item) { /*impl*/ }
}

public class AString
    : A<String>
{
    public new void Process<T>(T item)
    {
        base.Process<T>(item);
    }
}

Оба способа работают.

       var x = new A<String>();
        x.Process("");
        x.Process<String>("");

        var y = new AString();
        y.Process("");
        y.Process<String>("");

Лично я переместил бы уродливый тип проверки на Derived и обработал бы его там...

Выходы: (Если вы замените /*impl*/ на Console.WriteLine)

From Non Generic
From Non Generic
From Non Generic
From Non Generic

Вы также можете вызывать пометку метода private, а затем выставлять только Generic. С другой стороны метод будет вызываться с Reflection, если это необходимо.