14

假设我有三种方法:

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方法关闭泛型类型的方法:BarMemoryStream

Bar<MemoryStream>();

但是object调用了重载。如果我将通用约束添加到 Foo 签名where T : Stream,则Stream调用该版本。

MemoryStream有没有办法根据开放的泛型类型将方法调用调度到重载T

我不想使用Delegate.CreateDelegate其他反射 API。就在 C# 语言中。我可能在语言本身中遗漏了一些东西。

尝试使用值类型作为封闭泛型类型并使用静态方法的这种情况。

4

6 回答 6

9

您所描述的称为“模板专业化”,在 C# 中不起作用。它在 C++ 中可用,但还没有进入 C#。

这已经在“ C# 泛型接口专业化”中得到解答。简短的版本是你不能这样做。您可以解决它强制运行时解析,但在这种情况下使用泛型没有意义。泛型应该用于在不同类型上使用相同的代码。

也许还有另一种方法可以做你真正想做的事。在实现策略或模板方法模式时,我在类似的情况下运行过,我希望大多数代码在一般情况下工作,但修改一些特定步骤。

在这种情况下,当您实际创建“模板方法”时,最好将自定义步骤作为接口注入您的类,甚至是专门化行为的 Func<> 对象。

当然,还有很多其他方法可以做到这一点,其中一些方法比其他方法更适用于特定问题

于 2013-10-21T10:53:06.490 回答
9

这只能使用动态绑定来完成,例如:

void Bar<T>(T value)
{
    dynamic parameter = value;
    Foo(parameter); 
}

请注意,动态调度使用实际运行时对象的实际运行时类型来进行方法调度,因此必须有一个对象。如果值为null,这将不起作用。

于 2013-10-21T11:02:58.103 回答
4

也许是这样的:

void Bar<T>()
{
   if(typeof(T) == typeof(Stream))
      Foo(default(T) as Stream);  //just to show the scenario
}
于 2013-10-21T10:32:41.567 回答
2

这不是一个“漂亮”的答案(实际上,因为这有点颠覆泛型的意图,所以很难在语言中找到一个漂亮的答案),但您也许可以通过字典对重载查找进行编码:

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>例如),它将不起作用 - 尽管您可能会遍历基本类型(但即便如此,它对接口等也没有很好的支持)。

考虑到所有因素,这种方法没有太多可推荐的。我可能会建议从不同的角度解决整个问题(即无需这样做)。

于 2013-10-21T10:50:21.497 回答
0

当类型 T 是引用类型时,default(T) 将始终返回 null,如果 T 是数值类型,则将返回零。

因此,它在任何时候都不会返回一个对象,您可以使用该对象调用您的 Foo 方法的重载版本。

所以简短的回答你不能这样做,你必须找出其他方法来调用重载方法。

于 2013-10-21T10:58:09.793 回答
0

我遇到了同样的问题,我知道的唯一解决方案是尝试投射它,直到我得到null. 然后,我将在编译时拥有正确的类型,并且编译器将知道要调用的正确重载。我找不到另一种方法来实现这种“运行时多态性”。

为避免使用字典或类似开关的解决方案(Marc 指出的可维护性差),只需调用Method((dynamic) o),DLR 将根据运行时类型调用正确的重载方法。

请记住:

1) 提供一个可能的最顶级类型的默认重载;

2) 注意在运行时解析类型期间的任何歧义(即两个独立的接口和一个使用两者的实现);

3) 处理null案例。

你可以在这里阅读更多关于它的信息。

希望我有所帮助。

于 2015-07-23T19:08:23.133 回答