Как сделать специализацию шаблона в С#
Как бы вы специализировались на С#?
Я поставил бы проблему. У вас есть тип шаблона, вы понятия не имеете, что это такое. Но вы знаете, получен ли он из XYZ
вы хотите вызвать .alternativeFunc()
. Отличный способ, чтобы вызвать специальную функцию или класс и имеют normalCall
вернуть .normalFunc()
в то время как у другой специализации на любом производном типе XYZ
для вызова .alternativeFunc()
. Как это сделать в С#?
Ответы
Ответ 1
В С# наиболее близким к специализации является использование более конкретной перегрузки; однако это хрупкое и не охватывает все возможные применения. Например:
void Foo<T>(T value) {Console.WriteLine("General method");}
void Foo(Bar value) {Console.WriteLine("Specialized method");}
Здесь, если компилятор знает типы при компиляции, он выбирает наиболее конкретные:
Bar bar = new Bar();
Foo(bar); // uses the specialized method
Однако....
void Test<TSomething>(TSomething value) {
Foo(value);
}
будет использовать Foo<T>
даже для TSomething=Bar
, поскольку это сжигается во время компиляции.
Еще один подход заключается в том, чтобы использовать типовое тестирование в рамках универсального метода, но обычно это плохая идея и не рекомендуется.
В принципе, С# просто не хочет, чтобы вы работали со специализациями, кроме полиморфизма:
class SomeBase { public virtual void Foo() {...}}
class Bar : SomeBase { public override void Foo() {...}}
Здесь Bar.Foo
всегда будет решать правильное переопределение.
Ответ 2
Предполагая, что вы говорите о специализации шаблона, поскольку это можно сделать с помощью шаблонов C++ - такая функция, как это, на самом деле недоступна в С#. Это связано с тем, что генераторы С# не обрабатываются во время компиляции и являются более характерными чертами среды выполнения.
Однако вы можете добиться аналогичного эффекта с помощью методов расширения С# 3.0. Вот пример, показывающий, как добавить метод расширения только для типа MyClass<int>
, который похож на специализированную специализацию. Обратите внимание, однако, что вы не можете использовать это, чтобы скрыть стандартную реализацию метода, потому что компилятор С# всегда предпочитает стандартные методы для методов расширения:
class MyClass<T> {
public int Foo { get { return 10; } }
}
static class MyClassSpecialization {
public static int Bar(this MyClass<int> cls) {
return cls.Foo + 20;
}
}
Теперь вы можете написать следующее:
var cls = new MyClass<int>();
cls.Bar();
Если вы хотите иметь случай по умолчанию для метода, который будет использоваться, когда специализация не предоставляется, чем я считаю, что писать один общий метод расширения Bar
должен делать трюк:
public static int Bar<T>(this MyClass<T> cls) {
return cls.Foo + 42;
}
Ответ 3
Добавив промежуточный класс и словарь, возможно специализация.
Чтобы специализироваться на T, мы создаем общий интерфейс, имеющий метод, называемый (например,) Apply. Для конкретных классов, которые реализуются в интерфейсе, определяется метод Apply, специфичный для этого класса. Этот промежуточный класс называется классом признаков.
Этот класс признаков может быть указан как параметр в вызове универсального метода, который тогда (конечно) всегда принимает правильную реализацию.
Вместо указания вручную, класс признаков также можно сохранить в глобальном IDictionary<System.Type, object>
. Затем его можно искать и вуаля, у вас есть настоящая специализация.
Если это удобно, вы можете открыть его в методе расширения.
class MyClass<T>
{
public string Foo() { return "MyClass"; }
}
interface BaseTraits<T>
{
string Apply(T cls);
}
class IntTraits : BaseTraits<MyClass<int>>
{
public string Apply(MyClass<int> cls)
{
return cls.Foo() + " i";
}
}
class DoubleTraits : BaseTraits<MyClass<double>>
{
public string Apply(MyClass<double> cls)
{
return cls.Foo() + " d";
}
}
// Somewhere in a (static) class:
public static IDictionary<Type, object> register;
register = new Dictionary<Type, object>();
register[typeof(MyClass<int>)] = new IntTraits();
register[typeof(MyClass<double>)] = new DoubleTraits();
public static string Bar<T>(this T obj)
{
BaseTraits<T> traits = register[typeof(T)] as BaseTraits<T>;
return traits.Apply(obj);
}
var cls1 = new MyClass<int>();
var cls2 = new MyClass<double>();
string id = cls1.Bar();
string dd = cls2.Bar();
Смотрите ссылку в мой последний блог и последующие подробные описания и образцы.
Ответ 4
В некоторых предлагаемых ответах используется информация типа времени выполнения: по сути медленнее, чем вызовы метода привязки времени компиляции.
Компилятор не применяет специализацию, а также делает это на С++.
Я бы рекомендовал посмотреть PostSharp на способ ввода кода после выполнения обычного компилятора для достижения эффекта, подобного С++.
Ответ 5
Я искал шаблон для моделирования специализации шаблонов. Существуют некоторые подходы, которые могут работать при некоторых обстоятельствах. Однако как насчет случая
static void Add<T>(T value1, T value2)
{
//add the 2 numeric values
}
Можно было бы выбрать действие, используя утверждения, например. if (typeof(T) == typeof(int))
. Но есть лучший способ моделирования реальной специализации шаблонов с накладными расходами одного вызова виртуальной функции:
public interface IMath<T>
{
T Add(T value1, T value2);
}
public class Math<T> : IMath<T>
{
public static readonly IMath<T> P = Math.P as IMath<T> ?? new Math<T>();
//default implementation
T IMath<T>.Add(T value1, T value2)
{
throw new NotSupportedException();
}
}
class Math : IMath<int>, IMath<double>
{
public static Math P = new Math();
//specialized for int
int IMath<int>.Add(int value1, int value2)
{
return value1 + value2;
}
//specialized for double
double IMath<double>.Add(double value1, double value2)
{
return value1 + value2;
}
}
Теперь мы можем писать, не задумываясь заранее:
static T Add<T>(T value1, T value2)
{
return Math<T>.P.Add(value1, value2);
}
private static void Main(string[] args)
{
var result1 = Add(1, 2);
var result2 = Add(1.5, 2.5);
return;
}
Если специализация должна быть вызвана не только для реализованных типов, но и для производных типов, можно использовать параметр In
для интерфейса. Однако в этом случае типы возвращаемых методов не могут быть более типичного типа T
.
Ответ 6
Я думаю, что есть способ достичь этого с помощью.NET 4+ с использованием динамического разрешения:
static class Converter<T>
{
public static string Convert(T data)
{
return Convert((dynamic)data);
}
private static string Convert(Int16 data) => $"Int16 {data}";
private static string Convert(UInt16 data) => $"UInt16 {data}";
private static string Convert(Int32 data) => $"Int32 {data}";
private static string Convert(UInt32 data) => $"UInt32 {data}";
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Converter<Int16>.Convert(-1));
Console.WriteLine(Converter<UInt16>.Convert(1));
Console.WriteLine(Converter<Int32>.Convert(-1));
Console.WriteLine(Converter<UInt32>.Convert(1));
}
}
Выход:
Int16 -1
UInt16 1
Int32 -1
UInt32 1
Это показывает, что для разных типов вызывается другая реализация.
Ответ 7
Если вы просто хотите проверить, не заимствован ли тип из XYZ, вы можете использовать:
theunknownobject.GetType().IsAssignableFrom(typeof(XYZ));
Если это так, вы можете использовать "theunknownobject" для XYZ и вызывать альтернативный вариант() следующим образом:
XYZ xyzObject = (XYZ)theunknownobject;
xyzObject.alternativeFunc();
Надеюсь, это поможет.