如何基于封闭泛型类型调用方法重载?

时间:2013-10-21 10:23:35

标签: c# generics

假设我有三种方法:

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重载,因此我使用Bar关闭通用类型的方法MemoryStream

Bar<MemoryStream>();

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

有没有办法根据开放式通用类型MemoryStream调度方法调用T重载?

我不想使用Delegate.CreateDelegate或其他Reflection API。只是在C#语言的手段。我可能在语言本身内遗漏了一些东西。

使用值类型作为封闭泛型类型并使用静态方法尝试此方案。

6 个答案:

答案 0 :(得分:8)

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

C# generic interface specialization”已经回答了这个问题。简短的版本就是你不能这样做。您可以解决它强制运行时解析,但在这种情况下使用泛型是没有意义的。泛型应该用于在不同类型上使用相同的代码。

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

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

当然,还有很多其他方法可以做到这一点,其中一些方法比其他方法更能解决具体问题

答案 1 :(得分:7)

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

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

请注意,动态分派使用实际运行时对象的实际运行时类型来执行方法分派,因此必须有一个对象。如果值为null,则无效。

答案 2 :(得分:3)

也许是这样的:

void Bar<T>()
{
   if(typeof(T) == typeof(Stream))
      Foo(default(T) as Stream);  //just to show the scenario
}

答案 3 :(得分:2)

这不是一个“漂亮”的答案(事实上,因为这有点颠覆了泛型的意图,很难在语言中找到一个漂亮的答案),但你也许可以通过字典来编码过载查找: / p>

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>)则无效 - 尽管您可能会在基础上循环-types(但即便如此,它也没有很好的支持接口等)。

考虑到所有事情,这种方法没有太多建议。我可能会建议从不同的角度处理整个问题(即不需要这样做)。

答案 4 :(得分:0)

当类型T是引用类型时,

默认(T)将始终返回null,如果T是数值类型,则返回零。

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

如此简短的回答你无法做到这一点,你必须找到其他方法来调用重载方法。

答案 5 :(得分:0)

我遇到了同样的问题,我所知道的独特解决方案是试用它,直到我得到null之外的其他内容。然后,我在编译时会有正确的类型,编译器会知道调用正确的重载。我找不到另一种方法来实现这种'运行时多态'。

为了避免使用字典或类似交换机的解决方案(可靠性差,如Marc所指出的那样),只需调用Method((dynamic) o),DLR将根据运行时类型调用正确的重载方法。

请记住:

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

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

3)处理null案例。

您可以详细了解here

希望我帮助过。