您将如何专攻 C#?
我会提出一个问题。你有一个模板类型,你不知道它是什么。但是您确实知道它是否源自XYZ
您要调用的.alternativeFunc()
. 一个很好的方法是调用一个专门的函数或类,并在调用的任何派生类型上有其他专门化时normalCall
返回。这将如何在 C# 中完成?.normalFunc()
XYZ
.alternativeFunc()
您将如何专攻 C#?
我会提出一个问题。你有一个模板类型,你不知道它是什么。但是您确实知道它是否源自XYZ
您要调用的.alternativeFunc()
. 一个很好的方法是调用一个专门的函数或类,并在调用的任何派生类型上有其他专门化时normalCall
返回。这将如何在 C# 中完成?.normalFunc()
XYZ
.alternativeFunc()
在 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>
for 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;
}
通过添加中间类和字典,可以进行专业化。
为了专注于 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
。
我认为有一种方法可以使用.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++ 那样强制专门化。
我建议在完成通常的编译器以实现类似于 C++ 的效果后,查看 PostSharp 以获取注入代码的方法。
如果您只想测试一个类型是否来自 XYZ,那么您可以使用:
theunknownobject.GetType().IsAssignableFrom(typeof(XYZ));
如果是这样,您可以将“theunknownobject”转换为 XYZ 并像这样调用 AlternativeFunc():
XYZ xyzObject = (XYZ)theunknownobject;
xyzObject.alternativeFunc();
希望这可以帮助。