8

Why does the Roslyn compiler generate local functions with the internal access modifier (in IL, assembly) instead of private?

    private void M()
    {
        bool f = true;
        bool x1() => f;
        static bool x2() => true;
    }
    .method assembly hidebysig static
        bool '<M>g__x1|0_0' (
            valuetype C/'<>c__DisplayClass0_0'& ''
        ) cil managed { ... }

    .method assembly hidebysig static
        bool '<M>g__x2|0_1' () cil managed { ... }

sharplab.io

Docs say that it should be private: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/local-functions

Local functions are private methods of a type that are nested in another member.

Because all local functions are private, including an access modifier, such as the private keyword, generates compiler error CS0106

Boann
  • 48,794
  • 16
  • 117
  • 146
OwnageIsMagic
  • 1,949
  • 1
  • 16
  • 31
  • 2
    Side note: the spec you linked has no relation to the generated code - the spec is about adding access modifier *in the C# code* while what you are showing is decompiled code. – Alexei Levenkov Oct 19 '21 at 17:57
  • @AlexeiLevenkov in first line it says `Local functions are private methods of a type that are nested in another member.`. And private method of a type means it should be private. It's not a normative document, but it's better than nothing. – OwnageIsMagic Oct 19 '21 at 18:05
  • possibly an ignorant question: does it matter in practice? I think you can't access the local function outside of it's scope anyway. – bolov Oct 19 '21 at 19:05
  • @bolov 1. Semantics 2. Reflection 3. MSIL is one of .NET languages that can name those functions – OwnageIsMagic Oct 19 '21 at 19:12
  • Related question: https://stackoverflow.com/q/35839913/87698 (this is about the IL access modifier of lambda expressions). Apparently, the compiler teams sees access modifiers for auto-generated, non-user-callable methods as an undocumented implementation detail. Your question is a very good and interesting one, though, I'd be curious to learn more about the design rationale behind this decision. – Heinzi Oct 19 '21 at 20:19
  • @OwnageIsMagic Referring to MSIL/CIL as though it were a CLS language (like C# and VB.NET) is like saying x86 machine code is a high-level programming language. – Dai Oct 19 '21 at 20:31
  • Anyway, I _speculate_ that the function is `internal` so that the compiler can de-dupe the function's code in-case it sees the same function logic used elsewhere in the program (without needing to relocate it into another type). If it were `private` then the CLI's program-loader would complain about the program violating access-modifier rules. – Dai Oct 19 '21 at 20:32
  • The behavior is documented in a unit-test here: https://github.com/dotnet/roslyn/blob/bc3212cc70a2aa69648b80329b42e939831ed379/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenClosureLambdaTests.cs Look for `TestClosureMethodAccessibility()`: - however it doesn't explain **why** the compiler does it. – Dai Oct 19 '21 at 20:41
  • 2
    The actual spec on local functions is [here](https://learn.microsoft.com/dotnet/csharp/language-reference/proposals/csharp-7.0/local-functions). Note that it still says "TODO: WRITE SPEC" (in big capitals) and includes no details on the accessibility. Later versions of the spec refine what you can do with local functions, but do not improve on this point at all. C# is long, long overdue for a spec that is not "draft", even if building new features is a lot sexier than rolling them up into one formal spec, and even if the (informal) language description usually does a decent job. – Jeroen Mostert Oct 19 '21 at 20:47
  • @Dai can copy class definition and just change it's name. No code reused – OwnageIsMagic Oct 19 '21 at 23:37
  • @OwnageIsMagic I don't understand what you're referring to – Dai Oct 20 '21 at 00:24
  • @Dai https://sharplab.io/#v2:EYLgtghglgdgPgAQEwEYCwAoTyAEBhHAb0x1JwAcAnKANwgBcBTHBAFhwFkAKAShLOIYywnMAD2YgDY4AZjgC8OepQCujANz8ROWPRxiFOAByah20ROkAPFLwUA+WafMsUANgtScVpHfmPlNWdzBABWAB5dABodGHpHKwBmQy4rHgciUgQAdhwABnUcAF9g0iLMcoxcPCQiLQpqOiYWdm4+M1JBF3EvOUVAjXrhXX1DEyGyHutbdP8nCaz3T2tfWYDVQY6RMMi4mN0E5MVUtcyWXILi0uKKoA=== – OwnageIsMagic Oct 20 '21 at 09:39
  • 1
    @OwnageIsMagic _"2. Reflection"_ Reflection just circumvents the whole type and access safety features anyway, so it's a bit of an irrelevant case to point out. – Flater Oct 20 '21 at 14:55
  • 1
    @Flater It modifies non-private interface of the class, so it can interference with various legal use of reflection like RPC (remoting), and with `InternalsVisibleToAttribute` it becomes _<>_ interface. – OwnageIsMagic Oct 21 '21 at 00:17

1 Answers1

0

Looks like code reuse artifact. Roslyn probably uses same code to handle local functions and lambdas, but injects method definitions in different classes.

In case of lambda it injects lambda body to generated closure (DisplayClass) class and it should be internal to be referenced from calling function.

OwnageIsMagic
  • 1,949
  • 1
  • 16
  • 31