อะไรคือการใช้ downcasting ที่เหมาะสม?


67

Downcasting หมายถึงการคัดเลือกจากคลาสพื้นฐาน (หรืออินเทอร์เฟซ) ไปยังคลาสย่อยหรือคลาสลีฟ

ตัวอย่างของการดาวน์สตรีมอาจเป็นหากคุณส่งจากSystem.Objectประเภทอื่น

Downcasting นั้นเป็นที่นิยมอาจจะเป็นกลิ่นรหัส: หลักคำสอนเชิงวัตถุนั้นต้องการที่จะยกตัวอย่างเช่นการกำหนดและการเรียกวิธีเสมือนหรือนามธรรมแทนที่จะเป็นการลดทอน

  • หากมีกรณีใดบ้างเป็นกรณีการใช้งานที่เหมาะสมและเหมาะสมสำหรับการลดความเร็ว? นั่นคือในสถานการณ์ใดมันเป็นสิ่งที่เหมาะสมในการเขียนรหัสที่ downcasts?
  • หากคำตอบของคุณคือ "none" ดังนั้นทำไมระบบ downcasting จึงรองรับภาษานี้

5
สิ่งที่คุณอธิบายมักเรียกว่า "downcasting" ด้วยความรู้นั้นคุณสามารถค้นคว้าเพิ่มเติมด้วยตัวคุณเองก่อนและอธิบายว่าทำไมเหตุผลที่มีอยู่ที่คุณพบไม่เพียงพอ? TL; DR: downcasting หยุดการพิมพ์แบบสแตติก แต่บางครั้งระบบประเภทก็เข้มงวดเกินไป ดังนั้นจึงเป็นเรื่องที่สมเหตุสมผลสำหรับภาษาที่จะนำเสนอการลดความปลอดภัย
amon

คุณหมายถึง downcasting หรือเปล่า การอัปโหลดจะขึ้นอยู่กับลำดับชั้นของคลาสเช่นจากคลาสย่อยไปจนถึงคลาสซุปเปอร์ซึ่งตรงข้ามกับการลดระดับจากซูเปอร์คลาสเป็นคลาสย่อย ...
Andrei Socaciu

ฉันขอโทษ: คุณพูดถูก ฉันแก้ไขคำถามเพื่อเปลี่ยนชื่อ "upcast" เป็น "downcast"
ChrisW

