Как вызвать перегрузку метода на основе закрытого родового типа?
Предположим, что у меня есть три метода:
void Foo(MemoryStream v) {Console.WriteLine ("MemoryStream");}
void Foo(Stream v) {Console.WriteLine ("Stream");}
void Foo(object v) {Console.WriteLine ("object");}
Я вызываю метод Foo
, передавая первый параметр открытого родового типа:
void Bar<T>()
{
Foo(default(T)); //just to show the scenario
//default(T) or new T() doesn't make a difference, null is irrelevant here
}
Я хочу вызвать перегрузку MemoryStream
, поэтому я закрываю общий тип метода Bar
с помощью MemoryStream
:
Bar<MemoryStream>();
но вызывается перегрузка object
. Если я добавлю общее ограничение на подпись Foo where T : Stream
, то вызывается версия Stream
.
Есть ли способ отправить вызов метода на перегрузку MemoryStream
на основе открытого типа типа T
?
Я не хочу использовать Delegate.CreateDelegate
или другие API Reflection. Просто в средствах языка С#. Я, вероятно, что-то пропустил внутри самого языка.
Пробовал этот сценарий со значениями типа как закрытый общий тип и использовал статические методы.
Ответы
Ответ 1
То, что вы описываете, называется "специализация шаблона" и не работает на С#. Он доступен в С++, но до сих пор не дошел до С#.
Об этом уже говорилось в разделе специализация общего интерфейса С#". Короче говоря, вы не можете этого сделать. Вы можете обойти это, вынуждая разрешение времени выполнения, но в этом случае использование дженериков не имеет смысла. Дженерики должны использоваться для использования одного и того же кода на разных типах.
Возможно, есть еще один способ сделать то, что вы действительно хотите. Я выполнял аналогичные ситуации при реализации шаблонов стратегий или шаблонов, где я хочу, чтобы большая часть кода работала в общем случае, но изменяла некоторые конкретные шаги.
В таких случаях лучше вводить пользовательские шаги в свой класс в качестве интерфейсов или даже объекты Func < > , которые специализируют поведение, когда вы на самом деле создаете "метод шаблона".
Конечно, есть много других способов сделать это, некоторые из которых работают лучше, чем другие для конкретных проблем.
Ответ 2
Это можно сделать только с помощью динамического связывания, например. например:
void Bar<T>(T value)
{
dynamic parameter = value;
Foo(parameter);
}
Обратите внимание, что динамическая отправка использует фактический тип времени выполнения реального объекта времени выполнения для отправки метода, поэтому должен быть объект. Если значение null
, это не сработает.
Ответ 3
Возможно, что-то вроде этого:
void Bar<T>()
{
if(typeof(T) == typeof(Stream))
Foo(default(T) as Stream); //just to show the scenario
}
Ответ 4
Это не "симпатичный" ответ (действительно, поскольку это своего рода подрыв намерений дженериков, трудно найти довольно ответный вызов внутри языка), но вы могли бы, возможно, закодировать перегрузку через словарь:
static readonly Dictionary<Type, Action<object>> overloads
= new Dictionary<Type, Action<object>> {
{typeof(Stream), o => Foo((Stream)o)},
{typeof(MemoryStream), o => Foo((MemoryStream)o)}
};
public static void Bar<T>() {
Action<object> overload;
if (overloads.TryGetValue(typeof(T), out overload)) {
overload(default(T));
} else {
Foo((object)default(T));
}
}
Это нехорошо, и я не рекомендую его.
Для упрощения обслуживания вы могли бы переместить популяцию overloads
в статичный конструктор/тип инициализатора и заполнить его с помощью отражения. Также обратите внимание, что это работает только для точного T
- это не сработает, если кто-то использует неожиданный тип (например, Bar<NetworkStream>
) - хотя вы могли бы предположительно перебирать базовые типы (но даже тогда он не имеет большой поддержки интерфейсов и т.д.).
Этот подход не имеет большого значения, чтобы рекомендовать его, все рассмотренное. Скорее всего, я бы посоветовал подойти ко всей проблеме под другим углом (т.е. Устранить необходимость сделать это).
Ответ 5
default (T) всегда возвращает значение null, если тип T имеет ссылочный тип и возвращает ноль, если T имеет числовые значения.
Поэтому в любой момент не возвращает объект, с помощью которого вы можете вызвать перегруженную версию методов Foo.
Короткий ответ, который вы не можете сделать, вам нужно выяснить другие способы вызова перегруженных методов.
Ответ 6
У меня была одна и та же проблема, и единственное решение, которое я знал, это попробовать, пока я не получил что-то другое, кроме null
. Тогда у меня был бы правильный тип во время компиляции, и компилятор знал бы правильную перегрузку для вызова. Я не мог найти другого способа добиться этого "полиморфизма во время выполнения".
Чтобы избежать использования словаря или коммутационного решения (низкая ремонтопригодность, как указано Marc), просто вызовите Method((dynamic) o)
, а DLR вызовет правильный метод перегрузки в соответствии с типом времени выполнения.
Просто запомните:
1) Предоставьте перегрузку по умолчанию с максимальным возможным типом;
2) Следите за какой-либо двусмысленностью во время разрешения типа во время выполнения (т.е. два независимых интерфейса и одна реализация, которая использует оба);
3) Обращайтесь к случаю null
.
Подробнее об этом можно узнать здесь.
Надеюсь, я помог.