125

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

CodesInChaos
  • 106,488
  • 23
  • 218
  • 262
nunu
  • 3,184
  • 10
  • 43
  • 58

5 Answers5

156

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.

Lukazoid
  • 19,016
  • 3
  • 62
  • 85
  • 4
    +1. Can confirm - without a closure they are perfect candidates for `static` methods. – Simon Whitehead Sep 01 '14 at 10:48
  • 3
    I was just going to suggest that this question needed some expansion, I returned and there it was. Very informative - great to see what the compiler is doing under the covers. – Liath Sep 01 '14 at 10:51
  • 4
    @Liath `Ildasm` is really useful for understanding what is actually going on, I tend to use the `IL` tab of `LINQPad` to examine small samples. – Lukazoid Sep 01 '14 at 10:53
  • @Lukazoid Would you please tell us how did you get this compiler output? ILDASM won't give such output.. By any tool OR software? – nunu Sep 01 '14 at 10:54
  • 8
    @nunu In this example, I used the `IL` tab of [`LINQPad`](http://www.linqpad.net/) and inferred the C#. Some options to get the actual C# equivalent of the compiled output would be to use [`ILSpy`](http://ilspy.net/) or [`Reflector`](http://www.red-gate.com/products/dotnet-development/reflector/) on the compiled assembly, you would most likely need to disable some options which will attempt to display the lambdas and not the compiler generated classes. – Lukazoid Sep 01 '14 at 10:59
21

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.

Peter O.
  • 32,158
  • 14
  • 82
  • 96
  • 2
    +1 this behaviour should most likely not be relied upon, for the reasons stated; it is very much an implementation detail. – Lukazoid Sep 03 '14 at 10:45
19

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);
        }
    }
}

Delegate caching behavior changes in Roslyn talks about why this change was made.

Community
  • 1
  • 1
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
2

As of C# 6, this will always default to instance methods now, and will never be static (so actionMethod.Method.IsStatic will always be false).

See here: Why has a lambda with no capture changed from a static in C# 5 to an instance method in C# 6?

and here: Difference in CSC and Roslyn compiler's static lambda expression evaluation?

Community
  • 1
  • 1
James Wilkins
  • 6,836
  • 3
  • 48
  • 73
1

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.

Mel Padden
  • 983
  • 1
  • 9
  • 21