@amon en.wikipedia.org/wiki/Downcastingให้ "แปลงเป็นสตริง" เป็นตัวอย่าง (ซึ่ง. Net รองรับการใช้เสมือนToString); ตัวอย่างอื่น ๆ ของมันคือ "Java container" เพราะ Java ไม่รองรับ generics (ซึ่ง C # ทำ) stackoverflow.com/questions/1524197/downcast-and-upcastกล่าวว่าสิ่งที่ downcasting เป็นแต่ไม่มีตัวอย่างของเมื่อมันเหมาะสม
ChrisW

4
@ChrisW นั่นคือไม่before Java had support for generics because Java doesn't support genericsและ amon ก็ให้คำตอบกับคุณ - เมื่อระบบประเภทที่คุณทำงานด้วยนั้นเข้มงวดเกินไปและคุณไม่สามารถเปลี่ยนได้
Ordous

คำตอบ:


12

นี่คือการใช้ downcasting ที่เหมาะสม

และฉันก็ไม่เห็นด้วยอย่างรุนแรงกับคนอื่น ๆ ที่กล่าวว่าการใช้ downcasts เป็นกลิ่นรหัสแน่นอนเพราะฉันเชื่อว่าไม่มีวิธีอื่นที่เหมาะสมในการแก้ปัญหาที่เกี่ยวข้อง

Equals:

class A
{
    // Nothing here, but if you're a C++ programmer who dislikes object.Equals(object),
    // pretend it's not there and we have abstract bool A.Equals(A) instead.
}
class B : A
{
    private int x;
    public override bool Equals(object other)
    {
        var casted = other as B;  // cautious downcast (dynamic_cast in C++)
        return casted != null && this.x == casted.x;
    }
}

Clone:

class A : ICloneable
{
    // Again, if you dislike ICloneable, that's not the point.
    // Just ignore that and pretend this method returns type A instead of object.
    public virtual object Clone()
    {
        return this.MemberwiseClone();  // some sane default behavior, whatever
    }
}
class B : A
{
    private int[] x;
    public override object Clone()
    {
        var copy = (B)base.Clone();  // known downcast (static_cast in C++)
        copy.x = (int[])this.x.Clone();  // oh hey, another downcast!!
        return copy;
    }
}

Stream.EndRead/Write:

class MyStream : Stream
{
    private class AsyncResult : IAsyncResult
    {
        // ...
    }
    public override int EndRead(IAsyncResult other)
    {
        return Blah((AsyncResult)other);  // another downcast (likely ~static_cast)
    }
}

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


9
"กลิ่นรหัส" ไม่ได้หมายความว่า "การออกแบบนี้ไม่ดีอย่างแน่นอน " มันหมายถึง "การออกแบบนี้น่าสงสัย " ว่ามีคุณสมบัติภาษาที่น่าสงสัยโดยเนื้อแท้เป็นเรื่องของลัทธิปฏิบัตินิยมในส่วนของผู้เขียนภาษา
Caleth

5
@Caleth: และจุดทั้งหมดของฉันคือการออกแบบเหล่านี้เกิดขึ้นตลอดเวลาและไม่มีอะไรที่น่าสงสัยอย่างแน่นอนเกี่ยวกับการออกแบบที่ฉันได้แสดงและดังนั้นการหล่อจึงไม่ใช่กลิ่นรหัส
Mehrdad

4
@Caleth ฉันไม่เห็นด้วย สำหรับฉันรหัสกลิ่นหมายความว่ารหัสไม่ดีหรืออย่างน้อยที่สุดก็สามารถปรับปรุงได้
Konrad Rudolph

6
@ Mehrdad ความคิดเห็นของคุณน่าจะเป็นรหัสที่มีกลิ่นต้องเป็นความผิดของโปรแกรมเมอร์แอปพลิเคชัน ทำไม? ไม่ใช่ว่าทุกกลิ่นรหัสจะต้องเป็นปัญหาจริงหรือมีทางออก รหัสกลิ่นตาม Martin Fowler เป็นเพียง "ตัวบ่งชี้พื้นผิวที่มักจะสอดคล้องกับปัญหาที่ลึกกว่าในระบบ" object.Equalsเหมาะกับคำอธิบายที่ค่อนข้างดี (พยายามที่จะให้สัญญาที่ถูกต้องเท่ากับในซูเปอร์คลาสที่ช่วยให้ subclasses ใช้อย่างถูกต้อง ไม่น่ารำคาญ) ในฐานะที่เป็นโปรแกรมเมอร์แอปพลิเคชันเราไม่สามารถแก้ปัญหาได้ (ในบางกรณีเราสามารถหลีกเลี่ยงได้) เป็นหัวข้อที่แตกต่าง
Voo

5
@ Mehrdad มาดูกันว่านักออกแบบภาษาดั้งเดิมคนใดที่พูดถึง Equals .. Object.Equals is horrid. Equality computation in C# is completely messed up(ความเห็นของ Eric ในคำตอบของเขา) เป็นเรื่องตลกที่คุณไม่ต้องการพิจารณาความเห็นของคุณอีกครั้งแม้ว่านักออกแบบนำคนหนึ่งของภาษาจะบอกคุณว่าคุณคิดผิด
Voo

135

Downcasting นั้นเป็นที่นิยมอาจจะเป็นกลิ่นรหัส

ฉันไม่เห็นด้วย. Downcasting เป็นที่นิยมอย่างมาก ; โปรแกรมในโลกแห่งความจริงจำนวนมากประกอบด้วยการดาวน์สตรีมอย่างน้อยหนึ่งรายการ และอาจไม่ได้กลิ่นรหัส มันเป็นกลิ่นรหัส แน่นอน นั่นเป็นเหตุผลที่การดำเนินงาน downcasting จะต้องเป็นที่ประจักษ์ในข้อความของโปรแกรม มันคือเพื่อให้คุณสามารถสังเกตเห็นกลิ่นได้ง่ายขึ้นและใช้รหัสตรวจสอบความสนใจกับมัน

ในกรณีใด [s] มันเหมาะสมที่จะเขียนรหัสที่ downcasts?

ไม่ว่าในกรณีใด:

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

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

ทำไม downcasting จึงรองรับภาษา

C # ถูกคิดค้นโดยโปรแกรมเมอร์ในทางปฏิบัติที่มีงานต้องทำสำหรับโปรแกรมเมอร์ในทางปฏิบัติที่มีงานต้องทำ ผู้ออกแบบ C # ไม่ใช่คนเจ้าระเบียบ OO และระบบประเภท C # นั้นไม่สมบูรณ์แบบ; โดยการออกแบบให้ประเมินข้อ จำกัด ที่สามารถวางลงบนชนิดรันไทม์ของตัวแปรได้

นอกจากนี้ downcasting มีความปลอดภัยมากใน C # เรามีการรับประกันที่ดีว่า downcast จะถูกตรวจสอบที่ runtime และหากไม่สามารถตรวจสอบได้โปรแกรมจะทำสิ่งที่ถูกต้องและขัดข้อง มันวิเศษมาก หมายความว่าหากความเข้าใจที่ถูกต้อง 100% ของคุณเกี่ยวกับซีแมนทิกส์ประเภทเปลี่ยนเป็น 99.99% ถูกต้องโปรแกรมของคุณจะทำงาน 99.99% ของเวลาและหยุดเวลาที่เหลือแทนการทำงานที่ไม่คาดคิดและทำลายข้อมูลผู้ใช้ 0.01% ของเวลา .


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


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

14
@EricLippert คำสั่ง foreach แน่นอน สิ่งนี้ทำมาก่อน IEnumerable ทั่วไปใช่ไหม?
Arturo Torres Sánchez

18
คำอธิบายนี้ทำให้วันของฉัน ในฐานะครูฉันมักถูกถามว่าทำไม C # ได้รับการออกแบบด้วยวิธีนี้หรือไม่และคำตอบของฉันมักจะมาจากแรงจูงใจที่คุณและเพื่อนร่วมงานแบ่งปันกันที่นี่และในบล็อกของคุณ ก็มักจะเป็นบิตยากที่จะตอบคำถามติดตาม "แต่Whyyyyy ?!" และที่นี่ฉันพบคำตอบติดตามที่สมบูรณ์แบบ: "C # ถูกคิดค้นโดยโปรแกรมเมอร์ในทางปฏิบัติที่มีงานต้องทำสำหรับโปรแกรมเมอร์ในทางปฏิบัติที่มีงานต้องทำ" :-) ขอบคุณ @EricLippert!
Zano

12
นอกเหนือจากชัดเจนในความคิดเห็นของฉันข้างต้นฉันหมายถึงว่าเรามีdowncastจาก A ถึง B ฉันไม่ชอบการใช้ "up" และ "down" และ "parent" และ "parent" และ "child" เมื่อนำทางไปยังลำดับชั้นของชั้นเรียน ผิดตลอดเวลาในการเขียนของฉัน ความสัมพันธ์ระหว่างประเภทคือความสัมพันธ์แบบซูเปอร์เซ็ต / เซ็ตดังนั้นทำไมจึงควรมีทิศทางขึ้นหรือลงที่สัมพันธ์กับมันฉันไม่รู้ และอย่าให้ฉันเริ่มต้นด้วย "parent" ผู้ปกครองของยีราฟไม่ใช่สัตว์เลี้ยงลูกด้วยนมผู้เป็นแม่ของยีราฟคือนายและนางยีราฟ
Eric Lippert

9
Object.Equalsเป็นน่าเกลียดน่ากลัว การคำนวณความเท่าเทียมกันใน C # นั้นถูกทำให้ยุ่งเหยิงอย่างสมบูรณ์และสับสนเกินไปที่จะอธิบายในความคิดเห็น มีหลายวิธีในการแสดงความเสมอภาค มันง่ายมากที่จะทำให้มันไม่สอดคล้องกัน มันง่ายมากในความเป็นจริงที่จำเป็นในการละเมิดคุณสมบัติที่ต้องการของผู้ประกอบการความเท่าเทียมกัน (สมมาตร, การสะท้อนกลับและความไวแสง) ฉันได้ออกแบบระบบพิมพ์ตั้งแต่เริ่มต้นวันนี้จะไม่มีEquals(หรือGetHashcodeหรือToString) Objectเลย
Eric Lippert

33

MethodName(object sender, EventArgs e)จัดการเหตุการณ์มักจะมีลายเซ็น ในบางกรณีเป็นไปได้ที่จะจัดการกับเหตุการณ์โดยไม่คำนึงถึงประเภทsenderใดหรือแม้กระทั่งโดยไม่ใช้senderเลย แต่ในบางกรณีsenderจะต้องใช้ชนิดที่เจาะจงมากขึ้นเพื่อจัดการกับเหตุการณ์

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

private void TextBox_TextChanged(object sender, EventArgs e)
{
   TextBox box = (TextBox)sender;
   box.BackColor = string.IsNullOrEmpty(box.Text) ? Color.Red : Color.White;
}

ใช่ในตัวอย่างนี้คุณสามารถสร้างคลาสย่อยของคุณเองTextBoxซึ่งทำภายในได้ แม้ว่าจะไม่ได้ใช้งานหรือเป็นไปได้เสมอไป


2
ขอบคุณนั่นเป็นตัวอย่างที่ดี TextChangedเหตุการณ์เป็นสมาชิกของControl- ผมสงสัยว่าทำไมsenderไม่ได้ของการพิมพ์Controlแทนประเภทobject? นอกจากนี้ฉันยังสงสัยว่า (ในทางทฤษฎี) เฟรมเวิร์กสามารถกำหนดเหตุการณ์ใหม่สำหรับแต่ละคลาสย่อยได้หรือไม่ (เช่นTextBoxนั้นอาจมีเวอร์ชันใหม่ของเหตุการณ์โดยใช้TextBoxเป็นประเภทของผู้ส่ง) ... ที่อาจ (หรืออาจไม่) ไปที่TextBox) ภายในการTextBoxปรับใช้ (เพื่อใช้งานประเภทเหตุการณ์ใหม่) แต่จะหลีกเลี่ยงการต้องการดาวน์สตรีมในตัวจัดการเหตุการณ์ของรหัสแอปพลิเคชัน
ChrisW

