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


112

ฉันมีคลาสนี้

public class Overloaded
{
    public void ComplexOverloadResolution(params string[] something)
    {
        Console.WriteLine("Normal Winner");
    }

    public void ComplexOverloadResolution<M>(M something)
    {
        Console.WriteLine("Confused");
    }
}

ถ้าฉันเรียกแบบนี้:

        var blah = new Overloaded();
        blah.ComplexOverloadResolution("Which wins?");

มันเขียนNormal Winnerไปที่คอนโซล

แต่ถ้าฉันเพิ่มวิธีอื่น:

    public void ComplexOverloadResolution(string something, object somethingElse = null)
    {
        Console.WriteLine("Added Later");
    }

ฉันได้รับข้อผิดพลาดต่อไปนี้:

การเรียกไม่ชัดเจนระหว่างวิธีการหรือคุณสมบัติต่อไปนี้:> ' Overloaded.ComplexOverloadResolution(params string[])' และ ' Overloaded.ComplexOverloadResolution<string>(string)'

ฉันเข้าใจว่าการเพิ่มวิธีการอาจทำให้เกิดความคลุมเครือในการโทร แต่เป็นความคลุมเครือระหว่างสองวิธีที่มีอยู่แล้ว(params string[])และ<string>(string)! เห็นได้ชัดว่าทั้งสองวิธีที่เกี่ยวข้องกับความคลุมเครือไม่ได้เป็นวิธีการที่เพิ่มเข้ามาใหม่เนื่องจากวิธีแรกคือพารามิเตอร์และวิธีที่สองเป็นวิธีการทั่วไป

นี่คือบั๊กหรือไม่? ส่วนไหนของสเป็คที่บอกว่าควรจะเป็น


2
ฉันไม่คิดว่า'Overloaded.ComplexOverloadResolution(string)'หมายถึง<string>(string)วิธีการนี้ ฉันคิดว่ามันหมายถึง(string, object)วิธีการที่ไม่มีวัตถุให้มา
หลอน

1
@phoog โอ้ข้อมูลนั้นถูกตัดโดย StackOverflow เนื่องจากเป็นแท็ก แต่ข้อความแสดงข้อผิดพลาดมีตัวกำหนดเทมเพลต ฉันจะเพิ่มกลับเข้าไป
McKay

คุณจับฉันออกไป! ฉันอ้างถึงส่วนที่เกี่ยวข้องของข้อมูลจำเพาะในคำตอบของฉัน แต่ฉันไม่ได้ใช้เวลาครึ่งชั่วโมงที่ผ่านมาอ่านและทำความเข้าใจพวกเขา!
ผี

@phoog เมื่อมองผ่านส่วนต่างๆของข้อมูลจำเพาะฉันไม่เห็นอะไรเลยเกี่ยวกับการแนะนำความไม่ชัดเจนในวิธีการอื่นนอกเหนือจากตัวมันเองและวิธีอื่นไม่ใช่สองวิธีอื่น
McKay

สำหรับฉันแล้วว่านี่เป็นเพียงการเป่ายิ้งฉุบ : ชุดของสองค่าที่แตกต่างกันใด ๆ มีผู้ชนะ แต่ชุดค่าสามค่าที่สมบูรณ์ไม่มี
กลัว

คำตอบ:


107

นี่คือบั๊กหรือไม่?

ใช่.

ขอแสดงความยินดีคุณพบข้อบกพร่องในการแก้ปัญหาโอเวอร์โหลด ข้อผิดพลาดเกิดขึ้นใน C # 4 และ 5; จะไม่ทำซ้ำในเครื่องวิเคราะห์ความหมายเวอร์ชัน "Roslyn" ฉันได้แจ้งให้ทีมทดสอบ C # 5 ทราบแล้วและหวังว่าเราจะได้รับการตรวจสอบและแก้ไขปัญหานี้ก่อนการเปิดตัวครั้งสุดท้าย (เช่นเคยไม่มีสัญญา)

การวิเคราะห์ที่ถูกต้องมีดังต่อไปนี้ ผู้สมัครคือ:

0: C(params string[]) in its normal form
1: C(params string[]) in its expanded form
2: C<string>(string) 
3: C(string, object) 

เห็นได้ชัดว่าศูนย์ผู้สมัครไม่สามารถใช้งานได้เนื่องจากstringไม่สามารถแปลงเป็นstring[]. นั่นคือสาม

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

อย่างไหนดีกว่า 1 หรือ 2? tiebreaker ที่เกี่ยวข้องคือวิธีการทั่วไปนั้นแย่กว่าวิธีที่ไม่ใช่ทั่วไปเสมอ 2 แย่กว่า 1 ดังนั้น 2 จึงไม่สามารถเป็นผู้ชนะได้

