Как вызвать перегрузку метода на основе закрытого родового типа?

Предположим, что у меня есть три метода:

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.

Подробнее об этом можно узнать здесь.

Надеюсь, я помог.