ไม่สามารถใช้พารามิเตอร์ ref หรือ out ในการแสดงออกแลมบ์ดา


173

ทำไมคุณไม่สามารถใช้พารามิเตอร์ ref หรือ out ในนิพจน์แลมบ์ดา

ฉันเจอข้อผิดพลาดในวันนี้และพบวิธีแก้ปัญหา แต่ฉันก็ยังสงสัยว่าทำไมนี่เป็นข้อผิดพลาดแบบรวบรวมเวลา

CS1628 : ไม่สามารถใช้ในพารามิเตอร์ ref หรือ out 'พารามิเตอร์' ภายในวิธีที่ไม่ระบุชื่อนิพจน์แลมบ์ดาหรือนิพจน์แบบสอบถาม

นี่คือตัวอย่างง่ายๆ:

private void Foo()
{
    int value;
    Bar(out value);
}

private void Bar(out int value)
{
    value = 3;
    int[] array = { 1, 2, 3, 4, 5 };
    int newValue = array.Where(a => a == value).First();
}

มันเกี่ยวกับตัววนซ้ำ แต่มีเหตุผลมากมายในโพสต์นี้ (โดย Eric Lippert & mdash; เขาอยู่ในทีมออกแบบภาษาหลังจากทั้งหมด) นำไปใช้กับ lambdas: < blogs.msdn.com/ericlippert/archive/2009/07/13 / … >
Joel Coehoorn

17
ฉันขอถามได้ไหมว่าวิธีแก้ปัญหาที่คุณพบคืออะไร
Beatles1692

3
คุณสามารถประกาศตัวแปรท้องถิ่นเฉพาะและทำงานกับสิ่งนั้นและกำหนดผลลัพธ์ให้กับค่าหลังจากนั้น ... เพิ่ม var tempValue = value; และทำงานกับ tempValue
Drunken Code Monkey

คำตอบ:


122

แลมบ์ดามีลักษณะของการเปลี่ยนแปลงอายุการใช้งานของตัวแปรที่จับได้ ตัวอย่างเช่นการแสดงออกแลมบ์ดาต่อไปนี้ทำให้พารามิเตอร์ p1 อยู่ได้นานกว่าเฟรมเมธอดปัจจุบันเนื่องจากสามารถเข้าถึงค่าได้หลังจากเฟรมเมธอดไม่อยู่บนสแต็กอีกต่อไป

Func<int> Example(int p1) {
  return () => p1;
}

คุณสมบัติอีกประการของตัวแปรที่จับได้คือการเปลี่ยนแปลงตัวแปรนั้นสามารถมองเห็นได้นอกนิพจน์แลมบ์ดา ตัวอย่างเช่นพิมพ์ต่อไปนี้ 42

void Example2(int p1) {
  Action del = () => { p1 = 42; }
  del();
  Console.WriteLine(p1);
}

คุณสมบัติทั้งสองนี้สร้างชุดของเอฟเฟกต์บางอย่างซึ่งบินไปหาหน้าของพารามิเตอร์อ้างอิงด้วยวิธีต่อไปนี้

  • พารามิเตอร์อ้างอิงอาจมีอายุการใช้งานที่แน่นอน พิจารณาส่งตัวแปรโลคัลเป็นพารามิเตอร์ ref ไปยังฟังก์ชัน
  • ผลข้างเคียงในแลมบ์ดาจะต้องมองเห็นได้บนพารามิเตอร์การอ้างอิงเอง ทั้งภายในวิธีการและในผู้โทร

เหล่านี้เป็นคุณสมบัติที่เข้ากันไม่ได้และเป็นหนึ่งในเหตุผลที่พวกเขาไม่ได้รับอนุญาตในการแสดงออกแลมบ์ดา


36
ฉันเข้าใจว่าเราไม่สามารถใช้refภายในการแสดงออกแลมบ์ดาได้ แต่ความปรารถนาที่จะใช้มันไม่ได้ถูกป้อน
zionpi