อย่างไหนดีกว่า 1 หรือ 3? tiebreaker ที่เกี่ยวข้องคือ: วิธีการที่ใช้ได้เฉพาะในรูปแบบที่ขยายแล้วจะแย่กว่าวิธีที่ใช้ในรูปแบบปกติเสมอ ดังนั้น 1 จึงแย่กว่า 3 ดังนั้น 1 จึงไม่สามารถเป็นผู้ชนะได้

อย่างไหนดีกว่า 2 หรือ 3? tiebreaker ที่เกี่ยวข้องคือวิธีการทั่วไปนั้นแย่กว่าวิธีที่ไม่ใช่ทั่วไปเสมอ 2 แย่กว่า 3 ดังนั้น 2 จึงไม่สามารถเป็นผู้ชนะได้

ในการได้รับเลือกจากชุดของผู้สมัครที่เกี่ยวข้องหลายคนผู้สมัครจะต้อง (1) ไม่แพ้ใคร (2) เอาชนะผู้สมัครคนอื่นอย่างน้อยหนึ่งคนและ (3) เป็นผู้สมัครที่ไม่ซ้ำกันซึ่งมีคุณสมบัติสองประการแรก ผู้สมัครคนที่สามไม่แพ้ผู้สมัครคนอื่นและเอาชนะผู้สมัครคนอื่นอย่างน้อยหนึ่งคน เป็นผู้สมัครเพียงรายเดียวที่มีคุณสมบัตินี้ ดังนั้นผู้สมัครที่สามเป็นผู้สมัครที่ดีที่สุดที่ไม่ซ้ำกัน มันควรจะชนะ

ไม่เพียง แต่คอมไพเลอร์ C # 4 จะทำงานผิดพลาด แต่คุณสังเกตถูกต้องว่ามันกำลังรายงานข้อความแสดงข้อผิดพลาดที่แปลกประหลาด การที่คอมไพลเลอร์ได้รับการวิเคราะห์ความละเอียดเกินพิกัดนั้นผิดพลาดเล็กน้อย การที่ได้รับข้อความแสดงข้อผิดพลาดนั้นไม่น่าแปลกใจเลย ข้อผิดพลาดฮิวริสติก "วิธีการที่คลุมเครือ" โดยทั่วไปจะเลือกวิธีการสองวิธีจากชุดผู้สมัครหากไม่สามารถกำหนดวิธีการที่ดีที่สุดได้ มันไม่ค่อยดีนักในการค้นหาความคลุมเครือ "ของจริง" หากในความเป็นจริงมีอยู่

คนหนึ่งอาจถามว่าทำไมถึงเป็นเช่นนั้น มันค่อนข้างยุ่งยากในการหาสองวิธีการที่ "คลุมเครือ unambigously" เพราะ "betterness" ความสัมพันธ์เป็นกรรม เป็นไปได้ที่จะเกิดสถานการณ์ที่ผู้สมัคร 1 ดีกว่า 2, 2 ดีกว่า 3 และ 3 ดีกว่า 1 ในสถานการณ์เช่นนี้เราไม่สามารถทำได้ดีไปกว่าการเลือกสองคนเป็น "คนที่ไม่ชัดเจน"

ฉันต้องการปรับปรุงฮิวริสติกนี้สำหรับ Roslyn แต่มีความสำคัญต่ำ

(แบบฝึกหัดสำหรับผู้อ่าน: "ประดิษฐ์อัลกอริทึมเชิงเส้นเวลาเพื่อระบุสมาชิกที่ดีที่สุดที่ไม่ซ้ำกันของชุดขององค์ประกอบ n ที่ความสัมพันธ์ที่ดีกว่าเป็นอกรรมกริยา" เป็นหนึ่งในคำถามที่ฉันถูกถามในวันที่ฉันสัมภาษณ์ทีมนี้ไม่ใช่ อัลกอริทึมที่ยากมากลองยิงดูสิ)

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

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

ขอขอบคุณที่แจ้งเรื่องนี้ให้ฉันทราบ ขออภัยในความผิดพลาด


1
ขอบคุณสำหรับการตอบกลับ คุณบอกว่า "1 แย่กว่า 2" แต่จะเลือกวิธีที่ 1 ถ้าฉันมีวิธีที่ 1 และ 2?
McKay

@McKay: อ๊ะคุณพูดถูกฉันบอกว่าย้อนหลัง ฉันจะแก้ไขข้อความ
Eric Lippert

