Как сделать специализацию шаблона на C#
Как бы вы сделали специализацию на C#?
Я поставлю проблему. У вас есть тип шаблона, вы не знаете, что это такое. Но вы знаете, происходит ли это от того, что XYZ
вы хотите позвонить .alternativeFunc()
. Отличный способ - вызвать специализированную функцию или класс и получить normalCall
возврат, в .normalFunc()
то же время имея другую специализацию для любого производного типа XYZ
для вызова .alternativeFunc()
. Как это сделать на C#?
Ответов (7)7
Некоторые из предложенных ответов используют информацию о типе времени выполнения: по своей сути медленнее, чем вызовы методов с привязкой во время компиляции.
Компилятор не требует специализации так же хорошо, как в C++.
Я бы порекомендовал взглянуть на PostSharp, чтобы найти способ внедрения кода после того, как обычный компилятор будет выполнен, чтобы добиться эффекта, аналогичного C++.
Я думаю, что есть способ добиться этого с помощью .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
Это показывает, что для разных типов требуется другая реализация.
Если вы просто хотите проверить, является ли тип производным от XYZ, вы можете использовать:
theunknownobject.GetType().IsAssignableFrom(typeof(XYZ));
Если это так, вы можете преобразовать «theunknownobject» в XYZ и вызвать альтернативуFunc () следующим образом:
XYZ xyzObject = (XYZ)theunknownobject;
xyzObject.alternativeFunc();
Надеюсь это поможет.
Предполагая, что вы говорите о специализации шаблонов, поскольку это можно сделать с помощью шаблонов C++ - подобная функция на самом деле недоступна в C#. Это связано с тем, что универсальные шаблоны C# не обрабатываются во время компиляции и являются функцией среды выполнения.
Однако вы можете добиться аналогичного эффекта, используя методы расширения C# 3.0. Вот пример, который показывает, как добавить метод расширения только для MyClass<int>
типа, что аналогично специализации шаблона. Однако обратите внимание, что вы не можете использовать это, чтобы скрыть реализацию метода по умолчанию, потому что компилятор C# всегда предпочитает стандартные методы методам расширения:
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;
}
В C# наиболее близкой к специализации является использование более конкретной перегрузки; однако он хрупкий и не покрывает все возможные варианты использования. Например:
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
, так как это записывается во время компиляции.
Еще один подход - использовать тестирование типов в рамках универсального метода, однако это обычно плохая идея и не рекомендуется.
По сути, C# просто не хочет, чтобы вы работали со специализациями, кроме полиморфизма:
class SomeBase { public virtual void Foo() {...}}
class Bar : SomeBase { public override void Foo() {...}}
Здесь Bar.Foo
всегда будет разрешено правильное переопределение.
При добавлении промежуточного класса и словаря возможна специализация .
Чтобы специализироваться на 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();
См. Эту ссылку на мой недавний блог и последующие действия для подробного описания и примеров.
Я тоже искал шаблон для имитации специализации шаблона. Есть несколько подходов, которые могут сработать при определенных обстоятельствах. Однако как насчет случая
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
.