3
สิ่งนี้ถือว่ายอมรับได้เพราะเป็นการยากที่จะสรุปการจัดการเหตุการณ์หากทุกเหตุการณ์มีลายเซ็นวิธีการของตนเอง แม้ว่าฉันคิดว่ามันเป็นข้อ จำกัด ที่ยอมรับได้มากกว่าสิ่งที่ถือว่าเป็นแนวปฏิบัติที่ดีที่สุดเพราะทางเลือกนั้นลำบากกว่าจริง ๆ ถ้ามันเป็นไปได้ที่จะเริ่มต้นด้วยTextBox senderมากกว่าobject senderโดยไม่ต้องสุดเหวี่ยงแทรกซ้อนรหัสที่ฉันแน่ใจว่ามันจะทำ
Neil

6
@Neil ระบบอีเวนติ้งทำขึ้นเป็นพิเศษเพื่อให้ส่งต่อข้อมูลโดยพลการไปยังวัตถุใด ๆ มันเป็นไปไม่ได้ที่จะทำโดยไม่มีการตรวจสอบความถูกต้องของรันไทม์
Joker_vD

@Joker_vD ตามหลักการแล้ว API น่าจะสนับสนุนลายเซ็นการติดต่อกลับที่พิมพ์ออกมาอย่างมากโดยใช้ contravariance ทั่วไป ความจริงที่. NET (ขาดลอย) ไม่ได้ทำเช่นนี้เป็นเพียงความจริงที่ว่า generics และความแปรปรวนร่วมถูกนำมาใช้ในภายหลัง - กล่าวอีกนัยหนึ่งการดาวน์สตรีมที่นี่ (และโดยทั่วไปที่อื่น ๆ ) เป็นสิ่งจำเป็นเนื่องจากความไม่เพียงพอในภาษา / API ไม่ใช่เพราะเป็นแนวคิดที่ดี
Konrad Rudolph

