ทำไมการคอมไพล์ตกลงเมื่อฉันใช้เมธอด Invoke และไม่ตกลงเมื่อฉันกลับ Func <int, int> โดยตรง?


28

ฉันไม่เข้าใจกรณีนี้:

public delegate int test(int i);

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
}

public test Fail()
{
    Func<int, int> f = x => x;
    return f; // <- code doesn't compile
}

ทำไมการคอมไพล์ตกลงเมื่อฉันใช้Invokeวิธีการและไม่ตกลงเมื่อฉันกลับcsharp Func<int,int>โดยตรง?


คุณมีผู้รับมอบสิทธิ์ซึ่งหมายความว่าคุณได้รับเหตุการณ์บางอย่าง การเรียกใช้ป้องกันการยกเว้นข้ามเธรดและอนุญาตให้หลายกระบวนการเข้าถึงวัตถุ
jdweng

โปรดทราบว่าคุณจะเห็นปัญหานี้แม้ว่าคุณจะใช้ผู้ได้รับมอบหมายที่ดูเหมือนสองคนเช่นdelegate void test1(int i);และdelegate void test2(int i);
Matthew Watson

คำตอบ:


27

มีสองสิ่งที่คุณต้องรู้เพื่อทำความเข้าใจพฤติกรรมนี้

  1. ผู้รับมอบสิทธิ์ทั้งหมดมาจากSystem.Delegateแต่ผู้รับมอบสิทธิ์ที่แตกต่างกันมีประเภทที่แตกต่างกันดังนั้นจึงไม่สามารถมอบหมายให้กันได้
  2. ภาษา C # ให้การจัดการพิเศษสำหรับการกำหนดวิธีการหรือแลมบ์ดาจะเป็นผู้รับมอบสิทธิ์

เนื่องจากผู้รับมอบสิทธิ์ที่แตกต่างกันมีประเภทที่แตกต่างกันนั่นหมายความว่าคุณไม่สามารถมอบหมายผู้รับมอบสิทธิ์ให้แก่ตัวแทนประเภทหนึ่ง

ตัวอย่างเช่นกำหนด:

delegate void test1(int i);
delegate void test2(int i);

แล้ว:

test1 a = Console.WriteLine; // Using special delegate initialisation handling.
test2 b = a;                 // Using normal assignment, therefore does not compile.

บรรทัดแรกข้างต้นรวบรวม OK เพราะมันใช้การจัดการพิเศษสำหรับการกำหนดแลมบ์ดาหรือวิธีการให้กับผู้ได้รับมอบหมาย

ในความเป็นจริงบรรทัดนี้เขียนใหม่ได้อย่างมีประสิทธิภาพเช่นนี้โดยคอมไพเลอร์:

test1 a = new test1(Console.WriteLine);

บรรทัดที่สองด้านบนไม่ได้รวบรวมเนื่องจากพยายามกำหนดอินสแตนซ์ของประเภทหนึ่งให้เป็นประเภทอื่นที่เข้ากันไม่ได้

เท่าที่ไปประเภทไม่มีการกำหนดที่เข้ากันได้ระหว่างtest1และtest2เพราะพวกเขาเป็นประเภทที่แตกต่างกัน

ถ้าช่วยให้คิดได้ให้พิจารณาลำดับชั้นของคลาสนี้:

class Base
{
}

class Test1 : Base
{
}

class Test2 : Base
{
}

รหัสต่อไปนี้จะไม่รวบรวมแม้ว่าTest1และได้Test2รับมาจากคลาสฐานเดียวกัน:

Test1 test1 = new Test1();
Test2 test2 = test1; // Compile error.

สิ่งนี้อธิบายว่าทำไมคุณไม่สามารถกำหนดประเภทผู้ได้รับมอบหมายให้แก่ประเภทอื่น นั่นเป็นเพียงภาษา C # ปกติ

อย่างไรก็ตามสิ่งสำคัญคือการเข้าใจว่าทำไมคุณจึงได้รับอนุญาตให้กำหนดวิธีการหรือแลมบ์ดาให้กับตัวแทนที่เข้ากันได้ ดังที่กล่าวไว้ข้างต้นนี่เป็นส่วนหนึ่งของการสนับสนุนภาษา C # สำหรับผู้ได้รับมอบหมาย

