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


84

您将如何在C#中进行专业化?

我会提出一个问题。您有模板类型,不知道它是什么。但是您确实知道它是否源于XYZ您要调用.alternativeFunc()。一个很棒的方法是调用一个专用函数或类并normalCall返回,.normalFunc()而对任何派生类型的XYZcall进行其他专门化处理.alternativeFunc()。用C#怎么做?

Answers:


86

在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将始终解析为正确的替代。


63

假设您正在谈论可以使用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;
  }

Foo属性与Bar方法的比较……似乎并不是典型的专业化知识……
Marc Gravell

2
不,这不是典型的特殊化,但这是您可以做的唯一简单的事情...(AFAIK)
Tomas Petricek 09年

3
在不使用扩展方法的情况下,看起来也可以正常工作,而只是使用static通用类型的方法。也就是说,@ MarcGravell答案中指出的问题似乎可以通过基于像MyClass<T>/的arg来“模板化”方法MyClass<int>,而不是将方法模板化为特定的“数据”类型(T/ int)来解决。
Slipp D. Thompson

4
另外的限制是它不适用于间接的泛型调用,例如,从method内进行void CallAppropriateBar<T>() { (new MyClass<T>()).Bar(); }
BartoszKP

18

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

为了专门研究T,我们创建了一个通用接口,该接口具有一种称为(例如)Apply的方法。对于实现接口的特定类,定义特定于该类的方法Apply。此中间类称为特质类。

可以在通用方法的调用中将该特征类指定为参数,然后(当然)始终采用正确的实现。

无需手动指定,traits类也可以存储在global中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();

有关广泛的描述和示例,请参阅此链接至我最近的博客以及后续文章。


这是工厂模式,它是解决仿制药某些缺点的一种不错的方法
Yaur 2014年

1
@Yaur对我来说,我看起来像是教科书的Decorator模式。
Slipp D. Thompson

13

我也在寻找一种模式来模拟模板专业化。在某些情况下,有些方法可能会起作用。但是情况如何

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


太棒了,谢谢。它允许我创建一个通用接口,以调用一堆预先存在的方法,每种方法都是针对特定类型编写的,这些方法无法(或至少很难解决)被通用地重写。看起来好像我不得不做些可怕的事if (type == typeof(int)),然后将其放回带有额外装箱/拆箱的通用类型return (T)(object)result;(因为该类型仅在逻辑上已知,而不是静态已知)
Andrew Wright,

6

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

编译器不像在C ++中那样执行专业化。

我建议您看一下PostSharp,以便在完成通常的编译器以达到类似于C ++的效果之后注入代码。


4

我认为有一种方法可以使用.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

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


2
我想哭一点。
user2864740 18-10-17

0

如果您只想测试某个类型是否源自XYZ,则可以使用:

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

如果是这样,您可以将“ theunknownobject”强制转换为XYZ,并像下面这样调用AlternativeFunc():

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

希望这可以帮助。


1
我对C#的了解不多,但是无论您投票否决了谁,都应该说为什么。我不知道您的答案有什么问题或有什么问题。

也不确定。对我来说似乎足够有效。虽然比必要时更冗长。
jalf

3
不是我,而是因为答案与问题完全无关。查找"c++ template specialization"
georgiosd 2012年

这并不总是有效。例如,您看不到T是否为布尔值,然后转换为布尔值。
科斯2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.