เพราะเหตุใดคำหลัก 'out' จึงถูกใช้ในบริบทที่แตกต่างกันสองดูเหมือน


11

ใน C # outคำหลักสามารถใช้ได้สองวิธีที่ต่างกัน

  1. เป็นโมดิฟายเออร์พารามิเตอร์ที่อาร์กิวเมนต์ถูกส่งผ่านโดยการอ้างอิง

    class OutExample
    {
        static void Method(out int i)
        {
            i = 44;
        }
        static void Main()
        {
            int value;
            Method(out value);
            // value is now 44
        }
    }
  2. ในฐานะที่เป็นตัวปรับแต่งพารามิเตอร์ชนิดเพื่อระบุความแปรปรวน

    // Covariant interface. 
    interface ICovariant<out R> { }
    
    // Extending covariant interface. 
    interface IExtCovariant<out R> : ICovariant<R> { }
    
    // Implementing covariant interface. 
    class Sample<R> : ICovariant<R> { }
    
    class Program
    {
        static void Test()
        {
            ICovariant<Object> iobj = new Sample<Object>();
            ICovariant<String> istr = new Sample<String>();
    
            // You can assign istr to iobj because 
            // the ICovariant interface is covariant.
            iobj = istr;
        }
    }

คำถามของฉันคือ: ทำไม

เพื่อเริ่มต้นการเชื่อมต่อระหว่างสองดูเหมือนจะไม่ง่าย การใช้งานกับยาชื่อสามัญดูเหมือนจะไม่มีอะไรเกี่ยวข้องกับการส่งต่อโดยการอ้างอิง

ครั้งแรกที่ฉันได้เรียนรู้สิ่งที่outเกี่ยวข้องกับการส่งผ่านข้อโต้แย้งโดยการอ้างอิงและสิ่งนี้ขัดขวางความเข้าใจของฉันเกี่ยวกับการใช้การกำหนดความแปรปรวนร่วมกับยาชื่อสามัญ

มีการเชื่อมต่อระหว่างการใช้งานเหล่านี้ที่ฉันขาดหายไปหรือไม่?


5
การเชื่อมต่อเป็นเพียงเล็กน้อยที่เข้าใจมากขึ้นถ้าคุณมองไปที่แปรปรวนและ contravariance การใช้งานในผู้ร่วมประชุมSystem.Func<in T, out TResult>

4
นอกจากนี้นักออกแบบภาษาส่วนใหญ่พยายามลดจำนวนคำหลักและเพิ่มคำหลักใหม่ในบางภาษาที่มีอยู่ด้วย codebase ขนาดใหญ่นั้นเจ็บปวด (อาจเกิดความขัดแย้งกับบางรหัสที่มีอยู่โดยใช้คำนั้นเป็นชื่อ)
Basile Starynkevitch

คำตอบ:


20

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

ให้ดูที่หลักการการทดแทน Liskov :

...

หลักการของ Liskov กำหนดข้อกำหนดมาตรฐานบางประการเกี่ยวกับลายเซ็นซึ่งได้รับการรับรองในภาษาการเขียนโปรแกรมเชิงวัตถุใหม่ (โดยปกติจะอยู่ที่ระดับของชั้นเรียนมากกว่าประเภท; ดูประเภทย่อยและโครงสร้างย่อยเพื่อความแตกต่าง):

  • ความแปรปรวนของวิธีการโต้แย้งในประเภทย่อย
  • ความแปรปรวนร่วมของชนิดส่งคืนในชนิดย่อย

...

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

interface I<out T>
{
  void func(T t); //Invalid variance: The type parameter 'T' must be
                  //contravariantly valid on 'I<T>.func(T)'.
                  //'T' is covariant.

}

คล้ายคลึงกันถ้าคุณตั้งค่าสถานะพารามิเตอร์ประเภทด้วยinนั่นหมายความว่าคุณสามารถใช้มันเป็นอินพุต (พารามิเตอร์ฟังก์ชั่น) เท่านั้น ดังนั้นสิ่งต่อไปนี้ไม่ถูกต้อง:

interface I<in T>
{
  T func(); //Invalid variance: The type parameter 'T' must
            //be covariantly valid on 'I<T>.func()'. 
            //'T' is contravariant.

}

ดังนั้นเพื่อสรุปการเชื่อมต่อกับoutคำหลักคือด้วยพารามิเตอร์ฟังก์ชั่นมันหมายความว่ามันเป็นพารามิเตอร์ขาออกและสำหรับพารามิเตอร์ประเภทก็หมายความว่าประเภทที่ใช้ในบริบทเอาท์พุทเท่านั้น

