ปัญหาในการทำความเข้าใจความแตกต่างของความแปรปรวนร่วมกับยาชื่อสามัญใน C #


115

ฉันไม่เข้าใจว่าทำไมโค้ด C # ต่อไปนี้ไม่คอมไพล์

อย่างที่คุณเห็นฉันมีวิธีการทั่วไปแบบคงที่ Something ที่มีIEnumerable<T>พารามิเตอร์ (และTถูก จำกัด ให้เป็นIAอินเทอร์เฟซ) และพารามิเตอร์นี้ไม่สามารถแปลงเป็นIEnumerable<IA>ไฟล์.

มีคำอธิบายอย่างไร? (ฉันไม่ได้ค้นหาวิธีแก้ปัญหาเพียงเพื่อทำความเข้าใจว่าเหตุใดจึงไม่ได้ผล)

public interface IA { }
public interface IB : IA { }
public class CIA : IA { }
public class CIAD : CIA { }
public class CIB : IB { }
public class CIBD : CIB { }

public static class Test
{
    public static IList<T> Something<T>(IEnumerable<T> foo) where T : IA
    {
        var bar = foo.ToList();

        // All those calls are legal
        Something2(new List<IA>());
        Something2(new List<IB>());
        Something2(new List<CIA>());
        Something2(new List<CIAD>());
        Something2(new List<CIB>());
        Something2(new List<CIBD>());
        Something2(bar.Cast<IA>());

        // This call is illegal
        Something2(bar);

        return bar;
    }

    private static void Something2(IEnumerable<IA> foo)
    {
    }
}

เกิดข้อผิดพลาดฉันเข้าSomething2(bar)แถว:

อาร์กิวเมนต์ 1: ไม่สามารถแปลงจาก 'System.Collections.Generic.List' เป็น 'System.Collections.Generic.IEnumerable'



12
คุณไม่ได้ จำกัด เฉพาะTประเภทการอ้างอิง หากคุณใช้เงื่อนไขwhere T: class, IAแล้วควรใช้งานได้ คำตอบที่เชื่อมโยงมีรายละเอียดเพิ่มเติม
Dirk

2
@ เดิร์กฉันไม่คิดว่าสิ่งนี้ควรถูกตั้งค่าสถานะว่าซ้ำกัน แม้ว่าจะเป็นความจริงที่ว่าปัญหาของแนวคิดในที่นี้เป็นปัญหาความแปรปรวนร่วม / ความแตกต่างในประเภทมูลค่า แต่กรณีเฉพาะในที่นี้คือ "ข้อความแสดงข้อผิดพลาดนี้หมายถึงอะไร" เช่นเดียวกับผู้เขียนที่ไม่ได้ตระหนักว่าเป็นเพียง "คลาส" เท่านั้นที่ช่วยแก้ปัญหาของเขาได้ ฉันเชื่อว่าผู้ใช้ในอนาคตจะค้นหาข้อความแสดงข้อผิดพลาดนี้พบโพสต์นี้และปล่อยให้มีความสุข (อย่างที่ฉันทำบ่อยๆ)
Reginald Blue

คุณยังสามารถจำลองสถานการณ์ได้โดยเพียงแค่พูดSomething2(foo);ตรงๆ การไปรอบ ๆ.ToList()เพื่อรับList<T>( Tคือพารามิเตอร์ประเภทของคุณที่ประกาศโดยวิธีการทั่วไป) ไม่จำเป็นต้องเข้าใจสิ่งนี้ (a List<T>คือ an IEnumerable<T>)
Jeppe Stig Nielsen

@ReginaldBlue 100% กำลังจะโพสต์สิ่งเดียวกัน คำตอบที่คล้ายกันไม่ใช่คำถามที่ซ้ำกัน
UuDdLrLrSs

คำตอบ:


218

ข้อความแสดงข้อผิดพลาดให้ข้อมูลไม่เพียงพอและนั่นเป็นความผิดของฉัน ขอโทษด้วยกับเรื่องนั้น.

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

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

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