85

ภายใต้ประทุนนั้นจะมีการใช้วิธีการที่ไม่ระบุชื่อโดยการยกตัวแปรที่ถูกจับ (ซึ่งเป็นสิ่งที่ร่างกายของคุณถามเกี่ยวกับ) และเก็บไว้เป็นฟิลด์ของคอมไพเลอร์ที่สร้างคลาส ไม่มีวิธีในการจัดเก็บrefหรือoutพารามิเตอร์เป็นเขตข้อมูล เอริค Lippert กล่าวถึงไว้ในรายการบล็อก โปรดทราบว่ามีความแตกต่างระหว่างตัวแปรที่จับและพารามิเตอร์แลมบ์ดา คุณสามารถมี "พารามิเตอร์ที่เป็นทางการ" ได้ดังต่อไปนี้เนื่องจากไม่ใช่ตัวแปรที่จับภาพ:

delegate void TestDelegate (out int x);
static void Main(string[] args)
{
    TestDelegate testDel = (out int x) => { x = 10; };
    int p;
    testDel(out p);
    Console.WriteLine(p);
}

70

คุณสามารถ แต่คุณต้องกำหนดประเภททั้งหมดอย่างชัดเจน

(a, b, c, ref d) => {...}

ไม่ถูกต้องอย่างไรก็ตาม

(int a, int b, int c, ref int d) => {...}

ถูกต้อง


13
มันทำ; คำถามคือทำไมคุณถึงทำไม่ได้ คำตอบคือคุณสามารถ
Ben Adams

24
มันไม่ได้; คำถามคือเหตุผลที่คุณไม่สามารถอ้างอิงตัวแปรที่มีอยู่ซึ่งถูกกำหนดไว้แล้วrefหรือoutภายในแลมบ์ดา ชัดเจนถ้าคุณอ่านโค้ดตัวอย่าง (ลองอีกครั้งเพื่ออ่านอีกครั้ง) คำตอบที่ได้รับการยอมรับอธิบายอย่างชัดเจนว่าทำไม คำตอบของคุณเกี่ยวกับการใช้refหรือout พารามิเตอร์ของแลมบ์ดา อย่าตอบคำถามโดยสิ้นเชิงและพูดถึงสิ่งอื่น
edc65

4
@ edc65 ถูกต้อง ... สิ่งนี้ไม่เกี่ยวกับหัวเรื่องของคำถามซึ่งเกี่ยวกับเนื้อหาของนิพจน์ lamba (ทางขวา) ไม่ใช่รายการพารามิเตอร์ (ทางซ้าย) มันแปลกมากที่มี 26 upvotes
Jim Balter

6
มันช่วยฉันได้ +1 สำหรับสิ่งนั้น ขอบคุณ
Emad

1
แต่ฉันก็ยังไม่เข้าใจว่าทำไมมันจึงถูกออกแบบมาให้เป็นเช่นนี้ ทำไมฉันต้องกำหนดทุกประเภทอย่างชัดเจน ฉันไม่ต้องการความหมาย ฉันกำลังสูญเสียบางสิ่ง
joe

5