@ KonradRudolph แน่นอน แต่คุณจะเก็บเหตุการณ์ประเภทต่าง ๆ ไว้ในคิวเหตุการณ์ได้อย่างไร คุณเพียงแค่กำจัดการเข้าคิวและไปส่งโดยตรงหรือไม่
Joker_vD

17

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

decimal value;
Donation d = user.getDonation();
if (d is CashDonation) {
     value = ((CashDonation)d).getValue();
}
if (d is ItemDonation) {
     value = itemValueEstimator.estimateValueOf(((ItemDonation)d).getItem());
}
System.out.println("Thank you for your generous donation of $" + value);

ไม่นี่ไม่ได้กลิ่นที่ดี ในโลกที่สมบูรณ์แบบDonationจะมีวิธีที่เป็นนามธรรมgetValueและแต่ละการใช้ย่อยจะมีการใช้งานที่เหมาะสม

แต่ถ้าชั้นเรียนเหล่านี้มาจากห้องสมุดคุณไม่สามารถหรือไม่ต้องการที่จะเปลี่ยน? จากนั้นไม่มีตัวเลือกอื่น


ดูเหมือนว่าเป็นเวลาที่ดีที่จะใช้รูปแบบผู้เข้าชม หรือdynamicคำหลักที่บรรลุผลลัพธ์เดียวกัน
user2023861

