Why Local Functions generate IL different from Anonymous Methods and Lambda Expressions?

夙愿已清 提交于 2019-12-04 00:45:25

Anonymous methods stored in delegates may be called by any code, even code written in different languages, compiled years before C# 7 came out, and the CIL generated by the compiler needs to be valid for all possible uses. This means in your case, at the CIL level, the method must take no parameters.

Local methods can only be called by the same C# project (from the containing method, to be more specific), so the same compiler that compiles the method will also be handled to compile all calls to it. Such compatibility concerns as for anonymous methods therefore don't exist. Any CIL that produces the same effects will work here, so it makes sense to go for what's most efficient. In this case, the re-write by the compiler to enable the use of a value type instead of a reference type prevents unnecessary allocations.

The primary usage of anonymous methods (and lambda expressions) is the ability to pass them to a consuming method to specify a filter, predicate or whatever the method wants. They were not specifically suited for being called from the same method that defined them, and that ability was considered only later on, with the System.Action delegate.

On the other hand, local methods are the precise opposite - their primary purpose is to be called from the same method, like using a local variable.

Anonymous methods can be called from within the original method, but they were implemented in C# 2, and this specific usage wasn't taken into consideration.

So can local methods be passed to other methods, but their implementation details were designed in such a way that would be better for their purpose. After all, the difference you are observing is a simple optimisation. They could have optimised anonymous methods back in the day, but they didn't, and adding such optimisation now could potentially break existing programs (although we all know that relying on an implementation detail is a bad idea).

So let's see where the optimisation lies. The most important change is the struct instead of class. Well, an anonymous method needs a way to access the outside local variables even after the original method returns. This is called a closure, and the "DisplayClass" is what implements it. The main difference between C function pointers and C# delegates is that a delegate may optionally also carry a target object, simply used as this (the first argument internally). The method is bound to the target object, and the object is passed to the method every time the delegate is invoked (internally as the first argument, and the binding actually works even for static methods).

However, the target object is... well, object. You can bind a method to a value type, but it needs to be boxed before this. Now you can see why the DisplayClass needs to be a reference type in case of an anonymous method, because a value type will be a burden, not an optimisation.

Using a local method removes the need of binding a method to an object, and the consideration of passing the method to outside code. We can allocate the DisplayClass purely on the stack (as it should be for local data), presenting no burden on the GC. Now the developers had two choices - either make the LocalFunc instance and move it to the DisplayClass, or make it static and make the DisplayClass its first (ref) parameter. There is no difference in calling the method, so I think the choice was simply arbitrary. They could've decided otherwise, without any difference.

However, notice how quickly this optimisation is dropped once it could turn into a performance issue. A simple addition to your code, like Action a = DoIt; would immediately break the LocalFunc method. The implementation then immediately reverts to the one of the anonymous method, because the DisplayClass would need boxing etc.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!