คัดลอกตัวสร้างเทียบกับ Clone ()


120

ใน C # วิธีใดที่ต้องการเพิ่มฟังก์ชันการคัดลอก (ลึก) ลงในคลาส ควรใช้ตัวสร้างการคัดลอกหรือได้มาจากICloneableและใช้Clone()วิธีการนี้หรือไม่?

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

คำตอบ:


91

ICloneableคุณไม่ควรมาจาก

เหตุผลก็คือเมื่อ Microsoft ออกแบบ. net framework พวกเขาไม่เคยระบุว่าClone()เมธอดบนICloneableควรเป็นโคลนลึกหรือตื้นดังนั้นอินเทอร์เฟซจึงเสียความหมายเนื่องจากผู้โทรของคุณไม่ทราบว่าการโทรจะโคลนวัตถุลึกหรือตื้น

แต่คุณควรกำหนดเองของคุณIDeepCloneable(และIShallowCloneable) การเชื่อมต่อกับDeepClone()(และShallowClone()) วิธีการ

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

public interface IDeepCloneable
{
    object DeepClone();
}
public interface IDeepCloneable<T> : IDeepCloneable
{
    T DeepClone();
}

ซึ่งคุณจะดำเนินการดังนี้:

public class SampleClass : IDeepCloneable<SampleClass>
{
    public SampleClass DeepClone()
    {
        // Deep clone your object
        return ...;
    }
    object IDeepCloneable.DeepClone()   
    {
        return this.DeepClone();
    }
}

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

สิ่งนี้จะกล่าวถึงในแนวทางการออกแบบ . net Frameworkและในบล็อกของ Brad Abrams

(ฉันคิดว่าถ้าคุณกำลังเขียนแอปพลิเคชัน (ตรงข้ามกับเฟรมเวิร์ก / ไลบรารี) ดังนั้นคุณสามารถมั่นใจได้ว่าจะไม่มีใครนอกทีมของคุณเรียกรหัสของคุณมันไม่สำคัญมากนักและคุณสามารถกำหนดความหมายเชิงความหมายของ "deepclone" ไปยังอินเทอร์เฟซ. net ICloneable แต่คุณควรตรวจสอบให้แน่ใจว่าสิ่งนี้ได้รับการบันทึกไว้เป็นอย่างดีและเป็นที่เข้าใจกันดีภายในทีมของคุณโดยส่วนตัวแล้วฉันจะยึดตามแนวทางของกรอบงาน)


2
หากคุณกำลังมองหาอินเทอร์เฟซการมี DeepClone (ของ T) () และ DeepClone (ของ T) (ดัมมี่เป็น T) ทั้งสองอย่างจะคืนค่า T อย่างไร ไวยากรณ์หลังจะช่วยให้ T สามารถอนุมานได้ตามอาร์กิวเมนต์
supercat

@supercat: คุณกำลังบอกว่ามีพารามิเตอร์ดัมมี่เพื่อให้สามารถอนุมานประเภทได้หรือไม่? เป็นตัวเลือกที่ฉันคิดว่า ฉันไม่แน่ใจว่าฉันชอบมีพารามิเตอร์ดัมมี่เพียงเพื่อให้ประเภทที่อนุมานโดยอัตโนมัติ บางทีฉันอาจเข้าใจคุณผิด (อาจจะโพสต์โค้ดในคำตอบใหม่เพื่อให้ฉันเห็นความหมายของคุณ)
Simon P Stevens

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

2
คำถาม! ในสถานการณ์ใดที่คุณต้องการเวอร์ชันที่ไม่ใช่ทั่วไป สำหรับฉันแล้วมันสมเหตุสมผลที่IDeepCloneable<T>จะมีอยู่เพราะ ... คุณรู้ว่า T อะไรถ้าคุณนำไปใช้งานของคุณเองนั่นคือSomeClass : IDeepCloneable<SomeClass> { ... }
Kyle Baran