3
@ user2023861 ไม่ฉันคิดว่ารูปแบบของผู้เข้าชมขึ้นอยู่กับความร่วมมือจากคลาสย่อยการบริจาค - นั่นคือการบริจาคจำเป็นต้องประกาศนามธรรมvoid accept(IDonationVisitor visitor)ซึ่งคลาสย่อยนั้นดำเนินการโดยเรียกวิธีการIDonationVisitorเฉพาะ
ChrisW

@ChrisW คุณพูดถูก หากไม่มีความร่วมมือคุณสามารถใช้dynamicคำหลักนั้นได้
user2023861

ในกรณีนี้คุณสามารถเพิ่มวิธีการประมาณมูลค่าให้กับการบริจาค
user253751

@ user2023861: dynamicยังคงdowncasting เพียงช้าลง
Mehrdad

12

เพื่อเพิ่มคำตอบของ Eric Lippertเนื่องจากฉันไม่สามารถแสดงความคิดเห็น ...

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


อันที่จริงใคร ๆ ก็บอกว่า generics ใน Java หรือ C # เป็นเพียงแค่ wrapper ที่ปลอดภัยรอบ ๆ การจัดเก็บวัตถุ superclass และ downcasting เพื่อ subclass (ตรวจสอบคอมไพเลอร์) ที่ถูกต้องก่อนที่จะใช้องค์ประกอบอีกครั้ง (แตกต่างจากเทมเพลต C ++ ซึ่งเขียนโค้ดเพื่อใช้คลาสย่อยเสมอและหลีกเลี่ยงการดาวน์ - ซึ่งสามารถเพิ่มประสิทธิภาพของหน่วยความจำหากใช้เวลาในการรวบรวมและขนาดที่สามารถใช้งานได้)
leftaroundabout

4

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

มีโครงสร้าง "ไม่ปลอดภัย" ที่ช่วยให้คุณทำการยืนยันที่ไม่ได้พิสูจน์ให้คอมไพเลอร์ ตัวอย่างเช่นการโทรแบบไม่มีเงื่อนไขNullable.Value, downcasts ที่ไม่มีเงื่อนไข, dynamicวัตถุ, ฯลฯ พวกเขาอนุญาตให้คุณยืนยันการอ้างสิทธิ์ ("ฉันยืนยันว่าวัตถุaนั้นเป็นStringถ้าฉันผิดโยนInvalidCastException") โดยไม่จำเป็นต้องพิสูจน์มัน สิ่งนี้มีประโยชน์ในกรณีที่การพิสูจน์ว่าหนักกว่าคุ้มค่าอย่างมาก

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