1
รู้สึกอึดอัดใจเมื่ออ่านวลี "ช่วงเวลาที่เหลือของปี" เพราะเหลือเวลาอีกไม่ถึงครึ่งสัปดาห์ :)
BoltClock

2
@BoltClock จริง ๆ คำสั่ง "ออกไปพักผ่อนในช่วงที่เหลือของปี" หมายถึงวันหยุดหนึ่งวัน
กลัว

1
ฉันคิดอย่างนั้น ผมอ่าน"3) เป็นผู้สมัครที่ไม่ซ้ำกันที่มีคุณสมบัติสองครั้งแรก"เป็น"เป็นผู้สมัครเพียงคนเดียวที่ (จะแพ้ใครและเต้นอย่างน้อยหนึ่งผู้สมัครอื่น ๆ )" แต่ความคิดเห็นล่าสุดของคุณทำให้ฉันคิด"(เป็นผู้สมัครเพียงคนเดียวที่จะแพ้ใคร) และเต้นอย่างน้อยหนึ่งผู้สมัครอื่น ๆ" ภาษาอังกฤษสามารถใช้สัญลักษณ์การจัดกลุ่มได้จริงๆ ถ้าอย่างหลังจริงเดี๋ยวโดนอีกที
default.kramer

5

ส่วนใดของสเป็คที่บอกว่าควรเป็นเช่นนี้?

ส่วน 7.5.3 (ความละเอียดเกินพิกัด) พร้อมด้วยส่วน 7.4 (การค้นหาสมาชิก) และ 7.5.2 (การอนุมานประเภท)

โปรดสังเกตโดยเฉพาะส่วน 7.5.3.2 (สมาชิกฟังก์ชันที่ดีกว่า) ซึ่งระบุว่าในส่วน "พารามิเตอร์ทางเลือกที่ไม่มีอาร์กิวเมนต์ที่เกี่ยวข้องจะถูกลบออกจากรายการพารามิเตอร์" และ "ถ้า M (p) เป็นวิธีที่ไม่ใช่วิธีทั่วไป amd M (q) คือ วิธีการทั่วไปดังนั้น M (p) จะดีกว่า M (q) "

อย่างไรก็ตามฉันไม่เข้าใจส่วนต่างๆของข้อมูลจำเพาะเหล่านี้อย่างละเอียดเพียงพอที่จะทราบว่าส่วนใดของข้อมูลจำเพาะที่ควบคุมพฤติกรรมนี้นับประสาอะไรกับการตัดสินว่าเป็นไปตามข้อกำหนดหรือไม่


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

@McKay ยุติธรรมพอ (ดูแก้ไข) เราจะต้องรอให้ Eric Lippert บอกเราว่านี่เป็นพฤติกรรมที่ถูกต้องหรือไม่: ->
phoog

1
สิ่งเหล่านี้คือส่วนที่ถูกต้องของข้อมูลจำเพาะ ปัญหาคือพวกเขาบอกว่าไม่ควรเป็นอย่างนั้น!
Eric Lippert

3

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

แบบนี้ :

public class Overloaded
{
    public void ComplexOverloadResolution(params string[] somethings)
    {
        Console.WriteLine("Normal Winner");
    }

    public void ComplexOverloadResolution<M>(M something)
    {
        Console.WriteLine("Confused");
    }

    public void ComplexOverloadResolution(string something, object somethingElse = null)
    {
        Console.WriteLine("Added Later");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Overloaded a = new Overloaded();
        a.ComplexOverloadResolution(something:"asd");
    }
}

โอ้ฉันรู้ว่ารหัสนี้ไม่ดีและมีหลายวิธีในการแก้ไขคำถามคือ "เหตุใดคอมไพเลอร์จึงทำงานในลักษณะนี้"
McKay

1

หากคุณลบออกparamsจากวิธีแรกสิ่งนี้จะไม่เกิดขึ้น วิธีแรกและวิธีที่สามของคุณมีทั้งการโทรที่ถูกต้องComplexOverloadResolution(string)แต่หากวิธีแรกของคุณคือpublic void ComplexOverloadResolution(string[] something)จะไม่มีความคลุมเครือ

การระบุค่าสำหรับพารามิเตอร์object somethingElse = nullทำให้เป็นพารามิเตอร์ทางเลือกดังนั้นจึงไม่จำเป็นต้องระบุเมื่อเรียกโอเวอร์โหลดนั้น

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

'ConsoleApplication1.Program.ComplexOverloadResolution (สตริงพารามิเตอร์ [])' และ 'ConsoleApplication1.Program.ComplexOverloadResolution (สตริงวัตถุ)'

