如何在C#中进行模板专业化

80 c# partial-specialization

你会如何在C#中进行专业化?

我会提出一个问题.你有一个模板类型,你不知道它是什么.但你知道它是否来自XYZ你想要打电话.alternativeFunc().一个很好的方法是调用一个专门的函数或类,并normalCall返回,.normalFunc()同时在任何派生类型的XYZ调用上具有其他特化.alternativeFunc().如何在C#中完成?

Mar*_*ell 83

在C#中,最接近专业化的是使用更具体的重载; 然而,这很脆弱,并未涵盖所有可能的用途.例如:

void Foo<T>(T value) {Console.WriteLine("General method");}
void Foo(Bar value) {Console.WriteLine("Specialized method");}
Run Code Online (Sandbox Code Playgroud)

在这里,如果编译器在编译时知道类型,它将选择最具体的:

Bar bar = new Bar();
Foo(bar); // uses the specialized method
Run Code Online (Sandbox Code Playgroud)

然而....

void Test<TSomething>(TSomething value) {
    Foo(value);
}
Run Code Online (Sandbox Code Playgroud)

Foo<T>甚至会使用TSomething=Bar,因为它在编译时烧毁.

另一种方法是通用方法中使用类型测试- 但是,这通常是一个糟糕的想法,不推荐使用.

基本上,C#只是不希望你使用特殊化,除了多态:

class SomeBase { public virtual void Foo() {...}}
class Bar : SomeBase { public override void Foo() {...}}
Run Code Online (Sandbox Code Playgroud)

这里Bar.Foo将始终解析为正确的覆盖.


Tom*_*cek 60

假设您正在谈论模板专业化,因为它可以使用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;
  }
}
Run Code Online (Sandbox Code Playgroud)

现在你可以这样写:

var cls = new MyClass<int>();
cls.Bar();
Run Code Online (Sandbox Code Playgroud)

如果你想在没有提供专业化的情况下使用该方法的默认大小写,那么我认为编写一个通用Bar扩展方法应该可以解决问题:

  public static int Bar<T>(this MyClass<T> cls) {
    return cls.Foo + 42;
  }
Run Code Online (Sandbox Code Playgroud)

  • 这看起来也可以正常工作而不使用扩展方法 - 只是采用泛型类型的`static`方法.也就是说,在@MarcGravell答案中指出的问题看起来是通过"模板化"基于类似`MyClass <T>`/`MyClass <int>`的方法来避开,而不是将方法模板化为特定的"数据" "type(`T` /`int`). (3认同)
  • 不,这不是典型的specilaziation,但它是你能做的唯一容易的事情......(AFAIK) (2认同)
  • 另外的限制是它不适用于间接的泛型调用,例如,从方法`void CallAppropriateBar &lt;T&gt;(){(new MyClass &lt;T&gt;())。Bar(); }`。 (2认同)

小智 15

通过添加中间类和字典,可以实现专业化.

为了专注于T,我们创建了一个通用接口,有一个叫做(例如)Apply的方法.对于实现接口的特定类,定义特定于该类的方法.这个中间类称为traits类.

可以将traits类指定为泛型方法调用中的参数,然后(当然)总是采用正确的实现.

traits类也可以存储在全局中,而不是手动指定它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();
Run Code Online (Sandbox Code Playgroud)

有关详细说明和示例,请参阅此链接到我最近的博客和后续内容.


Lio*_*nAM 11

我也在寻找模拟模板专业化的模式.有些方法可能在某些情况下有效.不过那个案子呢

static void Add<T>(T value1, T value2)
{
    //add the 2 numeric values
}
Run Code Online (Sandbox Code Playgroud)

可以使用语句选择动作,例如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;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以写,而不必事先知道类型:

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;
}
Run Code Online (Sandbox Code Playgroud)

如果不仅要为已实现的类型调用特化,而且还要为派生类型调用,可以使用In参数作为接口.但是,在这种情况下,方法的返回类型不能再是泛型类型T.

  • 这真是太棒了,谢谢。它允许我创建一个通用接口,用于调用一堆预先存在的方法,每个方法都是为特定类型编写的,这些方法不能(或至少很难)一般地重写。看起来我必须做一些可怕的“if(type == typeof(int))”,然后转换回带有额外装箱/拆箱“return (T)(object)result;”的泛型类型(因为类型只是逻辑上已知的,而不是静态上已知的) (2认同)

Gre*_*egC 6

一些建议的答案是使用运行时类型信息:本质上比编译时绑定方法调用慢.

编译器不像C++那样强制实现专业化.

在推荐通常的编译器来实现类似于C++的效果之后,我建议在PostSharp中寻找一种注入代码的方法.


小智 5

我认为有一种方法可以使用.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));
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

Int16 -1
UInt16 1
Int32 -1
UInt32 1
Run Code Online (Sandbox Code Playgroud)

这表明为不同的类型调用了不同的实现。

  • 我有点想哭。 (2认同)