2
@ ไคล์บอกว่าคุณมีวิธีการที่เอาวัตถุที่MyFunc(IDeepClonable data)สามารถลอกเลียนแบบได้แล้วมันก็สามารถใช้ได้กับโคลนนิ่งทั้งหมดไม่ใช่เฉพาะบางประเภท หรือถ้าคุณมีคอลเลกชันของโคลน IEnumerable<IDeepClonable> lotsOfCloneablesจากนั้นคุณสามารถโคลนวัตถุจำนวนมากได้ในเวลาเดียวกัน หากคุณไม่ต้องการสิ่งนั้นให้ปล่อยสิ่งที่ไม่ใช่ทั่วไปออกไป
Simon P Stevens

33

ใน C # วิธีใดที่ต้องการเพิ่มฟังก์ชันการคัดลอก (ลึก) ลงในคลาส ควรใช้ตัวสร้างการคัดลอกหรือค่อนข้างมาจาก ICloneable และใช้วิธี Clone ()?

ปัญหาICloneableคือตามที่คนอื่น ๆ กล่าวถึงว่าไม่ได้ระบุว่าเป็นสำเนาลึกหรือตื้นซึ่งทำให้ไม่สามารถใช้งานได้จริงและในทางปฏิบัติแทบไม่ได้ใช้ นอกจากนี้ยังส่งกลับobjectซึ่งเป็นความเจ็บปวดเนื่องจากต้องใช้การหล่อมาก (และแม้ว่าคุณกล่าวถึงเฉพาะการเรียนในคำถามที่ดำเนินการICloneableบนstructต้องมวย.)

ตัวสร้างสำเนายังประสบปัญหาอย่างใดอย่างหนึ่งกับ ICloneable ไม่ชัดเจนว่าตัวสร้างสำเนากำลังทำสำเนาลึกหรือตื้น

Account clonedAccount = new Account(currentAccount); // Deep or shallow?

การสร้างเมธอด DeepClone () จะเป็นการดีที่สุด วิธีนี้เจตนาชัดเจนอย่างสมบูรณ์

สิ่งนี้ทำให้เกิดคำถามว่าควรเป็นวิธีแบบคงที่หรืออินสแตนซ์

Account clonedAccount = currentAccount.DeepClone();  // instance method

หรือ

Account clonedAccount = Account.DeepClone(currentAccount); // static method

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

class CheckingAccount : Account
{
    CheckAuthorizationScheme checkAuthorizationScheme;

    public override Account DeepClone()
    {
        CheckingAccount clone = new CheckingAccount();
        DeepCloneFields(clone);
        return clone;
    }

    protected override void DeepCloneFields(Account clone)
    {
        base.DeepCloneFields(clone);

        ((CheckingAccount)clone).checkAuthorizationScheme = this.checkAuthorizationScheme.DeepClone();
    }
}

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

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

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

8
@KenBeckett - เหตุผลที่ฉันรู้สึกว่าการโคลนนิ่งเป็นสิ่งที่ทำกับวัตถุเพราะวัตถุควร "ทำสิ่งหนึ่งและทำได้ดี" โดยปกติการทำสำเนาของตัวเองไม่ใช่ความสามารถหลักของคลาส แต่เป็นฟังก์ชันการทำงานที่ติดอยู่ การทำบัญชี BankAccount เป็นสิ่งที่คุณอาจต้องการทำ แต่การทำโคลนด้วยตัวเองไม่ใช่คุณลักษณะของบัญชีธนาคาร ตัวอย่างเซลล์ของคุณไม่ได้ให้คำแนะนำในวงกว้างเนื่องจากการสืบพันธุ์เป็นสิ่งที่เซลล์วิวัฒนาการมาเพื่อทำ Cell.Clone เป็นวิธีการอินสแตนซ์ที่ดี แต่สิ่งนี้ไม่เป็นความจริงสำหรับสิ่งอื่น ๆ ส่วนใหญ่
Jeffrey L Whitledge

23

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

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

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

Ex:

public class BaseType {
   readonly int mBaseField;

   public BaseType(BaseType pSource) =>
      mBaseField = pSource.mBaseField;

   public virtual BaseType Clone() =>
      new BaseType(this);
}

public class SubType : BaseType {
   readonly int mSubField;