แก้ไข 2: การค้นพบใหม่ การลบวิธีการใด ๆ จากสามวิธีข้างต้นจะไม่ทำให้เกิดความคลุมเครือระหว่างสองวิธี ดังนั้นดูเหมือนว่าความขัดแย้งจะปรากฏขึ้นก็ต่อเมื่อมีสามวิธีอยู่โดยไม่คำนึงถึงลำดับ


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

ความคลุมเครือเกิดขึ้นระหว่างวิธีแรกและวิธีที่สามของคุณ แต่ทำไมคอมไพเลอร์รายงานว่าอีกสองวิธีนั้นอยู่นอกเหนือฉัน
Tomislav Markovski

แต่ถ้าฉันลบวิธีที่สองฉันไม่มีความคลุมเครือมันก็เรียกวิธีที่สามสำเร็จ ดังนั้นจึงไม่ดูเหมือนว่าคอมไพเลอร์มีความคลุมเครือระหว่างวิธีแรกและวิธีที่สาม
McKay

ดูการแก้ไขของฉัน คอมไพเลอร์บ้า
Tomislav Markovski

อันที่จริงการรวมกันของเพียงสองวิธีไม่ก่อให้เกิดความคลุมเครือ นี่เป็นเรื่องแปลกมาก Edit2
Tomislav Markovski

1
  1. ถ้าคุณเขียน

    var blah = new Overloaded();
    blah.ComplexOverloadResolution("Which wins?");

    หรือเพียงแค่เขียน

    var blah = new Overloaded();
    blah.ComplexOverloadResolution();

    มันจะกลายเป็นวิธีเดียวกันในวิธีการ

    public void ComplexOverloadResolution(params string[] something

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

  2. หากคุณพยายามเพิ่มวิธีการใหม่ ๆ เช่นนี้

    public void ComplexOverloadResolution(string something)
    {
        Console.WriteLine("Added Later");
    }

    มันจะรวบรวมและเรียกเมธอดนี้อย่างสมบูรณ์แบบเนื่องจากเป็นการจับคู่ที่สมบูรณ์แบบสำหรับการโทรของคุณด้วยstringพารามิเตอร์ params string[] somethingเข้มแข็งมากขึ้นแล้ว

  3. คุณประกาศวิธีที่สองเหมือนที่คุณทำ

    public void ComplexOverloadResolution(string something, object something=null);

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

    var blah = new Overloaded();
    blah.ComplexOverloadResolution("Which wins?");

    Infact หากคุณลบพารามิเตอร์สตริงออกจากการโทรเช่นโค้ดต่อไปนี้ทุกอย่างจะคอมไพล์อย่างถูกต้องและทำงานได้เหมือนเดิม

    var blah = new Overloaded();
    blah.ComplexOverloadResolution(); // will be ComplexOverloadResolution(params string[] something) function called here, like a best match.

ขั้นแรกให้คุณเขียนกรณีการโทรสองกรณีที่เหมือนกัน แน่นอนว่ามันจะเป็นวิธีการเดียวกันหรือคุณหมายถึงเขียนอะไรที่แตกต่างออกไป?
McKay

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

อาขอบคุณ แต่นั่นยังคงทิ้งปัญหาที่ฉันพูดถึงในความคิดเห็นที่สองของฉันในโพสต์ของคุณ
McKay

เพื่อให้ชัดเจนยิ่งขึ้นคุณระบุว่า "คอมไพเลอร์กระโดดด้วยความสับสนทั้งหมดระหว่างวิธีแรกกับวิธีนี้เพิ่มเพียงวิธีเดียว" แต่มันไม่ใช่ มันกำลังสับสนกับอีกสองวิธี วิธี params และวิธีการทั่วไป
McKay

@McKay: แต่มันเพิ่มความสับสนด้วยการให้state3 ฟังก์ชั่นไม่ใช่เดี่ยวหรือสองอย่างเพื่อให้แม่นยำ Infact ก็เพียงพอที่จะแสดงความคิดเห็นใด ๆเพื่อแก้ไขปัญหา การจับคู่ที่ดีที่สุดในฟังก์ชันที่มีอยู่คือฟังก์ชันที่มีฟังก์ชันที่สองคือฟังก์ชันที่paramsมีgenericsพารามิเตอร์เมื่อเราเพิ่มsetฟังก์ชันที่สามจะทำให้เกิดความสับสนในฟังก์ชัน ฉันคิดว่าเป็นไปได้มากว่าจะไม่ชัดเจนข้อความแสดงข้อผิดพลาดที่สร้างโดยคอมไพเลอร์
Tigran
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.