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

Как бы вы занялись специализацией в C#?

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

8 ответов

В 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 всегда разрешит правильное переопределение.

Предполагая, что вы говорите о специализации шаблонов, как это можно сделать с помощью шаблонов 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;
  }

Я также искал шаблон для имитации специализации шаблона. Есть несколько подходов, которые могут работать в некоторых обстоятельствах. Однако как насчет дела

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 больше

При добавлении промежуточного класса и словаря возможна специализация.

Чтобы специализироваться на 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();

Посмотрите эту ссылку на мой недавний блог и продолжение для подробного описания и примеров.

Я думаю, что есть способ добиться этого с помощью.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

Что показывает, что для разных типов вызывается другая реализация.

Некоторые из предложенных ответов используют информацию типа времени выполнения: по своей природе медленнее, чем вызовы методов с привязкой во время компиляции.

Компилятор не обеспечивает специализацию так же, как в C++.

Я бы порекомендовал взглянуть на PostSharp для способа внедрения кода после выполнения обычного компилятора для достижения эффекта, подобного C++.

Более простая, короткая и более читаемая версия того, что предложил @LionAM (примерно половина размера кода), показанная для lerp , поскольку это был мой реальный вариант использования:

      public interface ILerp<T> {
    T Lerp( T a, T b, float t );
}

public class Lerp : ILerp<float>, ILerp<double> {
    private static readonly Lerp instance = new();

    public static T Lerp<T>( T a, T b, float t )
      => ( instance as ILerp<T> ?? throw new NotSupportedException() ).Lerp( a, b, t );

    float ILerp<float>.Lerp( float a, float b, float t ) => Mathf.Lerp( a, b, t );
    double ILerp<double>.Lerp( double a, double b, float t ) => Mathd.Lerp( a, b, t );
}

Затем вы можете просто, например,

      Lerp.Lerp(a, b, t);

в любом общем контексте или предоставить метод как сгруппированныйLerp.lerpсопоставление ссылки на методT(T,T,float)подпись.

ЕслиClassCastExceptionдостаточно хорош для вас, вы, конечно, можете просто использовать

       => ( (ILerp<T>) instance ).Lerp( a, b, t );

чтобы сделать код еще короче/проще.

Если вы просто хотите проверить, является ли тип производным от XYZ, вы можете использовать:

theunknownobject.GetType().IsAssignableFrom(typeof(XYZ));

Если это так, вы можете привести "theunknownobject" к XYZ и вызвать alternativeFunc() следующим образом:

XYZ xyzObject = (XYZ)theunknownobject; 
xyzObject.alternativeFunc();

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

Другие вопросы по тегам