具有继承和泛型约束的两步方法解析
在使用继承的泛型约束时,我遇到了一些非常令人惊讶的事情。我有一个重载方法 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
根据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.
通用 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 onAnimal
. Another option would be usingdynamic
.