System.Funcก็เป็นแบบอย่างที่ดีที่รยองพูดถึงในความคิดเห็นของเขา ในSystem.Funcการป้อนพารามิเตอร์ทั้งหมด ar flaged ด้วยinและพารามิเตอร์ที่ส่งออกเป็น flaged outกับ เหตุผลเป็นสิ่งที่ฉันอธิบาย


2
คำตอบที่ดี! ช่วยฉันหน่อย ... รอสักครู่ ... กำลังพิมพ์! โดยวิธีการ: ส่วนหนึ่งของ LSP ที่คุณยกมาเป็นที่รู้จักกันมานานก่อน Liskov มันเป็นเพียงกฎประเภทย่อยมาตรฐานสำหรับประเภทฟังก์ชั่น (ประเภทพารามิเตอร์เป็นแบบแปรปรวนชนิดส่งคืนจะแปรปรวน) ความแปลกใหม่ของวิธีการของ Liskov คือ a) การใช้ถ้อยคำกฎไม่ได้อยู่ในรูปของการร่วม / ตรงกันข้าม แต่ในแง่ของการทดแทนพฤติกรรม (ตามที่กำหนดโดย pre- / postconditions) และ b) กฎประวัติศาสตร์ซึ่งทำให้สามารถใช้เหตุผลทั้งหมดได้ เป็นประเภทข้อมูลที่ไม่แน่นอนซึ่งไม่สามารถทำได้ก่อนหน้านี้
Jörg W Mittag

10

@atupapa.com

คำหลักมีราคาแพงมาก คุณไม่สามารถใช้มันเป็นตัวระบุในโปรแกรมของคุณ แต่มีเพียงคำศัพท์จำนวนมากในภาษาอังกฤษ ดังนั้นบางครั้งคุณพบข้อขัดแย้งและคุณต้องเปลี่ยนชื่อตัวแปรวิธีการฟิลด์คุณสมบัติคลาสอินเทอร์เฟซหรือโครงสร้างเพื่อหลีกเลี่ยงการปะทะกันกับคำสำคัญ ตัวอย่างเช่นหากคุณกำลังสร้างโรงเรียนคุณจะเรียกชั้นเรียนว่าอย่างไร คุณไม่สามารถเรียกมันว่าคลาสเพราะclassเป็นคำหลัก!

การเพิ่มคำหลักให้กับภาษานั้นมีราคาแพงกว่ามาก มันทำให้รหัสทั้งหมดที่ใช้คำหลักนี้เป็นตัวระบุผิดกฎหมายทำลายความเข้ากันได้ย้อนหลังไปทุกที่

มีคำหลักinและoutอยู่แล้วดังนั้นจึงสามารถนำมาใช้ซ้ำได้

พวกเขาสามารถเพิ่มคำหลักเชิงบริบทซึ่งเป็นเพียงคำหลักภายในบริบทของรายการพารามิเตอร์ประเภท แต่พวกเขาจะเลือกคำหลักใด covariantและcontravariant? +และ-(เช่น Scala ทำเช่น)? superและextendsอย่างที่ Java ทำ คุณจำส่วนหัวที่พารามิเตอร์แปรปรวนและแปรปรวนได้ไหม

ด้วยวิธีแก้ปัญหาปัจจุบันมี mnemonic ที่ดี: พารามิเตอร์ประเภทผลลัพธ์ได้outคำหลักพารามิเตอร์ประเภทอินพุตรับคำinหลัก สังเกตความสมมาตรที่ดีของพารามิเตอร์เมธอด: พารามิเตอร์เอาต์พุตรับoutคีย์เวิร์ดพารามิเตอร์อินพุตรับinคีย์เวิร์ด (ดีจริงไม่มีคีย์เวิร์ดเลยเนื่องจากอินพุตเป็นค่าเริ่มต้น แต่คุณได้แนวคิด)

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


วิธีการจำความแปรปรวนร่วมกับการแปรปรวนคือการพิจารณาว่าจะเกิดอะไรขึ้นถ้าฟังก์ชั่นในอินเตอร์เฟสใช้พารามิเตอร์ของประเภทอินเตอร์เฟสทั่วไป ถ้ามีinterface Accepter<in T> { void Accept(T it);};, Accepter<Foo<T>>จะยอมรับTเป็นพารามิเตอร์อินพุตหากFoo<T>ยอมรับว่าเป็นพารามิเตอร์เอาต์พุตและในทางกลับกัน ดังนั้นในทางตรงกันข้าม -แปรปรวน โดยคมชัดจะมีสิ่งที่จัดเรียงของความแปรปรวนมี - จึงร่วม -variance interface ISupplier<out T> { T get();};Supplier<Foo<T>>Foo
supercat
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.