Why can the C# compiler “see” static properties, but not instance methods, of a class in a DLL that is not referenced?

后端 未结 1 722
佛祖请我去吃肉
佛祖请我去吃肉 2021-02-19 20:24

The premise of my question, in plain english:

  • A library named Foo depends on a library named Bar
  • A class within Foo extends a cl
1条回答
  •  醉酒成梦
    2021-02-19 20:59

    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.

    0 讨论(0)
提交回复
热议问题