Method to call an unassigned class

I have doubts about these two aspects:

First,

Test test = new Test(); result = test.DoWork(_param); 

Second,

  result = new Test().DoWork(_param); 

What happens if we do not assign the newly created instance to the variable and call the method directly?

I see the difference between the two methods in IL code.


Below is the result of IL 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 is one 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 tell me?

+5
source share
2 answers

The question is a bit hard to find here, but I think you are asking:

why does assigning a newly created variable reference cause the compiler to generate a callvirt, but the method call directly calls the call?

You are very observant to notice this subtle difference.

Before we get to your question, let's answer other questions.

Should I trust that the compiler generates good code?

Actually, yes. There are random code errors, but this is not one of them.

Is it possible to call a non-virtual method using callvirt?

Yes.

Is it possible to call a virtual method with a call?

Yes, if you are trying to, say, call a method of a base class, rather than overriding in a derived class. But usually this does not happen.

Is the method called in this example virtual or not?

This is not virtual.

Since the method is not virtual, it can be called using callvirt or call. Why does the compiler sometimes generate a callvirt and sometimes generate a call when it can generate a callvirt both times or call both times sequentially?

Now we will move on to the interesting part of your question.

There are two differences between calling and callvirt.

  • The call does not perform virtual sending; callvirt looks for the correct method in the virtual function dispatch table before it calls it. So callvirt is about a nanosecond slower.

  • callvirt always checks if the receiver is null, regardless of whether the called method is virtual or not. the call does not check if the receiver is null. Legally call a method with a null "this" through a call.

Now, perhaps you see where this is going.

Is C # required to bump with the null dereferencing exception whenever the call is made to zero of the reference receiver?

Yes C # needs to fail on a call with a null receiver. Therefore, C # has the following options when creating code to call a method:

  • Case 1: Generate an IL that checks for null and then generates a call.
  • Case 2: Create a callvirt.
  • Case 3: generate a call, but do not start with a zero check.

Case 1 is just dumb. IL is larger and therefore takes up more disk space, slower loading, slower jit. It would be foolish to generate this code when callvirt automatically performs a null check.

Case 2 is clever. The C # compiler generates callvirts so that a null check is done automatically.

What about case 3? Under what circumstances does C # skip a null check and generate a call? Only when:

  • Call - a call is not a virtual method, but
  • C # already knows that the receiver is not null

But C # knows that in new Foo().Bar() receiver cannot be null, because if that were the case, the build would throw an exception and we would never get to the call!

The compiler is not smart enough to understand that only nonzero values ​​are assigned to a variable. In this way, it creates a secure feedback.

The compiler can be written so smart. The compiler should already track the state of the variable assignment for a specific assignment check. It can also track that β€œsomething has been assigned that may be null,” and then it will generate a call in both cases. But the compiler is not smart yet.

+20
source

If you do nothing, there is no obvious difference between the two methods.

The only real difference is that in the first case you assign your instance of Test variable, so you can access it later / check it in the debugger. You cannot do this in the second case.

In terms of logic and assuming you don't do anything later with Test , there will be no difference except for a really minor performance improvement in the second case (so small that I can't think of any real scenario, it can count).

0
source

Source: https://habr.com/ru/post/1241501/


All Articles