ฉันเดาว่าฉันถามแล้วตัวอย่างเช่น [s] ของที่ไหน / เมื่อมีความจำเป็นหรือเป็นที่ต้องการ (เช่นแนวปฏิบัติที่ดี) เพื่อสร้าง "การยืนยันที่ไม่ได้รับการพิสูจน์"
ChrisW

1
วิธีการคัดเลือกผลลัพธ์ของการสะท้อนกลับ stackoverflow.com/a/3255716/3141234
Alexander

3

การดาวน์สตรีมจะถือว่าไม่ดีด้วยเหตุผลสองประการ ในขั้นต้นฉันคิดว่าเพราะมันต่อต้าน OOP

OOP จะชอบมันมากถ้าคุณไม่เคยเศร้าใจเลยเพราะ '' raison-d'etre '' คือ polymorphism หมายความว่าคุณไม่ต้องไปไหน

if(object is X)
{
    //do the thing we do with X's
}
else
{
    //of the thing we do with Y's
}

คุณทำ

x.DoThing()

และรหัสอัตโนมัติอย่างน่าอัศจรรย์ทำสิ่งที่ถูกต้อง

มีเหตุผลบางอย่าง 'ยาก' ที่จะไม่ยอมแพ้:

  • ใน C ++ มันช้า
  • คุณได้รับข้อผิดพลาด runtime หากคุณเลือกประเภทที่ไม่ถูกต้อง

แต่ทางเลือกในการลดความเร็วในบางสถานการณ์อาจดูน่าเกลียด

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

คุณสามารถใช้ Double Dispatch เพื่อแก้ไขปัญหา ( https://en.wikipedia.org/wiki/Double_dispatch ) ... แต่นั่นก็มีปัญหาเช่นกัน หรือคุณเพียงแค่มองหารหัสง่ายๆที่มีความเสี่ยงจากเหตุผลที่ยากเหล่านั้น

แต่นี่เป็นก่อนที่ Generics ถูกประดิษฐ์ขึ้น ตอนนี้คุณสามารถหลีกเลี่ยงการดาวน์สตรีมด้วยการระบุประเภทที่ระบุรายละเอียดในภายหลัง

MessageProccesor<T> สามารถเปลี่ยนแปลงพารามิเตอร์วิธีการและค่าส่งคืนตามประเภทที่ระบุ แต่ยังคงมีฟังก์ชั่นทั่วไป

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


1
ฉันสงสัยมันช้าใน C ++ โดยเฉพาะอย่างยิ่งถ้าคุณแทนstatic_cast dynamic_cast
ChrisW

"การประมวลผลข้อความ" - ฉันคิดว่าเป็นความหมายเช่นการส่งlParamไปยังบางสิ่งที่เฉพาะเจาะจง ฉันไม่แน่ใจว่าเป็นตัวอย่าง C # ที่ดี
ChrisW

3
@ChrisW - ดีใช่static_castรวดเร็วเสมอ ... แต่ไม่รับประกันว่าจะไม่ล้มเหลวในรูปแบบที่ไม่ได้กำหนดหากคุณทำผิดพลาดและประเภทไม่ใช่สิ่งที่คุณคาดหวัง
จูลส์

@Jules: นั่นเป็นจุดที่สมบูรณ์
Mehrdad

@ Mehrdad มันเป็นจุดที่แน่นอน การทำ c # cast ที่เทียบเท่าใน c ++ นั้นช้า และนี่คือเหตุผลดั้งเดิมที่ต่อต้านการคัดเลือกนักแสดง ทางเลือก static_cast ไม่เทียบเท่าและถือว่าเป็นตัวเลือกที่แย่กว่าเนื่องจากพฤติกรรมที่ไม่ได้กำหนด
Ewan

0

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

โดยทั่วไปการไม่ใช้บางสิ่งบางอย่างจนกระทั่งทุกวันนี้ไม่ได้บ่งบอกถึงบางสิ่งที่ไร้ประโยชน์เสมอ ;-)


