Как сделать специализацию шаблона в С#

Как бы вы специализировались на С#?

Я поставил бы проблему. У вас есть тип шаблона, вы понятия не имеете, что это такое. Но вы знаете, получен ли он из 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();

Надеюсь, это поможет.