ChatGPT解决这个技术问题 Extra ChatGPT

Why does the C# compiler not fault code where a static method calls an instance method?

The following code has a static method, Foo(), calling an instance method, Bar():

public sealed class Example
{
    int count;

    public static void Foo( dynamic x )
    {
        Bar(x);
    }

    void Bar( dynamic x )
    {
        count++;
    }
}

It compiles without error* but generates a runtime binder exception at runtime. Removing the dynamic parameter to these methods causes a compiler error, as expected.

So why does having a dynamic parameter allow the code to be compiled? ReSharper doesn't show it as an error either.

Edit 1: *in Visual Studio 2008

Edit 2: added sealed since it's possible that a subclass could contain a static Bar(...) method. Even the sealed version compiles when it's not possible that any method other than the instance method could be called at runtime.

+1 for very good question
This is an Eric-Lippert-question.
i'm pretty sure Jon Skeet would know what to do with this aswell tho ;) @OlivierJacot-Descombes
@Olivier, Jon Skeet probably wanted the code to compile, so the compiler allows it :-))
This is another example of why you shouldn't use dynamic unless you really need to.

J
Jeppe Stig Nielsen

UPDATE: Below answer was written in 2012, before the introduction of C# 7.3 (May 2018). In What's new in C# 7.3, the section Improved overload candidates, item 1, it is explained how the overload resolution rules have changed so that non-static overloads are discarded early. So the below answer (and this entire question) has mostly only historical interest by now!

(Pre C# 7.3:)

For some reason, overload resolution always finds the best match before checking for static versus non-static. Please try this code with all static types:

class SillyStuff
{
  static void SameName(object o) { }
  void SameName(string s) { }

  public static void Test()
  {
    SameName("Hi mom");
  }
}

This will not compile because the best overload is the one taking a string. But hey, that's an instance method, so compiler complains (instead of taking the second-best overload).

Addition: So I think the explanation of the dynamic example of the Original Question is that, in order to be consistent, when types are dynamic we also first find the best overload (checking only parameter number and parameter types etc., not static vs. non-static), and only then check for static. But that means that the static check has to wait until runtime. Hence the observed behavior.

Late addition: Some background on why they chose to do things this funny order can be inferred from this blog post by Eric Lippert.


There are no overloads in the original question. The answers showing a static overload are not relevant. It's not valid to answer "well if you wrote this ... " since I didn't write that :-)
@MikeScott I just try to convince you that the overload resolution in C# always goes like this: (1) Find best match disregarding static/non-static. (2) Now we know what overload to use, then check for static. Because of this, when dynamic was introduced in the language, I think the designers of C# said: "We won't consider (2) compile-time when it's a dynamic expression." So my purpose here is to come up with an idea of why they chose not to check for static versus instance until runtime. I would say, this check happens at binding-time.
fair enough but it still doesn't explain why in this case that the compiler can't resolve the call to the instance method. In other words, the way that the compiler does the resolution is simplistic - it doesn't recognise the simple case like my example where there's no possibility that it can't resolve the call. The irony is: by having a single Bar() method with a dynamic parameter, the compiler then ignores that single Bar() method.
I wrote this part of the C# compiler, and Jeppe is right. Please vote this up. Overload resolution happens prior to checking whether a given method is a static or an instance method, and in this case we defer overload resolution to runtime and therefore also the static/instance check until runtime as well. Additionally, the compiler puts in a "best effort" to statically find dynamic errors which is absolutely not comprehensive.
M
Marc Gravell

Foo has a parameter "x" that is dynamic, which means Bar(x) is a dynamic expression.

It would be perfectly possible for Example to have methods like:

static Bar(SomeType obj)

In which case the correct method would be resolved, so the statement Bar(x) is perfectly valid. The fact that there is an instance method Bar(x) is irrelevent and not even considered: by definition, since Bar(x) is a dynamic expression, we have deferred resolution to runtime.


but when you take out the instance Bar method, it no longer compiles.
@Justin interesting - a warning? Or an error? Either way, it may be validating only as far as the method-group, leaving full overload resolution to runtime.
@Marc, since there isn't another Bar() method, you're not answering the question. Can you explain this given that there's only one Bar() method with no overloads? Why defer to runtime when there's no way any other method could be called? Or is there? Note: I've edited the code to seal the class, which still compiles.
@mike as for why defer to runtime: because that is what dynamic means
@Mike impossible is not the point; what is important is whether it is required. The entire point with dynamic is that it is not the job of the compiler.
o
oberfreak

The "dynamic" expression will be bound during runtime, so if you define a static method with the correct signature or a instance method the compiler will not check it.

The "right" method will be determined during runtime. The compiler can not know if there is a valid method there during runtime.

The "dynamic" keyword is defined for dynamic and script languages, where the Method can be defined at any time, even during runtime. Crazy stuff

Here a sample which handles ints but no strings, because of the method is on the instance.

class Program {
    static void Main(string[] args) {
        Example.Foo(1234);
        Example.Foo("1234");
    }
}
public class Example {
    int count;

    public static void Foo(dynamic x) {
        Bar(x);
    }

    public static void Bar(int a) {
        Console.WriteLine(a);
    }

    void Bar(dynamic x) {
        count++;
    }
}

You can add a method to handle all "wrong" calls, which could not be handled

public class Example {
    int count;

    public static void Foo(dynamic x) {
        Bar(x);
    }

    public static void Bar<T>(T a) {
        Console.WriteLine("Error handling:" + a);
    }

    public static void Bar(int a) {
        Console.WriteLine(a);
    }

    void Bar(dynamic x) {
        count++;
    }
}

Shouldn't the calling code in your example be Example.Bar(...) instead of Example.Foo(...)? Isn't Foo() irrelevant in your example? I don't really understand your example. Why would adding the static generic method cause a problem? Could you edit your answer to include that method instead of giving it as an option?
but the example I posted has only a single instance method and no overloads so at compile-time you know that there are no possible static methods that could be resolved. Only if you add at least one does the situation change and the code is valid.
But this example still has more than one Bar() method. My example has only one method. So there's no possibility of calling any static Bar() method. The call can be resolved at compile-time.
@Mike can be != is; with dynamic, it is not required to do so
@MarcGravell, please clarify?