为什么C#编译器可以“看到”消息?静态属性,而不是没有引用的DLL中的类的实例方法? [英] Why can the C# compiler "see" static properties, but not instance methods, of a class in a DLL that is not referenced?

查看:100
本文介绍了为什么C#编译器可以“看到”消息?静态属性,而不是没有引用的DLL中的类的实例方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的问题的前提,用简单的英语:




  • 一个名为 Foo 依赖于名为 Bar

  • Foo中的类的库扩展了Bar

  • Foo定义了简单地传递到Bar的属性/方法

  • FooBar 应用程序仅依赖于 Foo



请考虑以下示例:

  class程序
{
static void Main(string [] args)
{
Foo foo = Foo.Instance;

int id = foo.Id; //编译器很高兴
foo.DoWorkOnBar(); //编译器不满意
}
}

Foo 的定义如下

 公共类Foo:Bar 
{
public new static Foo Instance {get => (Foo)Bar.Instance; }

public new int ID {get => Bar.Id; }

public void DoWorkOnBar()
{
Instance.DoWork();
}
}

Bar

 公共类Bar 
{
public static Bar Instance {get =>新Bar(); }

public static int Id {get => 5; }

public void DoWork(){}
}

完全困扰我的部分是:



没有引用 Bar




  • FooBar 可以检索提供的ID由 Bar (或至少编译)

  • FooBar 不能要求Foo做最终由 Bar



foo.DoWorkOnBar(); 相关的编译器错误是


The Bar类型是在未引用的程序集中定义的。您必须添加对程序集 Bar,版本1.0.0.0,Culture = Neutral,PublicKeyToken = null的引用。


为什么在编译器中会出现差异?



我会假设在没有 FooBar的情况下,这些操作都不会编译添加对 Bar 的引用。

解决方案

首先,请注意, Foo.Id Foo.DoWorkOnBar 实现不相关;编译器对 foo.Id foo.DoWorkOnBar()的处理方式不同,即使实现不访问 Bar

  //在课堂Foo:
public new int ID => 0;
public void DoWorkOnBar(){}

foo的原因.Id 编译成功,但 foo.DoWorkOnBar()并不是编译器使用不同的逻辑¹查找属性和方法。



对于 foo.Id ,编译器首先查找名为 Id Foo 中的c>。当编译器看到 Foo 具有名为 Id 的属性时,编译器将停止搜索并且不会打扰酒吧。编译器可以执行此优化,因为派生类中的属性会遮盖基类中所有具有相同名称的成员,因此 foo.Id 将始终引用 Foo.Id ,无论在 Bar 中将哪些成员命名为 Id



对于 foo.DoWorkOnBar(),编译器首先查找名为 DoWorkOnBar的成员中的 Foo 。当编译器发现 Foo 有一个名为 DoWorkOnBar 的方法时,编译器会继续在所有基类中搜索名为<$的方法。 c $ c> DoWorkOnBar 。编译器之所以这样做是因为(不同于属性)方法可以被重载,并且编译器以与C#规范中所述的基本相同的方式实现重载解决算法:


  1. 从方法组开始,该方法组由在 Foo DoWorkOnBar 的所有重载的集合组成> 及其基类

  2. 将设置缩小为候选方法(基本上是其参数与提供的参数兼容的方法)。

  3. 删除派生类较多的候选方法。

  4. 在其余候选方法中选择最佳。
  5. li>

步骤1触发要求您添加对程序集 Bar 的引用。 / p>

C#编译器能否以不同的方式实现算法?根据C#规范:


上述解析规则的直观效果如下:查找由方法调用的特定方法调用,从方法调用指示的类型开始,并继续进行继承链,直到找到至少一个适用的,可访问的,非重写的方法声明。然后对在该类型中声明的一组适用的,可访问的,非重写方法执行类型推断和重载解析,并调用由此选择的方法。


在我看来,答案是是:C#编译器理论上可以看到 Foo 声明了适用的 DoWorkOnBar 方法,而不必费心看着 Bar 。但是,对于Roslyn编译器,这将涉及对编译器的成员查找和重载解析代码的重大重写-考虑到开发人员自己能够轻松解决此错误,可能不值得付出努力。






TL; DR —调用方法时,编译器需要您引用基类程序集,因为这是编译器的实现方式。






¹参见 Microsoft.CodeAnalysis.CSharp.Binder类



²请参见 Micros oft.CodeAnalysis.CSharp.OverloadResolution类


The premise of my question, in plain english:

  • A library named Foo depends on a library named Bar
  • 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

Consider the following sample:

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 is defined as follows

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();
    }
}

Bar is defined as follows

public class Bar
{
    public static Bar Instance { get => new Bar(); }

    public static int Id { get => 5; }

    public void DoWork() { }
}

The part that is completely stumping me:

Without a reference to the Bar library

  • FooBar can retrieve the ID that is provided by Bar (or at least it compiles)
  • FooBar cannot request Foo to do work that is ultimately accomplished by Bar

The compiler error associated with foo.DoWorkOnBar(); is

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' .

Why does there appear to be a disparity in the compiler?

I would have assumed that neither of these operations would compile without FooBar adding a reference to Bar.

解决方案

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() { }

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.

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.

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:

  1. Start with the "method group" consisting of the set of all overloads of DoWorkOnBar declared in Foo and its base classes.
  2. Narrow the set down to "candidate" methods (basically, the methods whose parameters are compatible with the supplied arguments).
  3. Remove any candidate method that is shadowed by a candidate method in a more derived class.
  4. Choose the "best" of the remaining candidate methods.

Step 1 triggers the requirement for you to add a reference to assembly Bar.

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.

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 — When you invoke a method, the compiler needs you to reference the base class assembly because that’s the way the compiler was implemented.


¹ See the LookupMembersInClass method of the Microsoft.CodeAnalysis.CSharp.Binder class.

² See the PerformMemberOverloadResolution method of the Microsoft.CodeAnalysis.CSharp.OverloadResolution class.

这篇关于为什么C#编译器可以“看到”消息?静态属性,而不是没有引用的DLL中的类的实例方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