Перегрузка метода в родовом классе
Я работаю с кодом, который содержит следующий перегруженный метод в универсальном классе:
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, если это необходимо.