你会如何在C#中进行专业化?
我会提出一个问题.你有一个模板类型,你不知道它是什么.但你知道它是否来自XYZ
你想要打电话.alternativeFunc()
.一个很好的方法是调用一个专门的函数或类,并normalCall
返回,.normalFunc()
同时在任何派生类型的XYZ
调用上具有其他特化.alternativeFunc()
.如何在C#中完成?
在C#中,最接近专业化的是使用更具体的重载; 然而,这很脆弱,并未涵盖所有可能的用途.例如:
void Foo(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 value) { Foo(value); }
Foo
甚至会使用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
类型添加扩展方法,这就像模板特化.但请注意,您不能使用它来隐藏方法的默认实现,因为C#编译器总是更喜欢扩展方法的标准方法:
class MyClass{ public int Foo { get { return 10; } } } static class MyClassSpecialization { public static int Bar(this MyClass cls) { return cls.Foo + 20; } }
现在你可以这样写:
var cls = new MyClass(); cls.Bar();
如果你想在没有提供专业化的情况下使用该方法的默认大小写,那么我认为编写一个通用Bar
扩展方法应该可以解决问题:
public static int Bar(this MyClass cls) { return cls.Foo + 42; }
通过添加中间类和字典,可以实现专业化.
为了专注于T,我们创建了一个通用接口,有一个叫做(例如)Apply的方法.对于实现接口的特定类,定义特定于该类的方法.这个中间类称为traits类.
可以将traits类指定为泛型方法调用中的参数,然后(当然)总是采用正确的实现.
traits类也可以存储在全局中,而不是手动指定它IDictionary
.它可以被抬起来瞧,你在那里有真正的专业.
如果方便,您可以使用扩展方法公开它.
class MyClass{ public string Foo() { return "MyClass"; } } interface BaseTraits { string Apply(T cls); } class IntTraits : BaseTraits > { public string Apply(MyClass cls) { return cls.Foo() + " i"; } } class DoubleTraits : BaseTraits > { public string Apply(MyClass cls) { return cls.Foo() + " d"; } } // Somewhere in a (static) class: public static IDictionary register; register = new Dictionary (); register[typeof(MyClass )] = new IntTraits(); register[typeof(MyClass )] = new DoubleTraits(); public static string Bar (this T obj) { BaseTraits traits = register[typeof(T)] as BaseTraits ; return traits.Apply(obj); } var cls1 = new MyClass (); var cls2 = new MyClass (); string id = cls1.Bar(); string dd = cls2.Bar();
有关详细说明和示例,请参阅此链接到我最近的博客和后续内容.
我也在寻找模拟模板专业化的模式.有些方法可能在某些情况下有效.不过那个案子呢
static void Add(T value1, T value2) { //add the 2 numeric values }
可以使用语句选择动作,例如if (typeof(T) == typeof(int))
.但是有一种更好的方法可以通过单个虚函数调用的开销来模拟真实的模板专业化:
public interface IMath{ T Add(T value1, T value2); } public class Math : IMath { public static readonly IMath P = Math.P as IMath ?? new Math (); //default implementation T IMath .Add(T value1, T value2) { throw new NotSupportedException(); } } class Math : IMath , IMath { public static Math P = new Math(); //specialized for int int IMath .Add(int value1, int value2) { return value1 + value2; } //specialized for double double IMath .Add(double value1, double value2) { return value1 + value2; } }
现在我们可以写,而不必事先知道类型:
static T Add(T value1, T value2) { return Math .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
.
一些建议的答案是使用运行时类型信息:本质上比编译时绑定方法调用慢.
编译器不像C++那样强制实现专业化.
在推荐通常的编译器来实现类似于C++的效果之后,我建议在PostSharp中寻找一种注入代码的方法.
我认为有一种方法可以使用.NET 4+来实现动态分辨率:
static class Converter{ 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 .Convert(-1)); Console.WriteLine(Converter .Convert(1)); Console.WriteLine(Converter .Convert(-1)); Console.WriteLine(Converter .Convert(1)); } }
输出:
Int16 -1 UInt16 1 Int32 -1 UInt32 1
这表明为不同的类型调用了不同的实现。