问题描述
我的问题的前提,用简单的英语:
The premise of my question, in plain english:
- 名为
Foo
的库依赖于名为Bar
的库 - Foo 中的类扩展 Bar 中的类
- Foo 定义了简单地传递给 Bar 的属性/方法
FooBar
应用程序仅依赖于 Foo
- A library named
Foo
depends on a library namedBar
- A class within Foo extends a class within Bar
- Foo defines properties/methods that simply pass-through to Bar
- An application,
FooBar
, depends only on Foo
考虑以下示例:
class Program
{
static void Main(string[] args)
{
Foo foo = Foo.Instance;
int id = foo.Id; // Compiler is happy
foo.DoWorkOnBar(); // Compiler is not happy
}
}
Foo定义如下
public class Foo : Bar
{
public new static Foo Instance { get => (Foo)Bar.Instance; }
public new int Id { get => Bar.Id; }
public void DoWorkOnBar()
{
Instance.DoWork();
}
}
条形定义如下
public class Bar
{
public static Bar Instance { get => new Bar(); }
public static int Id { get => 5; }
public void DoWork() { }
}
完全难倒我的部分:
没有对 Bar
库的引用
FooBar
可以检索Bar
提供的 ID(或至少它可以编译)FooBar
不能请求 Foo 完成最终由Bar
完成的工作
FooBar
can retrieve the ID that is provided byBar
(or at least it compiles)FooBar
cannot request Foo to do work that is ultimately accomplished byBar
与foo.DoWorkOnBar();
相关的编译器错误是
Bar"类型是在未引用的程序集中定义的.您必须添加对程序集 'Bar, Version 1.0.0.0, Culture=Neutral, PublicKeyToken=null' 的引用.
The type 'Bar' is defined in an assembly that is not referenced. You must add a reference to assembly 'Bar, Version 1.0.0.0, Culture=Neutral, PublicKeyToken=null' .
为什么编译器会出现差异?
如果没有 FooBar
添加对 Bar
的引用,我会假设这些操作都不会编译.
I would have assumed that neither of these operations would compile without FooBar
adding a reference to Bar
.
推荐答案
首先注意Foo.Id
和Foo.DoWorkOnBar的实现代码> 无关紧要;即使实现不访问
Bar
,编译器也会以不同的方式处理 foo.Id
和 foo.DoWorkOnBar()
:
First, note that the implementations of Foo.Id
and Foo.DoWorkOnBar
are irrelevant; the compiler treats foo.Id
and foo.DoWorkOnBar()
differently even if the implementations don’t access Bar
:
// In class Foo:
public new int Id => 0;
public void DoWorkOnBar() { }
foo.Id
编译成功而 foo.DoWorkOnBar()
编译失败的原因是编译器使用不同的逻辑¹来查找属性与方法.
The reason that foo.Id
compiles successfully but foo.DoWorkOnBar()
doesn’t is that the compiler uses different logic¹ to look up properties versus methods.
对于 foo.Id
,编译器首先在 Foo
中查找名为 Id
的成员.当编译器发现 Foo
有一个名为 Id
的属性时,编译器会停止搜索并且不再查看 Bar
.编译器可以执行此优化,因为派生类中的属性会隐藏基类中的所有同名成员,因此 foo.Id
将始终引用 Foo.Id
,无论在 Bar
中可能将哪些成员命名为 Id
.
For foo.Id
, the compiler first looks for a member named Id
in Foo
. When the compiler sees that Foo
has a property named Id
, the compiler stops the search and doesn’t bother looking at Bar
. The compiler can perform this optimization because a property in a derived class shadows all members with the same name in a base class, so foo.Id
will always refer to Foo.Id
, no matter what members might be named Id
in Bar
.
对于 foo.DoWorkOnBar()
,编译器首先在 Foo
中查找名为 DoWorkOnBar
的成员.当编译器发现 Foo
有一个名为 DoWorkOnBar
的方法时,编译器会继续在所有基类中搜索名为 DoWorkOnBar
的方法.编译器这样做是因为(与属性不同)方法可以被重载,并且编译器以与 C# 规范中描述的方式基本相同的方式实现²重载解析算法:
For foo.DoWorkOnBar()
, the compiler first looks for a member named DoWorkOnBar
in Foo
. When the compiler sees that Foo
has a method named DoWorkOnBar
, the compiler continues searching all base classes for methods named DoWorkOnBar
. The compiler does this because (unlike properties) methods can be overloaded, and the compiler implements² the overload resolution algorithm in essentially the same way it’s described in the C# specification:
- 从方法组"开始,该方法组由
Foo
中声明的DoWorkOnBar
及其基类 中声明的所有重载集合组成.李> - 将集合缩小到候选"方法(基本上,其参数与提供的参数兼容的方法).
- 删除任何被派生类中的候选方法遮蔽的候选方法.
- 选择剩余候选方法中的最佳".
- Start with the "method group" consisting of the set of all overloads of
DoWorkOnBar
declared inFoo
and its base classes. - Narrow the set down to "candidate" methods (basically, the methods whose parameters are compatible with the supplied arguments).
- Remove any candidate method that is shadowed by a candidate method in a more derived class.
- Choose the "best" of the remaining candidate methods.
第 1 步触发要求您添加对程序集 Bar
的引用.
Step 1 triggers the requirement for you to add a reference to assembly Bar
.
C# 编译器能否以不同的方式实现该算法?根据 C# 规范:
Could a C# compiler implement the algorithm differently? According to the C# specification:
上述解析规则的直观效果如下:要定位方法调用所调用的特定方法,从方法调用所指示的类型开始,沿着继承链进行,直到至少有一个适用、可访问、找到非覆盖方法声明.然后对在该类型中声明的一组适用的、可访问的、不可覆盖的方法执行类型推断和重载决策,并调用由此选择的方法.
The intuitive effect of the resolution rules described above is as follows: To locate the particular method invoked by a method invocation, start with the type indicated by the method invocation and proceed up the inheritance chain until at least one applicable, accessible, non-override method declaration is found. Then perform type inference and overload resolution on the set of applicable, accessible, non-override methods declared in that type and invoke the method thus selected.
所以在我看来答案是是":理论上,C# 编译器可以看到 Foo
声明了一个适用的 DoWorkOnBar
方法,而不必费心查看 条形码
.然而,对于 Roslyn 编译器,这将涉及对编译器的成员查找和重载解析代码进行重大重写——考虑到开发人员可以轻松地自行解决此错误,这可能不值得.
So it seems to me that the answer is "Yes": a C# compiler could theoretically see that Foo
declares an applicable DoWorkOnBar
method and not bother looking at Bar
. For the Roslyn compiler, however, this would involve a major rewrite of the compiler’s member lookup and overload resolution code—probably not worth the effort given how easily developers can resolve this error themselves.
TL;DR — 当您调用方法时,编译器需要您引用基类程序集,因为这是编译器的实现方式.
TL;DR — When you invoke a method, the compiler needs you to reference the base class assembly because that’s the way the compiler was implemented.
¹ 请参阅 Microsoft.CodeAnalysis.CSharp.Binder 类.
² 请参阅 Microsoft.CodeAnalysis.CSharp.OverloadResolution 类.
这篇关于为什么 C# 编译器可以“看到"?DLL中未引用的类的静态属性,但不是实例方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!