Why do some C# lambda expressions compile to static methods?

C#.NetReflectionLambda

C# Problem Overview


As you can see in the code below, I have declared an Action<> object as a variable.

Would anybody please let me know why this action method delegate behaves like a static method?

Why does it return true in the following code?

Code:

public static void Main(string[] args)
{
    Action<string> actionMethod = s => { Console.WriteLine("My Name is " + s); };

    Console.WriteLine(actionMethod.Method.IsStatic);

    Console.Read();
}

Output:

example output of sample

C# Solutions


Solution 1 - C#

This is most likely because there are no closures, for example:

int age = 25;
Action<string> withClosure = s => Console.WriteLine("My name is {0} and I am {1} years old", s, age);
Action<string> withoutClosure = s => Console.WriteLine("My name is {0}", s);
Console.WriteLine(withClosure.Method.IsStatic);
Console.WriteLine(withoutClosure.Method.IsStatic);

This will output false for withClosure and true for withoutClosure.

When you use a lambda expression, the compiler creates a little class to contain your method, this would compile to something like the following (the actual implementation most likely varies slightly):

private class <Main>b__0
{
    public int age;
    public void withClosure(string s)
    {
        Console.WriteLine("My name is {0} and I am {1} years old", s, age)
    }
}

private static class <Main>b__1
{
    public static void withoutClosure(string s)
    {
        Console.WriteLine("My name is {0}", s)
    }
}

public static void Main()
{
    var b__0 = new <Main>b__0();
    b__0.age = 25;
    Action<string> withClosure = b__0.withClosure;
    Action<string> withoutClosure = <Main>b__1.withoutClosure;
    Console.WriteLine(withClosure.Method.IsStatic);
 	Console.WriteLine(withoutClosure.Method.IsStatic);
}

You can see the resulting Action<string> instances actually point to methods on these generated classes.

Solution 2 - C#

The "action method" is static only as a side effect of the implementation. This is a case of an anonymous method with no captured variables. Since there are no captured variables, the method has no additional lifetime requirements beyond those for local variables in general. If it did reference other local variables, its lifetime extends to the lifetime of those other variables (see sec. L.1.7, Local variables, and sec. N.15.5.1, Captured outer variables, in the C# 5.0 specification).

Note that the C# specification only talks about anonymous methods being converted to "expression trees", not "anonymous classes". While the expression tree could be represented as additional C# classes, for example, in the Microsoft compiler, this implementation is not required (as acknowledged by sec. M.5.3 in the C# 5.0 specification). Therefore, it is undefined whether the anonymous function is static or not. Moreover, section K.6 leaves much open as to the details of expression trees.

Solution 3 - C#

Delegate caching behavior was changed in Roslyn. Previously, as stated, any lambda expression which didn't capture variables was compiled into a static method at the call site. Roslyn changed this behavior. Now, any lambda, which captures variables or not, is transformed into a display class:

Given this example:

public class C
{
	public void M()
	{
		var x = 5;
		Action<int> action = y => Console.WriteLine(y);
	}
}

Native compiler output:

public class C
{
	[CompilerGenerated]
	private static Action<int> CS$<>9__CachedAnonymousMethodDelegate1;
	public void M()
	{
		if (C.CS$<>9__CachedAnonymousMethodDelegate1 == null)
		{
			C.CS$<>9__CachedAnonymousMethodDelegate1 = new Action<int>(C.<M>b__0);
		}
		Action<int> arg_1D_0 = C.CS$<>9__CachedAnonymousMethodDelegate1;
	}
	[CompilerGenerated]
	private static void <M>b__0(int y)
	{
		Console.WriteLine(y);
	}
}

Roslyn:

public class C
{
	[CompilerGenerated]
	private sealed class <>c__DisplayClass0
	{
		public static readonly C.<>c__DisplayClass0 CS$<>9__inst;
		public static Action<int> CS$<>9__CachedAnonymousMethodDelegate2;
		static <>c__DisplayClass0()
		{
			// Note: this type is marked as 'beforefieldinit'.
			C.<>c__DisplayClass0.CS$<>9__inst = new C.<>c__DisplayClass0();
		}
		internal void <M>b__1(int y)
		{
			Console.WriteLine(y);
		}
	}
	public void M()
	{
		Action<int> arg_22_0;
		if (arg_22_0 = C.
		               <>c__DisplayClass0.CS$<>9__CachedAnonymousMethodDelegate2 == null)
		{
			C.<>c__DisplayClass0.CS$<>9__CachedAnonymousMethodDelegate2 =
		  new Action<int>(C.<>c__DisplayClass0.CS$<>9__inst.<M>b__1);
		}
	}
}

https://stackoverflow.com/questions/30897647/delegate-caching-behavior-changes-in-roslyn talks about why this change was made.

Solution 4 - C#

Solution 5 - C#

The method has no closures and also references a static method itself (Console.WriteLine), so I would expect it to be static. The method will declare an enclosing anonymous type for a closure, but in this instance it is not required.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionnunuView Question on Stackoverflow
Solution 1 - C#LukazoidView Answer on Stackoverflow
Solution 2 - C#Peter O.View Answer on Stackoverflow
Solution 3 - C#Yuval ItzchakovView Answer on Stackoverflow
Solution 4 - C#James WilkinsView Answer on Stackoverflow
Solution 5 - C#Mel PaddenView Answer on Stackoverflow