   public SubType(SubType pSource)
   : base(pSource) =>
      mSubField = pSource.mSubField;

   public override BaseType Clone() =>
      new SubType(this);
}

8
+1 สำหรับการจัดการโคลนโพลีมอร์ฟิค การประยุกต์ใช้การโคลนอย่างมีนัยสำคัญ
samis

18

มีข้อโต้แย้งที่ดีว่าคุณควรใช้ clone () โดยใช้ตัวสร้างสำเนาที่มีการป้องกัน

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

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

(แม้ว่าอินเทอร์เฟซสาธารณะที่แนะนำคืออินเทอร์เฟซ Clone () แทนที่จะใช้ Constructor-based)

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

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


"ไม่ควรเป็นข้อกังวลของผู้โทร". ฉันไม่สามารถเห็นด้วยมากขึ้น แต่ที่นี่ฉันกำลังพยายามหาว่า List <T> aList = new List <T> (aFullListOfT) จะทำสำเนาแบบลึก (ซึ่งเป็นสิ่งที่ฉันต้องการ) หรือสำเนาตื้น (ซึ่งจะแตก รหัสของฉัน) และฉันต้องใช้วิธีอื่นเพื่อให้งานสำเร็จหรือไม่!
ThunderGr

3
รายการ <T> เป็นเรื่องธรรมดาเกินไป (ฮ่าฮ่า) สำหรับการโคลนจะทำให้เข้าใจได้ ในกรณีของคุณนั่นเป็นเพียงสำเนาของรายการเท่านั้นไม่ใช่วัตถุที่รายการชี้ไป การจัดการรายการใหม่จะไม่ส่งผลต่อรายการแรก แต่วัตถุจะเหมือนกันและเว้นแต่จะไม่เปลี่ยนรูปวัตถุในชุดแรกจะเปลี่ยนไปหากคุณเปลี่ยนรายการในชุดที่สอง หากมีการดำเนินการ list.Clone () ในไลบรารีของคุณคุณควรคาดหวังว่าผลลัพธ์จะเป็นโคลนเต็มรูปแบบเช่นเดียวกับ "จะไม่เปลี่ยนแปลงเมื่อฉันทำบางอย่างกับสิ่งแรก" ที่ใช้กับวัตถุที่มีอยู่เช่นกัน
DanO

