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

Как бы вы сделали специализацию на C#?

Я поставлю проблему. У вас есть тип шаблона, вы не знаете, что это такое. Но вы знаете, происходит ли это от того, что XYZ вы хотите позвонить .alternativeFunc() . Отличный способ - вызвать специализированную функцию или класс и получить normalCall возврат, в .normalFunc() то же время имея другую специализацию для любого производного типа XYZ для вызова .alternativeFunc() . Как это сделать на C#?

Ответов (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 .