ใช่ดูตัวอย่างสิ่งที่ใช้เป็นแท็คุณสมบัติในสุทธิ หนึ่งในคำตอบที่นั่นมีคนเขียนฉันใช้เพื่อป้อนคำแนะนำให้กับผู้ใช้ในแอปพลิเคชัน Windows Forms เมื่อการควบคุมเหตุการณ์ GotFocus ถูกเรียกคำสั่งคุณสมบัติ Label.Text ได้รับการกำหนดค่าของคุณสมบัติแท็กการควบคุมของฉันซึ่งมีสตริงคำสั่ง ฉันเดาวิธีอื่นในการ 'ขยาย' the Control(แทนที่จะใช้object Tagคุณสมบัติ) คือการสร้างDictionary<Control, string>ที่เก็บป้ายสำหรับแต่ละตัวควบคุม
ChrisW

1
ฉันชอบคำตอบนี้เพราะTagเป็นคุณสมบัติสาธารณะของคลาสเฟรมเวิร์ก. Net ซึ่งแสดงให้เห็นว่าคุณควรใช้มันมากหรือน้อย
ChrisW

@ChrisW: ไม่ได้ที่จะบอกว่าข้อสรุปที่เป็นธรรม (หรือขวา) แต่ทราบว่านั่นคือเหตุผลเลอะเทอะเพราะมีความอุดมสมบูรณ์ของการทำงาน .NET สาธารณะที่เป็นล้าสมัย (พูดSystem.Collections.ArrayList) หรือเลิกเป็นอย่างอื่น (พูดIEnumerator.Reset)
Mehrdad

0

การใช้ downcasting ที่พบบ่อยที่สุดในงานของฉันเป็นเพราะคนบ้าบางคนทำลายหลักการทดแทนของ Liskov

ลองนึกภาพว่ามีอินเทอร์เฟซใน lib บุคคลที่สามบางส่วน

public interface Foo
{
     void SayHello();
     void SayGoodbye();
 }

และ 2 คลาสที่ติดตั้ง และBar มีความประพฤติดีQuxBar

public class Bar
{
    void SayHello()
    {
        Console.WriteLine(“Bar says hello.”);
    }
    void SayGoodbye()
    {
         Console.WriteLine(“Bar says goodbye”);
    }
}

แต่Quxก็ไม่ได้ประพฤติดี

public class Qux
{
    void SayHello()
    {
        Console.WriteLine(“Qux says hello.”);
    }
    void SayGoodbye()
    {
        throw new NotImplementedException();
    }
}

อืม ... ตอนนี้ฉันไม่มีทางเลือก ฉันต้องพิมพ์การตรวจสอบและ (อาจ) downcast เพื่อหลีกเลี่ยงการให้โปรแกรมของฉันหยุดชะงัก


1
ไม่เป็นความจริงสำหรับตัวอย่างนี้ คุณสามารถจับ NotImplementedException เมื่อโทร SayGoodbye
ต่อ von Zweigbergk

มันเป็นตัวอย่างที่เรียบง่ายเกินไปแต่อาจทำงานกับ. NET API เก่า ๆ สักหน่อยและคุณจะพบกับสถานการณ์นี้ บางครั้งวิธีที่ง่ายที่สุดในการเขียนโค้ดคือการโยน var qux = foo as Qux;Ala
RubberDuck

0

อีกตัวอย่างจริงของโลกเป็นของ VisualTreeHelperWPF มันใช้เศร้าใจที่จะโยนDependencyObjectไปและVisual Visual3Dยกตัวอย่างเช่นVisualTreeHelper.GetParent ดาวน์สตรีมเกิดขึ้นในVisualTreeUtils.AsVisualHelper :

private static bool AsVisualHelper(DependencyObject element, out Visual visual, out Visual3D visual3D)
{
    Visual elementAsVisual = element as Visual;

    if (elementAsVisual != null)
    {
        visual = elementAsVisual;
        visual3D = null;
        return true;
    }

    Visual3D elementAsVisual3D = element as Visual3D;

    if (elementAsVisual3D != null)
    {
        visual = null;
        visual3D = elementAsVisual3D;
        return true;
    }            

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