1
รายการ <T> จะไม่ทราบอะไรเพิ่มเติมเกี่ยวกับการโคลนเนื้อหาอย่างถูกต้องมากกว่าที่คุณเป็น หากวัตถุต้นแบบไม่เปลี่ยนรูปคุณก็พร้อมที่จะไป มิฉะนั้นหากวัตถุต้นแบบมีเมธอด Clone () คุณจะต้องใช้มัน รายการ <T> aList = รายการใหม่ <T> (aFullListOfT.Select (t = t.Clone ())
DanO

1
+1 สำหรับแนวทางไฮบริด ทั้งสองวิธีมีข้อดีและข้อเสีย แต่ดูเหมือนว่าจะมีประโยชน์โดยรวมมากกว่า
Kyle Baran

12

ไม่แนะนำให้ใช้ ICloneableเนื่องจากไม่ได้ระบุว่าเป็นสำเนาลึกหรือตื้นดังนั้นฉันจะไปหาผู้สร้างหรือเพียงแค่ใช้บางอย่างด้วยตัวเอง อาจจะเรียกมันว่า DeepCopy () เพื่อให้ชัดเจนจริงๆ!


5
@ แกรนท์ผู้สร้างถ่ายทอดเจตนาอย่างไร? IOW ถ้าวัตถุเข้ามาในตัวสร้างสำเนานั้นลึกหรือตื้น? มิฉะนั้นฉันเห็นด้วยอย่างสมบูรณ์กับข้อเสนอแนะของ DeepCopy () (หรืออื่น ๆ )
Marc

7
ฉันขอยืนยันว่าตัวสร้างเกือบจะไม่ชัดเจนเท่ากับอินเทอร์เฟซ ICloneable - คุณต้องอ่านเอกสาร / รหัส API เพื่อให้รู้ว่ามันเป็นโคลนลึกหรือไม่ ฉันแค่กำหนดไฟล์IDeepCloneable<T>อินเทอร์เฟซด้วยDeepClone()วิธีการ
Kent Boogaart

2
@ จอน - การทำปฏิกิริยายังไม่เสร็จสิ้น!
Crofton

@Marc, @Kent - ใช่จุดที่ยุติธรรมผู้สร้างอาจไม่ใช่ความคิดที่ดีเช่นกัน
Crofton

3
มีใครเห็นการใช้งานที่ iCloneable ใช้กับวัตถุที่ไม่รู้จักประเภทนี้บ้าง? จุดเชื่อมต่อทั้งหมดคือสามารถใช้กับวัตถุที่ไม่รู้จักประเภท; มิฉะนั้นอาจทำให้ Clone เป็นวิธีมาตรฐานที่ส่งคืนประเภทที่เป็นปัญหา
supercat

12

คุณจะพบปัญหาเกี่ยวกับตัวสร้างการคัดลอกและคลาสนามธรรม ลองนึกภาพว่าคุณต้องการทำสิ่งต่อไปนี้:

abstract class A
{
    public A()
    {
    }

    public A(A ToCopy)
    {
        X = ToCopy.X;
    }
    public int X;
}

class B : A
{
    public B()
    {
    }

    public B(B ToCopy) : base(ToCopy)
    {
        Y = ToCopy.Y;
    }
    public int Y;
}

class C : A
{
    public C()
    {
    }

    public C(C ToCopy)
        : base(ToCopy)
    {
        Z = ToCopy.Z;
    }
    public int Z;
}

class Program
{
    static void Main(string[] args)
    {
        List<A> list = new List<A>();

        B b = new B();
        b.X = 1;
        b.Y = 2;
        list.Add(b);

        C c = new C();
        c.X = 3;
        c.Z = 4;
        list.Add(c);

        List<A> cloneList = new List<A>();

        //Won't work
        //foreach (A a in list)
        //    cloneList.Add(new A(a)); //Not this time batman!

        //Works, but is nasty for anything less contrived than this example.
        foreach (A a in list)
        {
            if(a is B)
                cloneList.Add(new B((B)a));
            if (a is C)
                cloneList.Add(new C((C)a));
        }
    }
}

หลังจากทำตามข้างต้นแล้วคุณจะเริ่มคิดว่าจะใช้อินเทอร์เฟซหรือตกลงสำหรับการใช้งาน DeepCopy () / ICloneable.Clone ()


2
ข้อโต้แย้งที่ดีสำหรับแนวทางที่ใช้อินเทอร์เฟซ
DanO

4

ปัญหาเกี่ยวกับ ICloneable มีทั้งเจตนาและความสม่ำเสมอ ไม่เคยชัดเจนว่าเป็นสำเนาลึกหรือตื้น ด้วยเหตุนี้จึงอาจไม่เคยใช้ในลักษณะใดลักษณะหนึ่งเท่านั้น

ฉันไม่พบตัวสร้างสำเนาสาธารณะที่ชัดเจนในเรื่องนั้น

ที่กล่าวว่าฉันจะแนะนำระบบวิธีการที่เหมาะกับคุณและถ่ายทอดเจตนา (a'la ค่อนข้างจัดทำเอกสารด้วยตัวเอง)


3

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

ตอนนี้ฉันไม่สามารถเข้าถึงรหัสได้ แต่เป็นแบบนี้

public object DeepCopy(object source)
{
   // Copy with Binary Serialization if the object supports it
   // If not try copying with XML Serialization
   // If not try copying with Data contract Serailizer, etc
}

6
การใช้การทำให้เป็นอนุกรมเป็นวิธีในการใช้การโคลนนิ่งแบบลึกไม่เกี่ยวข้องกับคำถามที่ว่าควรใช้โคลนลึกเป็น ctor หรือวิธีการ
Kent Boogaart

1
ผมว่าเป็นอีกทางเลือกที่ใช้ได้ ฉันไม่คิดว่าเขา จำกัด เพียงสองวิธีในการคัดลอกลึก ๆ
Shaun Bowe

5
@Kent Boogaart - เมื่อ OP เริ่มต้นด้วยบรรทัด "ใน C # วิธีใดที่ดีที่สุดในการเพิ่มฟังก์ชันการคัดลอก (แบบลึก) ลงในคลาส" ฉันคิดว่ามันยุติธรรมพอที่ Shaun จะเสนอทางเลือกอื่น ๆ โดยเฉพาะอย่างยิ่งในสถานการณ์เดิมที่คุณมีคลาสจำนวนมากที่คุณต้องการใช้ฟังก์ชันโคลนเคล็ดลับนี้จะมีประโยชน์ ไม่เบาเท่ากับการใช้โคลนของคุณเองโดยตรง แต่ก็ยังมีประโยชน์ หากผู้คนไม่เคยเสนอทางเลือก "คุณนึกถึง ... " ให้กับคำถามของฉันฉันคงไม่ได้เรียนรู้อะไรมากเท่าที่ฉันมีในช่วงหลายปีที่ผ่านมา
Rob Levine

2

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

สำหรับฉันมี:

// myobj is some transparent proxy object
var state = new ObjectState(myobj.State);

// do something

myobject = GetInstance();
var newState = new ObjectState(myobject.State);

if (!newState.Equals(state))
    throw new Exception();

แทน:

// myobj is some transparent proxy object
var state = myobj.State.Clone();

// do something

myobject = GetInstance();
var newState = myobject.State.Clone();

if (!newState.Equals(state))
    throw new Exception();

ดูเป็นการแสดงเจตนาที่ชัดเจนขึ้น


0

ฉันคิดว่าควรมีรูปแบบมาตรฐานสำหรับวัตถุที่สามารถโคลนได้แม้ว่าฉันจะไม่แน่ใจว่ารูปแบบควรเป็นอย่างไร เกี่ยวกับการโคลนดูเหมือนว่าจะมีคลาสสามประเภท:

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

เท่าที่ฉันสามารถบอกได้วิธีเดียว (อย่างน้อยใน. net 2.0) ในการรับวัตถุใหม่ของคลาสเดียวกันกับวัตถุที่มีอยู่คือการใช้ MemberwiseClone รูปแบบที่ดีดูเหมือนว่าจะมีฟังก์ชัน Clone "new" / "Shadows" ซึ่งจะส่งคืนชนิดปัจจุบันเสมอซึ่งคำจำกัดความคือการเรียก MemberwiseClone เสมอจากนั้นจึงเรียกรูทีนย่อยเสมือนที่มีการป้องกัน CleanupClone (originalObject) รูทีน CleanupCode ควรเรียก base.Cleanupcode เพื่อจัดการกับความต้องการการโคลนของประเภทพื้นฐานจากนั้นเพิ่มการล้างข้อมูลของตัวเอง หากรูทีนการโคลนนิ่งต้องใช้ออบเจ็กต์ดั้งเดิมก็จะต้องเป็นแบบไทคาสต์ แต่มิฉะนั้นการพิมพ์ดีดเพียงอย่างเดียวจะอยู่ในการเรียก MemberwiseClone

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

ถึงกระนั้นฉันคิดว่าการมีรูปแบบที่กำหนดไว้จะดีกว่าไม่มีอะไรเลย

อนึ่งหากรู้ว่าประเภทฐานรองรับ iCloneable แต่ไม่ทราบชื่อของฟังก์ชันที่ใช้มีวิธีใดบ้างในการอ้างอิงฟังก์ชัน iCloneable.Clone ของประเภทฐาน


0

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

ฉันจะโคลนคุณสมบัติของคลาสของบุคคลที่สามโดยใช้วิธีการขยายทั่วไปได้อย่างไร

อธิบายวิธีการใช้วิธีการขยายCreateCopy()ซึ่งสร้างสำเนา "ลึก" ของวัตถุรวมถึงคุณสมบัติทั้งหมด (โดยไม่ต้องคัดลอกคุณสมบัติตามคุณสมบัติด้วยตนเอง)

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