【问题标题】:How to call a method overload based on closed generic type?如何调用基于封闭泛型的方法重载?
【发布时间】:2013-10-21 10:23:35
【问题描述】:

假设我有三种方法:

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# 语言中。我可能在语言本身中遗漏了一些东西。

尝试将值类型作为封闭的泛型类型并使用静态方法来解决此问题。

【问题讨论】:

  • default(T) for MemoryStreamnull,所以调用了对象重载。我觉得还可以。
  • @StephaneDelcroix 如果我添加 where T : stream 然后 default(T) 仍然为空,但会调用其他方法。检查 msdn 的过载解决方案。编译器可以猜测表达式的 type,即使结果值为 null。示例 Foo((MemoryStream)null) 将使用 null 作为参数调用正确的重载。
  • 应该注意,“基于不同的T做不同的事情”与“泛型”的意图几乎完全相反,后者本质上是定义为“对任何不同的 T 应用相同的模式” - 泛型 本身 在这里没有帮助你应该不足为奇......
  • 您的建议本质上是一个虚拟通话。恐怕唯一的方法是实现自己的 vtable 模式或在 c# 中使用动态调度。
  • 顺便说一句,用new T() 替换default(T) 仍然调用object 重载。这不是关于空值,而是关于泛型和重载!

标签: c# generics


【解决方案1】:

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

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

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

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

当然,还有很多其他方法可以做到这一点,其中一些方法在特定问题上比其他方法效果更好

【讨论】:

  • 我喜欢你的回答和 Marc 的评论。它们为泛型的意图和使用提供了良好而深入的指导。从某种意义上说,我从来没有想过它们会在相同的数据上应用相同的功能,即使我已经在 msdn 和 C# 书籍中一遍又一遍地阅读了这一点。我一直认为它们是对具有开放类型的类型或方法进行参数化,这是对泛型原始意图的过度简化
【解决方案2】:

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

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

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

【讨论】:

  • 这无济于事,只需删除 MemoryStream 重载方法并自己查看即可。 :)
  • 或者只是调用 Bar(),在你的情况下它仍然会调用 MemoryStream 重载。
  • @AlagesanPalani 你试过了吗?你为什么不自己去看看,你可能会感到惊讶。要了解其工作原理,请参阅 blogs.msdn.com/b/samng/archive/2008/11/06/…blogs.u2u.net/kris/post/2010/11/30/Farewell-Visitor.aspx
  • +1。这肯定解决了我的问题。它可以调用正确的重载,而无需使用创建字典或使用案例逻辑等丑陋的技巧。这是 IMO 的最佳答案,很遗憾它没有投票。
  • 迄今为止的最佳答案。
【解决方案3】:

大概是这样的:

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

【讨论】:

  • 不知道为什么这被否决了;这不是一个很棒的 方法,但那是因为场景本身并不理想——它是你能得到的问题的最佳答案。这更多地反映了问题中的代码而不是答案。
  • 实际上,我什至认为这是最好的解决方案,因为很清楚会发生什么。如果重载的数量不会改变,那么这种方法是绝对有效的——考虑到这个问题。在其他情况下,我也会避免基于类型的流控制,但这正是这里所要求的。
【解决方案4】:

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

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 填充移动到静态构造函数/类型初始化器,并通过反射填充它。另请注意,这仅适用于 exact T - 如果有人使用意外类型(例如Bar&lt;NetworkStream&gt;),它将不起作用 - 尽管您可能会遍历基本类型(但即使那样,它也没有很好地支持接口等)。

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

【讨论】:

  • 不错的答案。一个问题Foo((object)default(T)) 为什么要投给object?在Bar签名处对泛型类型T没有约束,我想这在这里是多余的。
  • @IlyaIvanov 它对编译器来说是多余的;我认为它对读者很有用,以明确我们知道这总是调用 object 重载
  • 我明白了,谢谢。我也考虑过使用字典作为调度程序的这种情况,但它确实很注重可维护性。从来没有想过用反射填充它。无论如何,谢谢。
【解决方案5】:

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

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

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

【讨论】:

  • default(T) 返回null 的事实无关紧要。 null 确实没有运行时类型,但default(T) 肯定有编译时类型,即 T。这个 T 用于重载解析。
  • 而当 T 不受约束时,它将在重载结果算法中表现为object。重载解析在编译时起作用,并且在编译时不能考虑运行时类型。当使用dynamic 时,这显然会发生变化:然后在运行时发生重载结果,并且(不幸的是,对于 OP 发布的示例)没有考虑编译时类型。
【解决方案6】:

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

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

请记住:

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

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

3) 处理null 案例。

您可以阅读更多关于它的信息here

希望我有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多