ข้อความแสดงข้อผิดพลาดควรบอกว่าการแปลงเป็นไปไม่ได้จริง ๆ เนื่องจากความแปรปรวนร่วมต้องการการรับประกันประเภทอ้างอิงเนื่องจากเป็นปัญหาพื้นฐาน


3
ทำไมคุณถึงบอกว่ามันเป็นความผิดของคุณ?
user4951

77
@ user4951: เนื่องจากฉันใช้ตรรกะการตรวจสอบการแปลงทั้งหมดรวมถึงข้อความแสดงข้อผิดพลาด
Eric Lippert

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

3
@ PeterA.Schneider: ฉันซาบซึ้งในสิ่งนั้น แต่เป้าหมายหลักประการหนึ่งของฉันในการออกแบบตรรกะการรายงานข้อผิดพลาดใน Roslyn โดยเฉพาะอย่างยิ่งเพื่อจับภาพไม่เพียงว่ากฎใดถูกละเมิดเท่านั้น แต่ยังเป็นการระบุ "สาเหตุที่แท้จริง" หากเป็นไปได้ ตัวอย่างเช่นข้อความแสดงข้อผิดพลาดควรมีไว้เพื่อcustomers.Select(c=>c.FristName)อะไร? ข้อกำหนด C # นั้นชัดเจนมากว่านี่เป็นข้อผิดพลาดในการแก้ปัญหาเกินพิกัด : ชุดของวิธีการที่ใช้งานได้ชื่อ Select ที่สามารถใช้แลมบ์ดานั้นว่างเปล่า แต่ต้นตอเกิดFirstNameจากการพิมพ์ผิด
Eric Lippert

3
@ PeterA.Schneider: ฉันทำงานหลายอย่างเพื่อให้แน่ใจว่าสถานการณ์ที่เกี่ยวข้องกับการอนุมานประเภททั่วไปและแลมบ์ดาใช้ฮิวริสติกส์ที่เหมาะสมเพื่อสรุปข้อความแสดงข้อผิดพลาดที่น่าจะช่วยนักพัฒนาได้ดีที่สุด แต่ฉันทำงานได้ดีน้อยกว่ามากกับข้อความแสดงข้อผิดพลาดของ Conversion โดยเฉพาะอย่างยิ่งในกรณีที่ความแปรปรวนเกี่ยวข้อง ฉันเสียใจมาตลอด
Eric Lippert

26

ฉันแค่ต้องการเติมเต็มคำตอบภายในที่ยอดเยี่ยมของ Eric ด้วยตัวอย่างโค้ดสำหรับผู้ที่อาจไม่คุ้นเคยกับข้อ จำกัด ทั่วไป

เปลี่ยนSomethingลายเซ็น 's เช่นนี้classข้อ จำกัด ที่มีมาก่อน

public static IList<T> Something<T>(IEnumerable<T> foo) where T : class, IA

2
ฉันสงสัย ... อะไรคือเหตุผลเบื้องหลังความสำคัญของการสั่งซื้อ?
Tom Wright

5
@ TomWright - สเป็คไม่ได้รวมคำตอบสำหรับ "ทำไม" ไว้มากมาย คำถาม แต่ในกรณีนี้จะทำให้ชัดเจนว่ามีข้อ จำกัด สามประเภทที่แตกต่างกันและเมื่อใช้ทั้งสามข้อจะต้องมีการใช้โดยเฉพาะprimary_constraint ',' secondary_constraints ',' constructor_constraint
Damien_The_Unbeliever

2
@ TomWright: Damien ถูกต้อง; ไม่มีเหตุผลใดที่ฉันทราบเป็นพิเศษนอกจากความสะดวกของผู้เขียนโปรแกรมแยกวิเคราะห์ ถ้าฉันมีคนขับรถของฉันไวยากรณ์สำหรับข้อ จำกัด ประเภทจะมีรายละเอียดมากกว่านี้มาก classไม่ดีเพราะหมายถึง "ประเภทอ้างอิง" ไม่ใช่ "คลาส" ฉันจะมีความสุขมากขึ้นกับบางสิ่งบางอย่างเช่นwhere T is not struct
Eric Lippert
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.