ดังนั้นในที่สุดก็จะตอบคำถามของคุณ:

เมื่อคุณใช้Invoke()คุณกำลังกำหนดวิธีการโทรไปยังผู้รับมอบสิทธิ์โดยใช้การจัดการภาษา C # พิเศษสำหรับการกำหนดวิธีการหรือ lambdas ให้กับผู้ร่วมประชุมแทนที่จะพยายามกำหนดประเภทที่เข้ากันไม่ได้ - ดังนั้นจึงรวบรวม OK

เพื่อความชัดเจนรหัสที่รวบรวมใน OP ของคุณ:

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
}

จริง ๆ แล้วเป็นการแปลงมโนภาพเป็นบางสิ่งเช่น:

public test Success()
{
    Func<int, int> f = x => x;
    return new test(f.Invoke);
}

ในขณะที่รหัสความล้มเหลวพยายามที่จะกำหนดระหว่างสองประเภทที่เข้ากันไม่ได้:

public test Fail()
{
    Func<int, int> f = x => x;
    return f; // Attempting to assign one delegate type to another: Fails
}

6

ในกรณีที่สองfเป็นประเภทแต่วิธีการที่บอกว่าจะกลับมาเป็นFunc<int, int> testเหล่านี้เป็นชนิดที่ไม่เกี่ยวข้อง (ผู้รับมอบสิทธิ์) ที่ไม่สามารถกลับกันได้ดังนั้นจึงเกิดข้อผิดพลาดคอมไพเลอร์ คุณสามารถไปที่ส่วนนี้ของข้อกำหนดภาษาและค้นหา "ผู้รับมอบสิทธิ์" คุณจะไม่พูดถึงการแปลงระหว่างผู้รับมอบสิทธิ์ที่มีลายเซ็นเหมือนกัน

อย่างไรก็ตามในกรณีแรกf.Invokeเป็นการแสดงออกของกลุ่มเมธอดซึ่งไม่มีชนิดจริง C # คอมไพเลอร์จะแปลงการแสดงออกวิธีการกลุ่มประเภทผู้รับมอบสิทธิ์เฉพาะตามบริบทผ่านการแปลงวิธีการกลุ่ม

(อ้างถึงสัญลักษณ์ข้อที่ 5 ที่นี่เน้นการเน้นของฉัน)

นิพจน์ถูกจัดประเภทเป็นอย่างใดอย่างหนึ่งต่อไปนี้:

  • ...

  • กลุ่มเมธอดซึ่งเป็นชุดของเมธอดโอเวอร์โหลดที่เกิดจากการค้นหาสมาชิก [... ] กลุ่มวิธีการได้รับอนุญาตใน invocation_expression, delegate_creation_expression และเป็นด้านซ้ายมือของisโอเปอเรเตอร์และสามารถแปลงเป็นประเภทตัวแทนที่เข้ากันได้โดยปริยาย

ในกรณีนี้มันจะถูกแปลงเป็นtestประเภทผู้ได้รับมอบหมาย

กล่าวอีกอย่างคือreturn fใช้งานไม่ได้เพราะfมีประเภทอยู่แล้ว แต่f.Invokeยังไม่มีประเภท


2

ปัญหาที่นี่คือประเภทความเข้ากันได้:

ต่อไปนี้เป็นคำนิยามของผู้แทนFuncจาก MSDN Sources:

public delegate TResult Func<in T, out TResult>(T arg);

หากคุณเห็นว่าไม่มีความสัมพันธ์โดยตรงระหว่าง Func ที่กล่าวถึงข้างต้นและผู้แทนที่คุณกำหนด:

public delegate int test(int i);

เหตุใดข้อมูลโค้ดชุดแรกจึงรวบรวม:

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
 }

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

เหตุใดข้อมูลโค้ดที่สองจึงไม่สามารถรวบรวมได้

ระหว่าง Func และผู้รับมอบสิทธิ์การทดสอบไม่มีความเข้ากันได้กับประเภท / การกำหนด Func ไม่สามารถกรอกข้อมูลเป็นส่วนหนึ่งของกฎระบบประเภท แม้เมื่อผลลัพธ์ของมันสามารถกำหนดและกรอกข้อมูลได้test delegateเช่นเดียวกับในกรณีแรก

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