在重载方法上使用泛型参数

本文关键字:泛型 参数 重载 方法 | 更新日期: 2023-09-27 18:34:15

为什么这个程序输出Generic Value而不是Hello world!

using System;
class Example
{
    public static void Print<T>(T value)
    {
        Console.WriteLine("Generic Value");
    }
    public static void Print(string value)
    {
        Console.WriteLine(value);
    }
    public static void GenericFunc<T>(T value)
    {
        Print(value);
    }
    static void Main()
    {
        GenericFunc("Hello world!");
    }
}

泛型方法参数是如何在 C# 的后台转换的?

在重载方法上使用泛型参数

重载解析仅在编译时完成。

由于GenericFunc<T>在编译时不知道Tstring还是其他东西,因此它只能使用"重载"Print<T>(T value)

使用 dynamic ,您可以将其更改为动态调度,并获得预期的行为:

Print((dynamic)value);

这使得重载解析在运行时发生,实际运行时类型为 value

简单问题的简单答案

另一个答案解释了泛型的绑定方式(编译时(。但它并没有回答OOP,良好实践,或者为什么你不应该首先编写这段代码。

哎呀

OOP 中的第一个 O 表示对象,没有只有静态方法。

责任

让我们将该方法的 Generic versin 视为负责打印一组不同可能类型的方法。 String类型是集合的一部分。因此,它应该由Print函数的通用版本管理。

public static void Print<T>(T value)
{
    Console.WriteLine(value.ToString());
}

然后你遇到了 ref 类型的空性问题。

    public static void Print<T>(T value) where T : class
    {
        if (value != null)
        {
            Console.WriteLine(value.ToString());
        }
    }
    public static void GenericFunc<T>(T value) where T : class
    {
        Print(value);
    }

对于那些知道为什么你不应该使用动态的人,除非在某些情况下(见我的安维尔(。

更清洁的 OOP 解决方案

现在想象一下,您要打印不同的对象。每个对象都应该负责知道如何显示它。首先,因为它通过不向外部世界泄露内部数据来简化数据的封装。其次,因为内部数据和打印函数之间有固有的耦合,所以两者都应该位于同一个地方:类内部。这就是ToString函数的目的。

让我们采取一些高度...

现在,我们可以想象它不是一个打印功能,而是其他东西。

我们

得到了一个在同一函数上具有重载的类层次结构(我们称之为 Foo (,以及这些类的实例集合,您必须为其调用函数Foo 。然后让我们让所有这些类实现IFooCallable接口:

public interface IFooCallable
{
    void Foo();
}

有点复杂...

好的,但是想象一下,没有通用的方法来处理所有这些类实例,因为存在非常不同的实例。

我们称之为访客模式。当您想要分析每个节点非常不同的某些对象树时,通常会使用它(如在 AST 中(。这是一种已知的模式,可以轻松地与您的团队共享可知的信息。

您得到了访客

public class Visitor : IVisitor
{
    public void Visit(Foo foo)
    {
        // do something with foo
    }
    public void Visit(Bar bar)
    {
        // do something with bar
    }
}

可访问

public class Foo : IVisitable
{
    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

此外,这种模式是可重用的(如果需要,你可以编写几个IVisitor的实现(。

我不买dynamic的东西。特别是当有更清洁、更快的替代品时。如果动态如此出色,为什么不写这个,那么;)

    public static void Print(dynamic value)
    {
        Console.WriteLine(value);            
    }
    public static void GenericFunc(dynamic value)
    {
        Print(value);
    }
    static void Main(dynamic[] args)
    {
        GenericFunc((dynamic)"Hello World");
    }