具有继承和泛型约束的两步方法解析

发布于 2024-12-17 07:00:20 字数 1853 浏览 0 评论 0原文

在使用继承的泛型约束时,我遇到了一些非常令人惊讶的事情。我有一个重载方法 Foo ,其参数不同 - 基类实例或派生类实例。在这两种情况下,通常只是将实例传递给第二对重载方法 - Bar

当我使用基类实例调用 Foo 时,会调用基类的 Bar 重载。当我使用派生类实例调用 Foo 时,会调用派生类的 Bar 重载。这是明确且预期的。

但是,当我尝试将 Foo 方法合并到使用泛型和约束的单个 GenericFoo 时,方法的解析方式有所不同 - T 已正确解析,但只有 GenericFoo 的基类重载code>Bar 被调用。

public class Animal { }
public class Cat : Animal { }

public class AnimalProcessor
{
    public static void Foo(Animal obj)
    {
        Console.WriteLine("Foo(Animal)");
        Bar(obj);
    }

    public static void Foo(Cat obj)
    {
        Console.WriteLine("Foo(Cat)");
        Bar(obj);
    }

    // new generic method to replace the two above
    public static void GenericFoo<T>(T obj)
        where T : Animal
    {
        Console.WriteLine("Foo(generic)");
        Bar(obj);
    }

    public static void Bar(Animal obj)
    {
        Console.WriteLine("Bar(Animal)");
    }

    public static void Bar(Cat obj)
    {
        Console.WriteLine("Bar(Cat)");
    }
}

测试代码 - 前两个案例用于非泛型旧方法,最后两个案例用于新泛型方法。

Console.WriteLine("Animal()");
AnimalProcessor.Foo(new Animal());
Console.WriteLine();

Console.WriteLine("Cat()"); 
AnimalProcessor.Foo(new Cat());
Console.WriteLine();

Console.WriteLine("Animal()");
AnimalProcessor.GenericFoo(new Animal());
Console.WriteLine();

Console.WriteLine("Cat()"); 
AnimalProcessor.GenericFoo(new Cat());
Console.ReadLine();

结果 - 请注意 Bar 中解析的类型的差异:

Animal()
Foo(Animal)
Bar(Animal)

Cat()
Foo(Cat)
Bar(Cat)

Animal()
Foo(generic)
Bar(Animal)

Cat()
Foo(generic)
Bar(Animal)

看起来编译器将来自 GenericFoo 的所有调用绑定到最不具体的重载,即使所有更具体的类型调用在编译时是已知的。为什么会这样,这种行为的原因是什么?规范的哪一部分定义了这一点?

I've encountered something quite surprising when using generic constraints with inheritance. I have an overloaded methods Foo that differ with parameter - either base or derived class instance. In both cases it's generally just passing the instance to the second pair of overloaded methods - Bar.

When I call Foo with base class instance, Bar overload for the base class is called. When I call Foo with derived class instance, Bar overload for the derived class is called. This is clear and expected.

But when I tried to merge Foo methods into single one GenericFoo that use generics and constraints, methods are resolved differently - T is resolved correctly, but only base-class overload of Bar is called.

public class Animal { }
public class Cat : Animal { }

public class AnimalProcessor
{
    public static void Foo(Animal obj)
    {
        Console.WriteLine("Foo(Animal)");
        Bar(obj);
    }

    public static void Foo(Cat obj)
    {
        Console.WriteLine("Foo(Cat)");
        Bar(obj);
    }

    // new generic method to replace the two above
    public static void GenericFoo<T>(T obj)
        where T : Animal
    {
        Console.WriteLine("Foo(generic)");
        Bar(obj);
    }

    public static void Bar(Animal obj)
    {
        Console.WriteLine("Bar(Animal)");
    }

    public static void Bar(Cat obj)
    {
        Console.WriteLine("Bar(Cat)");
    }
}

Testing code - two first cases for non-generic old methods, two last for new generic method.

Console.WriteLine("Animal()");
AnimalProcessor.Foo(new Animal());
Console.WriteLine();

Console.WriteLine("Cat()"); 
AnimalProcessor.Foo(new Cat());
Console.WriteLine();

Console.WriteLine("Animal()");
AnimalProcessor.GenericFoo(new Animal());
Console.WriteLine();

Console.WriteLine("Cat()"); 
AnimalProcessor.GenericFoo(new Cat());
Console.ReadLine();

And the result - note the difference in type resolved in Bar:

Animal()
Foo(Animal)
Bar(Animal)

Cat()
Foo(Cat)
Bar(Cat)

Animal()
Foo(generic)
Bar(Animal)

Cat()
Foo(generic)
Bar(Animal)

It looks like the compiler binds all calls from GenericFoo to the least specific overload, even if all more specific-typed calls are known at compile time. Why is that, what is the reason for such behaviour? Which part of specs defines this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

池木 2024-12-24 07:00:20

根据OP的要求,评论重新发布为答案:

泛型不是模板。通用方法编译一次,其行为适用于“最通用”的情况(在本例中为 Animal)。这与 C++ 样式模板不同,在 C++ 样式模板中,模板是按类型针对每个专业化单独编译的。

Per OP's request, comment re-posted as answer:

Generics are not templates. Generic methods are compiled once and their behavior is for the 'most generic' case (in this case, Animal.) This is different from C++ style templating, where the template is compiled separately for each specialization by type.

溺孤伤于心 2024-12-24 07:00:20

通用 C# 方法被编译为通用 IL 方法。在 IL 中,您必须明确指定要调用哪个重载。所以编译器没有简单的方法可以做到这一点。 (有一种复杂的方法:运行一个小型编译器,动态选择此时的重载,这就是 dynamic 所做的。)

如果您想要这种行为,一个选择是使 Bar () Animal 上的虚拟方法。另一种选择是使用动态

The generic C# method is compiled into a generic IL method. And in IL, you have to explicitly specify which overload you are calling. So there is no simple way how the compiler could have done this. (There is complicated way: run a mini-compiler that chooses the overload at this point dynamically, which is what dynamic does.)

If you want this behavior, one option would be to make Bar() a virtual method on Animal. Another option would be using dynamic.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文