เนื่องจากนี่เป็นหนึ่งในผลลัพธ์ยอดนิยมสำหรับ "C # lambda ref" บน Google; ฉันรู้สึกว่าฉันต้องขยายคำตอบข้างต้น ไวยากรณ์ของผู้รับมอบสิทธิ์แบบไม่ระบุชื่อรุ่นเก่า (C # 2.0) ทำงานได้และสนับสนุนลายเซ็นที่ซับซ้อนมากขึ้น (รวมถึงการปิด) แลมบ์ดาและผู้ได้รับมอบหมายนิรนามอย่างน้อยที่สุดก็มีการใช้การรับรู้ร่วมกันในแบ็กเอนด์คอมไพเลอร์ (ถ้าไม่เหมือนกัน) - และที่สำคัญที่สุดคือพวกเขาสนับสนุนการปิด

สิ่งที่ฉันพยายามทำเมื่อทำการค้นหาเพื่อสาธิตให้เห็นถึงไวยากรณ์:

public static ScanOperation<TToken> CreateScanOperation(
    PrattTokenDefinition<TNode, TToken, TParser, TSelf> tokenDefinition)
{
    var oldScanOperation = tokenDefinition.ScanOperation; // Closures still work.
    return delegate(string text, ref int position, ref PositionInformation currentPosition)
        {
            var token = oldScanOperation(text, ref position, ref currentPosition);
            if (token == null)
                return null;
            if (tokenDefinition.LeftDenotation != null)
                token._led = tokenDefinition.LeftDenotation(token);
            if (tokenDefinition.NullDenotation != null)
                token._nud = tokenDefinition.NullDenotation(token);
            token.Identifier = tokenDefinition.Identifier;
            token.LeftBindingPower = tokenDefinition.LeftBindingPower;
            token.OnInitialize();
            return token;
        };
}

เพียงจำไว้ว่า Lambdas นั้นปลอดภัยกว่าในการดำเนินการและทางคณิตศาสตร์ (เนื่องจากการส่งเสริมค่าอ้างอิงที่กล่าวถึงก่อนหน้านี้): คุณอาจเปิดเวิร์มกระป๋องได้ คิดให้รอบคอบเมื่อใช้ไวยากรณ์นี้


3
ฉันคิดว่าคุณเข้าใจผิดคำถาม คำถามคือทำไมแลมบ์ดาไม่สามารถเข้าถึงตัวแปรการอ้างอิง / ออกในวิธีการจัดเก็บของมันไม่ใช่เหตุผลที่แลมบ์ดาเองไม่สามารถมีตัวแปรการอ้างอิง / ออกได้ AFAIK ไม่มีเหตุผลที่ดีสำหรับคนหลัง วันนี้ฉันเขียนแลมบ์ดา(a, b, c, ref d) => {...}และrefขีดเส้นใต้สีแดงพร้อมกับข้อความแสดงข้อผิดพลาด "ต้องแจ้งพารามิเตอร์ '4' ด้วยคำหลัก 'ref' facepalm! PS "การส่งเสริมค่าอ้างอิง" คืออะไร?
Qwertie

1
@Qertert ฉันได้สิ่งนี้เพื่อทำงานกับการกำหนดพารามิเตอร์แบบเต็มความหมายรวมถึงประเภทใน a, b, c และ d และมันใช้ได้ ดูคำตอบของเบ็นแอดดัม (แม้ว่าเขาจะเข้าใจผิดกับคำถามเดิม)
Ed Bayiates

@Qertert ฉันคิดว่าฉันเพิ่งลบจุดครึ่งนั้นออกไป - ฉันคิดว่าจุดเริ่มต้นคือการวาง params อ้างอิงในการปิดอาจมีความเสี่ยง แต่ฉันต้องตระหนักในภายหลังว่านี่ไม่ได้เกิดขึ้นในตัวอย่างที่ฉันให้ (และไม่ทำ ฉันรู้ว่าจะรวบรวมหรือไม่)
Jonathan Dickinson

สิ่งนี้ไม่เกี่ยวกับคำถามที่ถามจริง ... ดูคำตอบที่ยอมรับและความคิดเห็นภายใต้คำตอบจาก Ben Adams ซึ่งก็เข้าใจผิดคำถามเช่นกัน
Jim Balter

1

และอาจเป็นเช่นนี้?

private void Foo()
{
    int value;
    Bar(out value);
}

private void Bar(out int value)
{
    value = 3;
    int[] array = { 1, 2, 3, 4, 5 };
    var val = value; 
    int newValue = array.Where(a => a == val).First();
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.