lysergic-acid
lysergic-acid

Reputation: 20050

Why adding a return type to a void returning method causes a MissingMethodException

I have a .NET application that uses an assembly (.dll) that defines some method:

    public void DoSomething()
    {
        // Do work
    }

Suppose this method signature changes to include a string return type:

    public string DoSomething()
    {
        // Do work
        return "something";
    }

Why does the code that uses this method fails on a System.MissingMethodException ?

It seems to me, that at all call sites to this method, no use was made of the return value (since it did not exist before).

Why does this change break the code then?

Upvotes: 10

Views: 2640

Answers (5)

Eric Lippert
Eric Lippert

Reputation: 660463

The other answers which state that you've changed the signature of a method, and therefore must recompile the callers, are correct. I thought I might add some additional information about this bit of your question:

It seems to me, that at all call sites to this method, no use was made of the return value (since it did not exist before).

That is exactly right. Now, consider this question: how do you write code that makes no use of data? You seem to be labouring under the entirely false assumption that not using a value requires no code, but not using a value certainly does require code!

Suppose you have methods:

static int M1(int y) { return y + 1; }
static void M2(int z) { ... }

and you have a call

int x;
x = M1(123);

What happens at the IL level? the following:

  • Allocate space on the temporary pool for x.
  • Push 123 on the stack
  • Invoke M1.
  • Push 1 on the stack. Stack is now 1, 123
  • Add the top two things on the stack. This pops both and pushes the result. Stack is now 124.
  • Return to the caller
  • The stack is still 124.
  • Store the value on the stack into the temporary storage for x. This pops the stack, so the stack is now empty.

Suppose you now do:

M1(345);

What happens? Same thing:

  • Push 345 on the stack
  • Invoke M1.
  • Push 1 on the stack. Stack is now 1, 345
  • Add the top two things on the stack. This pops both and pushes the result. Stack is now 346.
  • Return to the caller
  • The stack is still 346.

But there is no instruction which stores the value on the stack anywhere, so we have to issue a pop instruction:

  • Pop the unused value off the stack.

Now suppose you call

M2(456);

What happens?

  • Push 456 on the stack
  • Invoke M2.
  • M2 does its thing. When it returns to the caller, the stack is empty because it is void returning.
  • The stack is now empty, so do not pop anything off it.

Now do you see why changing a method from void returning to value returning is a breaking change? Every caller now has to pop the unused value off the stack. Doing nothing with data still requires cleaning it up off the stack. You are misaligning the stack if you do not pop that value off; the CLR requires the stack to be empty at the beginning of every statement to ensure that this sort of misalignment does not happen.

Upvotes: 16

Alois Kraus
Alois Kraus

Reputation: 13545

Because you did a breaking api change. You did either change the method signature in a base class or an interface.

The callers are linked against this method. In IL a method reference is not only a reference to a type and its method with some index to the method but the callers method references does include the complete method signature.

This change is therefore fixable by a recompile of all assemblies that call this method but you get run time exceptions when you only recompile the changed assembly and hope the the using assemblies will magically pick up the changed method signature. This is not the case because the method reference does contain the complete method signature and defining type.

It did happen to me also. You are right that nobody could use the return type so this change is safe but you need to recompile all affected targets.

Upvotes: 1

adelphus
adelphus

Reputation: 10316

Because you've changed the method signature.

When the external code needs to locate a method, it needs to ensure what it is calling is correct. It stores some of this information as a signature at compile time - the signature information includes the return type (regardless of whether it is actually used anywhere).

As far as the CLR is concerned, a method with a void return type no longer exists - hence the MissingMethodException.

Upvotes: 1

Wiktor Zychla
Wiktor Zychla

Reputation: 48314

If there's no reflection involved but the linking is static (I assume this from the description) then the runtime will try to find a method using the exact signature. It's just how CIL's callvirt works.

It doesn't matter then whether or not the value is consumed - the runtime is unable to locate the void YourClass::DoSomething() and it doesn't even try to look for string YourClass::DoSomething().

If such change was possible then you could easily blow up the runtime by causing stack underflows/overflows.

Upvotes: 2

John Pick
John Pick

Reputation: 5650

The makers of C# decided it should be strict about method signatures.

Upvotes: 0

Related Questions