110

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.

Smi
  • 13,850
  • 9
  • 56
  • 64
Mike Scott
  • 12,274
  • 8
  • 40
  • 53
  • 8
    +1 for very good question – cuongle Oct 11 '12 at 15:07
  • 40
    This is an Eric-Lippert-question. – Olivier Jacot-Descombes Oct 11 '12 at 15:09
  • 1
    @Olivier, I hope he's watching! :-) – Mike Scott Oct 11 '12 at 15:10
  • 3
    i'm pretty sure Jon Skeet would know what to do with this aswell tho ;) @OlivierJacot-Descombes – Thousand Oct 11 '12 at 15:11
  • @Felix K. - It compiles in Mono 2.10.9... – Piotr Zierhoffer Oct 11 '12 at 15:11
  • Which is the target framework? :) – Simon Edström Oct 11 '12 at 15:12
  • 2
    @Olivier, Jon Skeet probably wanted the code to compile, so the compiler allows it :-)) – Mike Scott Oct 11 '12 at 15:13
  • @JoshBerke, it compiles in 2012 under .Net4 and .Net4.5 for me. – Justin Harvey Oct 11 '12 at 15:17
  • Ok yea it compiles in 2012 as well:-) the project I was testing with won't compile cause of the dynamic type. Good question – JoshBerke Oct 11 '12 at 15:23
  • 5
    This is another example of why you shouldn't use `dynamic` unless you really need to. – Servy Oct 11 '12 at 15:30
  • Inspired by this question I have my own - http://stackoverflow.com/questions/12843251/how-to-invoke-static-method-with-dynamic-argument-when-a-non-static-better-fit Maybe it's Mono related, but in my opinion the example is even more hardcore ;] – Piotr Zierhoffer Oct 11 '12 at 15:35
  • Is this a feature to be refined in a future release or, is this a design choice? – Jodrell Oct 11 '12 at 15:43
  • C# compiler does little or no checking for dynamic binding during normal static compilation. It could definitely check for more "wrong" cases but that goes kind of against the nature of dynamic. – Marek Safar Oct 11 '12 at 18:17
  • 1
    @marek.safar IMO, the compiler should definitely attempt to determine at compile-time, code that will fail at runtime. Just because the **parameter** is dynamic doesn't mean that the compiler just has to throw away any idea of static checking. If someone can show me an example of code that would prevent such checking - like Eric Lippert would do - I'll accept the answer. – Mike Scott Oct 11 '12 at 22:34
  • Note: You can even change your call inside the `Foo` method from `Bar(x);` to `Example.Bar(x);`, i.e. use "explicit" static syntax in the call. The compiler is still happy (and of course it must still fail at runtime). – Jeppe Stig Nielsen Nov 12 '12 at 12:29
  • Similar question: http://stackoverflow.com/questions/8105879 – Rune Mar 28 '13 at 14:39
  • 1
    @OlivierJacot-Descombes: You guessed right, see the [accepted answer](https://stackoverflow.com/a/12843782/1016343) - it cites an **Eric Lippert blog post :-D** – Matt Jun 26 '17 at 16:09

3 Answers3

71

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.

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • 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 :-) – Mike Scott Oct 11 '12 at 16:17
  • 5
    @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**. – Jeppe Stig Nielsen Oct 11 '12 at 16:23
  • 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. – Mike Scott Oct 11 '12 at 16:30
  • 45
    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. – Chris Burrows Oct 11 '12 at 20:14
