เหตุใดนิพจน์ C # แลมบ์ดาบางตัวจึงรวบรวมวิธีการคง


122

ดังที่คุณเห็นในโค้ดด้านล่างฉันได้ประกาศให้Action<>ออบเจ็กต์เป็นตัวแปร

ใครช่วยแจ้งให้เราทราบได้ไหมว่าทำไมตัวแทนวิธีการดำเนินการนี้จึงทำงานเหมือนวิธีการคงที่

เหตุใดจึงส่งคืนtrueในรหัสต่อไปนี้

รหัส:

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

    Console.WriteLine(actionMethod.Method.IsStatic);

    Console.Read();
}

เอาท์พุท:

ตัวอย่างผลลัพธ์ของตัวอย่าง

คำตอบ:


153

เป็นไปได้มากที่สุดเนื่องจากไม่มีการปิดเช่น:

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

ออกจะนี้falseสำหรับwithClosureและสำหรับtruewithoutClosure

เมื่อคุณใช้นิพจน์แลมบ์ดาคอมไพเลอร์จะสร้างคลาสเล็ก ๆ ขึ้นมาเพื่อให้มีเมธอดของคุณซึ่งจะรวบรวมสิ่งต่อไปนี้ (การใช้งานจริงมักจะแตกต่างกันเล็กน้อย):

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

คุณสามารถเห็นAction<string>อินสแตนซ์ที่เป็นผลลัพธ์ชี้ไปที่เมธอดในคลาสที่สร้างขึ้นเหล่านี้


4
+1 สามารถยืนยันได้ - โดยไม่ต้องปิดพวกเขาเป็นตัวเลือกที่สมบูรณ์แบบสำหรับstaticวิธีการ
Simon Whitehead

3
ฉันแค่จะแนะนำว่าคำถามนี้ต้องการการขยายตัวฉันกลับมาและนั่นก็เป็นเช่นนั้น ให้ข้อมูลมาก - เยี่ยมมากที่ได้เห็นว่าคอมไพเลอร์กำลังทำอะไรอยู่ภายใต้ฝาครอบ
โกหก

4
@ เลีย ธIldasmมีประโยชน์มากสำหรับการทำความเข้าใจสิ่งที่เกิดขึ้นจริงฉันมักจะใช้ILแท็บLINQPadเพื่อตรวจสอบตัวอย่างขนาดเล็ก
Lukazoid

@Lukazoid คุณช่วยบอกเราได้ไหมว่าคุณได้รับเอาต์พุตคอมไพเลอร์นี้ได้อย่างไร? ILDASM จะไม่ให้ผลลัพธ์ดังกล่าว .. ด้วยเครื่องมือหรือซอฟต์แวร์ใด ๆ ?
nunu

8
@nunu ในตัวอย่างนี้ฉันใช้ILแท็บLINQPadและอนุมาน C # บางตัวเลือกเพื่อให้ได้ผลลัพธ์ที่คอมไพล์เทียบเท่า C # จริงจะใช้ILSpyหรือReflectorในแอสเซมบลีที่คอมไพล์แล้วคุณมักจะต้องปิดใช้งานตัวเลือกบางอย่างซึ่งจะพยายามแสดง lambdas ไม่ใช่คลาสที่สร้างคอมไพเลอร์
Lukazoid

20

"วิธีการดำเนินการ" เป็นแบบคงที่เป็นผลข้างเคียงของการนำไปใช้เท่านั้น นี่เป็นกรณีของวิธีการแบบไม่ระบุตัวตนที่ไม่มีตัวแปรที่จับได้ เนื่องจากไม่มีตัวแปรที่จับได้เมธอดจึงไม่มีข้อกำหนดอายุการใช้งานเพิ่มเติมนอกเหนือจากตัวแปรท้องถิ่นโดยทั่วไป หากอ้างอิงตัวแปรโลคัลอื่น ๆ อายุการใช้งานจะขยายไปถึงอายุการใช้งานของตัวแปรอื่น ๆ (ดูวินาที L.1.7 ตัวแปรโลคัลและวินาที N.15.5.1 ตัวแปรภายนอกที่ถูกจับในข้อกำหนด C # 5.0)

โปรดทราบว่าข้อกำหนด C # พูดถึงเฉพาะเมธอดที่ไม่ระบุชื่อที่ถูกแปลงเป็น "ต้นไม้นิพจน์" ไม่ใช่ "คลาสที่ไม่ระบุชื่อ" แม้ว่าแผนภูมินิพจน์สามารถแสดงเป็นคลาส C # เพิ่มเติมได้ตัวอย่างเช่นในคอมไพเลอร์ของ Microsoft การใช้งานนี้ไม่จำเป็นต้องใช้ (ตามที่รับทราบโดยวินาที M.5.3 ในข้อกำหนด C # 5.0) ดังนั้นจึงไม่ได้กำหนดว่าฟังก์ชันนิรนามเป็นแบบคงที่หรือไม่ ยิ่งไปกว่านั้นส่วน K.6 ยังเปิดกว้างเกี่ยวกับรายละเอียดของแผนภูมินิพจน์


2
+1 พฤติกรรมนี้ไม่ควรพึ่งพาด้วยเหตุผลที่ระบุไว้; มันเป็นรายละเอียดการใช้งานอย่างมาก
Lukazoid

18

พฤติกรรมการแคชของผู้รับมอบสิทธิ์มีการเปลี่ยนแปลงใน Roslyn ก่อนหน้านี้ตามที่ระบุไว้นิพจน์แลมด้าใด ๆ ที่ไม่ได้จับตัวแปรจะถูกรวบรวมเป็นstaticวิธีการที่ไซต์การโทร รอสลินเปลี่ยนพฤติกรรมนี้ ตอนนี้แลมด้าใด ๆ ที่จับตัวแปรหรือไม่ถูกเปลี่ยนเป็นคลาสดิสเพลย์:

ยกตัวอย่างนี้:

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

เอาต์พุตคอมไพเลอร์ดั้งเดิม:

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

โรสลิน:

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

มอบหมายการเปลี่ยนแปลงพฤติกรรมการแคชใน Roslynพูดถึงสาเหตุที่มีการเปลี่ยนแปลงนี้


2
ขอบคุณฉันสงสัยว่าทำไมวิธีการของ Func <int> f = () => 5 ของฉันถึงไม่คงที่
vc 74

2

สำหรับ C # 6 สิ่งนี้จะเป็นค่าเริ่มต้นของวิธีการอินสแตนซ์ในตอนนี้เสมอและจะไม่คงที่ ( actionMethod.Method.IsStaticจะเป็นเท็จเสมอ)

ดูที่นี่: เหตุใดแลมด้าที่ไม่มีการดักจับจึงเปลี่ยนจากแบบคงที่ใน C # 5 เป็นวิธีอินสแตนซ์ใน C # 6

และที่นี่: ความแตกต่างในการประเมินนิพจน์แลมด้าคงที่ของคอมไพเลอร์ CSC และ Roslyn?


1

วิธีนี้ไม่มีการปิดและยังอ้างอิงถึงวิธีการแบบคงที่ (Console.WriteLine) ดังนั้นฉันคาดว่ามันจะคงที่ เมธอดนี้จะประกาศประเภทที่ไม่ระบุตัวตนสำหรับการปิด แต่ในกรณีนี้ไม่จำเป็นต้องใช้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.