假设我有三种方法:
void Foo(MemoryStream v) {Console.WriteLine ("MemoryStream");}
void Foo(Stream v) {Console.WriteLine ("Stream");}
void Foo(object v) {Console.WriteLine ("object");}
我调用方法Foo
传递开放泛型类型的第一个参数:
void Bar<T>()
{
Foo(default(T)); //just to show the scenario
//default(T) or new T() doesn't make a difference, null is irrelevant here
}
我想调用MemoryStream
重载,所以我用MemoryStream
关闭了通用类型的方法Bar
:
Bar<MemoryStream>();
但是调用object
重载。如果我将通用约束添加到 Foo 签名where T : Stream
,则调用Stream
版本。
有没有办法根据开放的泛型类型T
调度方法调用以MemoryStream
重载?
我不想使用Delegate.CreateDelegate
或其他反射 API。只是在C#语言的手段中。我可能错过了语言本身中的某些东西。
尝试了此方案,值类型为封闭泛型类型并使用静态方法。
您描述的内容称为"模板专用化",在 C# 中不起作用。它在 C++ 中可用,但仍未进入 C#。
这已经在"C# 通用接口专用化"中得到了解答。简短的版本是你不能这样做。您可以强制运行时解析,但在这种情况下,使用泛型是没有意义的。泛型应该用于在不同类型上使用相同的代码。
也许还有另一种方法可以做你真正想要的事情。我在实现策略或模板方法模式时遇到过类似的情况,我希望大多数代码在一般情况下都能工作,但修改一些特定步骤。
在这种情况下,最好在实际创建"模板方法"时将自定义步骤作为接口注入到类中,甚至是专门化行为的 Func<> 对象。
当然,还有很多其他方法可以做到这一点,其中一些方法对于特定问题比其他方法效果更好
这只能使用动态绑定来完成,例如:
void Bar<T>(T value)
{
dynamic parameter = value;
Foo(parameter);
}
请注意,动态调度使用实际运行时对象的实际运行时类型来执行方法调度,因此必须有一个对象。如果值为 null
,这将不起作用。
也许是这样的:
void Bar<T>()
{
if(typeof(T) == typeof(Stream))
Foo(default(T) as Stream); //just to show the scenario
}
这不是一个"漂亮"的答案(事实上,由于这有点颠覆泛型的意图,所以很难在语言中找到一个漂亮的答案),但你也许可以通过字典对重载查找进行编码:
static readonly Dictionary<Type, Action<object>> overloads
= new Dictionary<Type, Action<object>> {
{typeof(Stream), o => Foo((Stream)o)},
{typeof(MemoryStream), o => Foo((MemoryStream)o)}
};
public static void Bar<T>() {
Action<object> overload;
if (overloads.TryGetValue(typeof(T), out overload)) {
overload(default(T));
} else {
Foo((object)default(T));
}
}
这不好,我不推荐它。为了便于维护,您可以将overloads
填充移动到静态构造函数/类型初始化器,并通过反射填充它。另请注意,这仅适用于确切的T
- 如果有人使用意外的类型(例如Bar<NetworkStream>
),它不起作用 - 尽管您可能可以遍历基本类型(但即便如此,它也没有很好的支持接口等)。
所有因素,这种方法没有太多值得推荐的地方。我可能会建议从不同的角度处理整个问题(即消除这样做的需要)。
引用类型时,默认值 (T) 将始终返回 null,如果 T 为数值类型,则返回零。
因此,在任何时候它都不会返回一个对象,您可以使用该对象调用重载版本的 Foo 方法。
所以简答你不能这样做,你必须找出其他方法来调用重载方法。
我遇到了同样的问题,我知道的独特解决方案是尝试铸造它,直到我得到null
以外的东西。然后,我将在编译时拥有正确的类型,编译器将知道要调用的正确重载。我找不到另一种方法来实现这种"运行时多态性"。
为了避免使用字典或类似开关的解决方案(可维护性差,正如 Marc 指出的那样),只需调用 Method((dynamic) o)
,DLR 将根据运行时类型调用正确的重载方法。
请记住:
1) 提供具有最顶级类型的默认重载;
2) 在运行时解析类型时注意任何歧义(即两个独立的接口和一个同时使用两者的实现);
3)处理null
案件。
您可以在此处阅读有关它的更多信息。
希望我有所帮助。