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

本文关键字:调用 方法 重载 泛型类型 何基于 | 更新日期: 2023-09-27 18:36:08

假设我有三种方法:

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 为

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

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

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

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

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

请记住:

1) 提供具有最顶级类型的默认重载;

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

3)处理null案件。

您可以在此处阅读有关它的更多信息。

希望我有所帮助。