30

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.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 14
    but when you take out the instance Bar method, it no longer compiles. – Justin Harvey Oct 11 '12 at 15:20
  • 1
    @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 Gravell Oct 11 '12 at 15:23
  • @JustinHarvey, the compiler could check the number of parameters and it could check whether it was static or instance, then fail if there are no valid candidates. The more checking that can be done at compile time the better, IMO. – Jodrell Oct 11 '12 at 15:26
  • @Jodrell, agreed, hard to see why it doesn't do this more basic checking. – Justin Harvey Oct 11 '12 at 15:28
  • It's not a compiler issue; not exactly. The code you write is transformed into totally different code before the compilation into IL stage. The keyword `dynamic` is an abstraction for much much more verbose object code that doesn't actually contain a reference to the method `Bar`, except in string form. It's a limitation of the IDE, or possibly the pre-compilation component that generates the object code. – GregRos Oct 11 '12 at 15:56
  • But surely the point is that there AREN'T any other Bar() methods. There is only the instance method. So the call can be resolved at compile-time to an instance method call. There's no possibility to generate code which would run. – Mike Scott Oct 11 '12 at 16:08
  • @Greg, what you describe is an implementation detail but that doesn't mean that the compiler is correct. It's possible to deduce that there's no possibility that generated could would run, since there is no other method that could resolve in the call to Bar(). – Mike Scott Oct 11 '12 at 16:11
  • 1
    @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 Scott Oct 11 '12 at 16:21
  • @mike I would have to take a magnifying glass to the spec; frankly in surprised it errors at all - as I stated though, it looks like it is resolving only as far as the method-group as a typo-check. A method group can have one member. Since it is still deferring overload resolution, I don't see any problem. I would have expected the "no such method" case to be a warning though – Marc Gravell Oct 11 '12 at 16:28
  • 1
    @mike as for why defer to runtime: because that is what dynamic ***means*** – Marc Gravell Oct 11 '12 at 16:28
  • @MikeScott, I'm sorry, I'm just plain wrong. It's an error that is also made by `csc.exe`. If the method `Bar` doesn't exist, `csc.exe` doesn't compile the code. More so, because it is very easy to check whether the method `Bar` is static or not via a reflection flag, it's somewhat of a bug. You basically have a call to `GetMembers()` that doesn't specify that the `BindingFlags.Static` flag. – GregRos Oct 11 '12 at 16:41
  • Please see Jeppe Stig Nielsen's answer to this question and my comment. I can elaborate if necessary, but there seems to be some question about the extent to which the compiler will statically validate that a given dynamic call cannot succeed. Performing that analysis completely, aside from being impossible, is not a requirement of the language. The compiler makes a best effort and Jeppe has the correct reason for why this goes to runtime. – Chris Burrows Oct 11 '12 at 20:20
  • @ChrisBurrows could you explain how it's impossibly to validate statically that the only Bar() method in my example code is the instance method? Could you give an example of how there could be another Bar() method in a sealed class that has only one? – Mike Scott Oct 11 '12 at 22:27
  • 2
    @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. – Marc Gravell Oct 11 '12 at 22:51
  • 1
    @Mike, yes, the compiler _could_ detect this case. It could contain a branch that determines that the call is on a simple name, and that name corresponds to a method group in the class that has no static methods, and furthermore that the call occurs in the body of an instance method, and also that the class is sealed. It could do that. But nowhere in the language specification does it say that it must, and I hope you can appreciate that you could construct pretty much arbitrarily many examples such as this one. Look at the C# 4.0 spec section 7.5.4 to see what the compiler does check for you. – Chris Burrows Oct 12 '12 at 05:23
  • And in while you're at it, vote for Jeppe's answer because it is the correct one. – Chris Burrows Oct 12 '12 at 05:24
  • @ChrisBurrows if it *did* have such a branch, it would probably have to be a warning not an error; if the spec doesn't say anything about the scenario, then it would presumably be a compiler bug to not compile, **even if** it wasn't possible. For the same reasons that `if (true == null)` emits a warning, not an error. – Marc Gravell Oct 12 '12 at 07:16
  • @Marc, but if (true == null) does not blow chunks at run time. – Justin Harvey Oct 12 '12 at 07:56
  • 1
    @ChrisBurrows I've accepted Jeppe's answer. I also accept that the compiler can't check all the deep, unusual corner cases. But this is neither deep nor unusual - pretty simple code and the number of upvotes shows it's not at all obvious why the compiler accepts it. Most everyone who likes compiled languages states a main reason is catching errors at compile-time instead of blowing up at runtime. Anders has stated this many times and even gives that as one main reason for TypeScript. So maybe a future version of the compiler could check this case and issue a warning? – Mike Scott Oct 14 '12 at 12:17
  • @ChrisBurrows in addition, it's pretty common to promote a static method to an instance method when required. ReSharper offers to make methods static when they don't refer to any instance members. I discovered this static-method-calls-instance-method "quirk" when I did exactly this - made a static method into an instance method. Everything was fine until runtime, when the code blew up. IMHO, it's this kind of common error that compilers should catch. Though the language specs may not say so, the code contains an error nonetheless-it will ALWAYS fail. Perhaps the language spec could be changed? – Mike Scott Oct 14 '12 at 12:24
9

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++;
    }
}
oberfreak
  • 1,799
  • 13
  • 20
  • 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? – Mike Scott Oct 11 '12 at 15:18
  • 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. – Mike Scott Oct 11 '12 at 15:29
  • 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 Scott Oct 11 '12 at 16:07
  • @Mike can be != is; with dynamic, it is not required to do so – Marc Gravell Oct 11 '12 at 16:09
  • @MarcGravell, please clarify? – Mike Scott Oct 11 '12 at 16:32
  • @Mike the entire point of dynamic is that it defers resolution to runtime. Frankly, it even noticing the existance / non-existance of the method-group sounds like a free extra. – Marc Gravell Oct 11 '12 at 17:45
  • @MarcGravell I understand that, but in the example I posted, could you explain why it's not possible to determine at compile-time that the only Bar() method that can be called is the instance method? It's only the parameter that's dynamic. It's not a method call on a dynamic instance which clearly would have to be resolved at runtime. – Mike Scott Oct 11 '12 at 22:30
  • @mike the existence if "x" being dynamic makes the statement dynamic. The scenario is not very different. – Marc Gravell Oct 11 '12 at 22:52