问题
I have doubt about these two aspects;
First one;
Test test = new Test();
result = test.DoWork(_param);
Second one;
result = new Test().DoWork(_param);
What happens if we dont assign the newly created instance to a variable and directly call the method?
I see some difference between two way on IL code.
This one below is IL output of first c# code
IL_0000: ldstr "job "
IL_0005: stloc.0
IL_0006: newobj instance void Works.Test::.ctor()
IL_000b: stloc.1
IL_000c: ldloc.1
IL_000d: ldloc.0
IL_000e: callvirt instance string Works.Test::DoWork(string)
IL_0013: pop
IL_0014: ret
And this one is the IL output of the second c# code
IL_0000: ldstr "job "
IL_0005: stloc.0
IL_0006: newobj instance void Works.Test::.ctor()
IL_000b: ldloc.0
IL_000c: call instance string Works.Test::DoWork(string)
IL_0011: pop
IL_0012: ret
Could you please inform me?
回答1:
The question is a little bit hard to find here but I think what you are asking is:
why does assigning the newly created reference to a variable cause the compiler to generate a callvirt, but calling the method directly generates a call?
You are very observant to notice this subtle difference.
Before we get to your question let's answer some other questions.
Should I trust that the compiler generates good code?
Generally yes. There are occasional code gen bugs but this is not one of them.
Is it legal to call a non-virtual method with callvirt?
Yes.
Is it legal to call a virtual method with call?
Yes, if you're trying to, say, call a base class method rather than an override in a derived class. But usually this does not happen.
Is the method being called in this example virtual or not?
It's not virtual.
Since the method is not virtual, it could be called with callvirt or call. Why does the compiler sometimes generate callvirt and sometimes generate call, when it could generate callvirt both times or call both times, consistently?
Now we get to the interesting part of your question.
There are two differences between call and callvirt.
call does not do virtual dispatch; callvirt looks up the right method in the virtual function dispatch table before it calls it. So therefore callvirt is about a nanosecond slower.
callvirt always checks if the receiver is null, regardless of whether the method called is virtual or not. call does not check to see if the receiver is null. It is legal to call a method with a null "this" via call.
Now perhaps you see where this is going.
Is C# required to crash with a null dereference exception whenever a call is made on a null reference receiver?
Yes. C# is required to crash when you invoke something with a null receiver. Therefore C# has the following choices when generating code to call a method:
- Case 1: Generate IL that checks for null, then generate a call.
- Case 2: Generate a callvirt.
- Case 3: Generate a call, but do not start with a null check.
Case 1 is just dumb. The IL is larger, and so takes up more room on disk, is slower to load, is slower to jit. It would be foolish to generate this code when callvirt automatically does a null check.
Case 2 is smart. The C# compiler generates callvirts so that the null check is done automatically.
Now what about case 3? Under what circumstances can C# skip the null check and generate a call? Only when:
- The call is a non-virtual method call, and
- C# already knows that the receiver is not null
But C# knows that in new Foo().Bar()
the receiver cannot be null because if it was, then the construction would have thrown an exception and we'd never get to the call!
The compiler is not smart enough to realize that the variable has only ever been assigned non-null values. So it generates a callvirt to be safe.
The compiler could be written to be that smart. The compiler already has to track the assignment state of variables for definite assignment checking. It could also track the "was assigned something that might be null" state, and then it would generate a call in both cases. But the compiler is not (yet) that smart.
回答2:
If you don't do anything else, there is no visible difference between the two ways of doing.
The only real difference is that in the first case, you assign your Test
instance to a variable, so you will be able to access it later/inspect it in the debugger. You cannot do that in the second case.
In terms of logic, and provided you don't do anything later with test
, there will be no difference except a really minor performance improvement in the second case (so tiny I can't think of any actual scenario where it might count).
来源:https://stackoverflow.com/questions/35015907/calling-method-of-non-assigned-class