ทำไมไม่มี ICloneable <T>


223

มีเหตุผลบางประการที่ทำไมICloneable<T>ไม่มียาชื่อสามัญอยู่?

มันจะสะดวกสบายมากขึ้นถ้าฉันไม่จำเป็นต้องโยนมันทุกครั้งที่ฉันโคลนบางอย่าง


6
No! ด้วยความเคารพต่อ 'เหตุผล' ทั้งหมดฉันเห็นด้วยกับคุณพวกเขาควรจะใช้มัน!
Shimmy Weitzhandler

คงจะเป็นเรื่องดีสำหรับ Microsoft ที่จะให้คำจำกัดความ (ปัญหาของอินเทอร์เฟซสำหรับผู้ผลิตเบียร์ของคุณเองคืออินเทอร์เฟซในแอสเซมบลีที่แตกต่างกันสองชุดจะไม่เข้ากัน ถ้าฉันออกแบบอินเตอร์เฟสมันจะมีสมาชิกสามคนคือ Clone, Self และ Clone IfMutable ซึ่งทั้งหมดจะคืนค่า T (สมาชิกสุดท้ายจะส่งคืน Clone หรือ Self ตามความเหมาะสม) สมาชิก Self จะทำให้สามารถยอมรับ ICloneable (ของ Foo) เป็นพารามิเตอร์แล้วใช้เป็น Foo โดยไม่จำเป็นต้องใช้ typecast
supercat

ที่จะช่วยให้ลำดับชั้นของการโคลนนิ่งเหมาะสมซึ่งคลาสที่สืบทอดได้แสดงวิธีการ "โคลน" ที่ได้รับการป้องกันและมีอนุพันธ์ที่ปิดผนึกซึ่งเปิดเผยต่อสาธารณะ ตัวอย่างเช่นหนึ่งอาจมี Pile, CloneablePile: Pile, EnhancedPile: Pile และ CloneableEnhancedPile: EnhancedPile ซึ่งไม่มีวิธีใดที่จะหักถ้าถูกโคลน (แม้ว่าจะไม่เปิดเผยวิธีการโคลนสาธารณะทั้งหมด) และ FurtherEnhancedPile: EnhancedPile ถ้าโคลน แต่ไม่เปิดเผยวิธีการโคลน) รูทีนที่ยอมรับ ICloneable (ของ Pile) สามารถยอมรับ CloneablePile หรือ CloneableEnhancedPile ...
supercat

... แม้ว่า CloneableEnhancedPile จะไม่สืบทอดจาก CloneablePile โปรดทราบว่าหาก EnhancedPile สืบทอดมาจาก CloneablePile, FurtherEnhancedPile จะต้องเปิดเผยวิธีการโคลนสาธารณะและสามารถส่งผ่านไปยังรหัสที่คาดว่าจะโคลนมันละเมิดหลักการแทน Liskov เนื่องจาก CloneableEnhancedPile จะใช้ ICloneable (ของ EnhancedPile) และโดยนัย ICloneable (ของ Pile) ก็อาจถูกส่งผ่านไปยังรูทีนที่คาดว่าจะมีการลอกเลียนแบบของ Pile
supercat

คำตอบ:


111

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

คุณอาจจะทำวิธีการขยายการโคลนนิ่งที่พิมพ์ แต่ฉันคิดว่ามันจะต้องมีชื่อที่แตกต่างกันเนื่องจากวิธีการขยายมีความสำคัญน้อยกว่าเดิม


8
ฉันไม่เห็นด้วยไม่ดีหรือไม่เลวมันมีประโยชน์มากในบางครั้งสำหรับการโคลนนิ่งของ SHALLOW และในกรณีเหล่านี้จำเป็นต้องใช้ Clone <T> เพื่อประหยัดการชกมวยที่ไม่จำเป็น
Shimmy Weitzhandler

2
@AndreyShchekin: มีอะไรไม่ชัดเจนเกี่ยวกับการโคลนนิ่งตื้นเขินลึก vs หากList<T>มีวิธีการโคลนฉันจะคาดหวังให้ผลตอบแทนList<T>ซึ่งรายการนั้นมีตัวตนเดียวกันกับรายการในรายการเดิม แต่ฉันคาดหวังว่าโครงสร้างข้อมูลภายในใด ๆ จะถูกทำซ้ำตามต้องการเพื่อให้แน่ใจว่าไม่มีการทำรายการใดรายการหนึ่งอัตลักษณ์ของรายการที่เก็บไว้ในที่อื่น ๆ ความกำกวมอยู่ที่ไหน ปัญหาที่ใหญ่กว่าของการโคลนนิ่งมาพร้อมกับการเปลี่ยนแปลงของ "ปัญหาเพชร": หากCloneableFooสืบทอดมาจาก [ไม่ลอกเลียนแบบสาธารณะ] Fooควรได้CloneableDerivedFooมาจาก ...
supercat

1
@supercat: ฉันไม่สามารถพูดได้ว่าฉันเข้าใจความคาดหวังของคุณอย่างเต็มที่เช่นสิ่งที่คุณจะพิจารณาidentityของรายการตัวเองเช่น (ในกรณีของรายการของรายการ)? Cloneแต่ไม่สนใจที่คาดหวังของคุณไม่ได้เป็นไปได้เพียงคนคิดอาจจะมีเมื่อเรียกหรือการดำเนินการ เกิดอะไรขึ้นถ้าผู้เขียนห้องสมุดใช้รายการอื่นไม่ปฏิบัติตามความคาดหวังของคุณ? API ควรมีความชัดเจนน้อยมากไม่น่าสงสัย
Andrey Shchekin

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

5
@supercat คุณไม่ต้องคำนึงถึงมันเป็นส่วนหนึ่งของ. NET Framework ไม่ใช่ส่วนหนึ่งของแอปพลิเคชันของคุณ ดังนั้นหากคุณสร้างคลาสที่ไม่ทำการโคลนนิ่งลึกคนอื่นจะสร้างคลาสที่ทำการโคลนนิ่งแบบลึกและโทรCloneไปยังทุกส่วนของคลาสนั้นจะไม่สามารถคาดเดาได้ ใครที่ชอบการโคลนนิ่งลึก ประเด็นของคุณเกี่ยวกับรูปแบบนั้นถูกต้อง แต่การมี IMHO ใน API นั้นไม่ชัดเจนเพียงพอ - มันควรถูกเรียกShallowCopyให้เน้นประเด็นหรือไม่ได้ระบุไว้เลย
Andrey Shchekin

141

นอกจากนี้ในการตอบกลับของอันเดรย์ (ซึ่งผมเห็นด้วยกับ +1) - เมื่อICloneable มีการทำคุณยังสามารถเลือกการดำเนินงานอย่างชัดเจนเพื่อให้ประชาชนClone()กลับมาเป็นวัตถุที่พิมพ์:

public Foo Clone() { /* your code */ }
object ICloneable.Clone() {return Clone();}

แน่นอนว่ายังมีประเด็นที่สองที่เกี่ยวข้องกับICloneable<T>มรดก -

ถ้าฉันมี:

public class Foo {}
public class Bar : Foo {}

และฉันนำไปใช้ICloneable<T>แล้วฉันจะใช้งานICloneable<Foo>อย่างไร ICloneable<Bar>? คุณเริ่มใช้อินเทอร์เฟซที่เหมือนกันจำนวนมากได้อย่างรวดเร็ว ... เปรียบเทียบกับนักแสดง ... และมันแย่มากจริง ๆ เหรอ?


10
+1 ผมคิดเสมอว่าปัญหาความแปรปรวนเป็นเหตุผลที่แท้จริง
Tamas Czinege

มีปัญหาเกี่ยวกับสิ่งนี้: การใช้งานอินเทอร์เฟซที่ชัดเจนต้องเป็นแบบส่วนตัวซึ่งจะทำให้เกิดปัญหาหากฟูบางจุดจำเป็นต้องส่งไปยัง ICloneable ... ฉันขาดอะไรบางอย่างที่นี่หรือไม่?
Joel ในGö

3
ความผิดพลาดของฉัน: ปรากฎว่าแม้จะถูกกำหนดเป็นส่วนตัววิธีการที่จะถูกเรียกว่าควรจะถูกโยนไป IClonable (CLR ผ่านทาง C # 2nd Ed, p.319) ดูเหมือนว่าจะเป็นการออกแบบที่แปลก แต่ก็มี ดังนั้น Foo.Clone () ให้วิธีแรกและ ((ICloneable) Foo) .Clone () ให้วิธีที่สอง
Joel ในGö

ในความเป็นจริงการใช้งานอินเทอร์เฟซที่ชัดเจนคือการพูดอย่างเคร่งครัดส่วนหนึ่งของ API สาธารณะ ในการที่พวกเขาจะเรียกร้องต่อสาธารณะผ่านการคัดเลือกนักแสดง
Marc Gravell

8
วิธีแก้ปัญหาที่เสนอนั้นดูดีมากในตอนแรก ... จนกว่าคุณจะรู้ว่าในลำดับชั้นของคลาสคุณต้องการให้ 'public Foo Clone ()' เป็นเสมือนจริงและถูกแทนที่ในคลาสที่ได้รับเพื่อให้พวกเขาสามารถส่งคืนชนิดของตนเอง หลักสูตร) น่าเศร้าที่คุณไม่สามารถเปลี่ยนประเภทการส่งคืนในการแทนที่ (ปัญหาความแปรปรวนร่วมที่กล่าวถึง) ดังนั้นคุณกลับมาที่ปัญหาเดิมอีกครั้ง - การใช้งานที่ชัดเจนไม่ได้ซื้อคุณมากนัก (นอกเหนือจากการคืนประเภทพื้นฐานของลำดับชั้นของคุณแทนที่จะเป็น 'วัตถุ') และคุณกลับมาหล่อส่วนใหญ่ของคุณ Clone () ผลการโทร yuck!
Ken Beckett

18

ฉันต้องถามว่าคุณจะทำอะไรกับส่วนต่อประสานอื่นที่ไม่ใช่ใช้งานจริง อินเทอร์เฟซมักจะมีประโยชน์เฉพาะเมื่อคุณส่งไปให้ (เช่นคลาสนี้รองรับ 'IBar') หรือมีพารามิเตอร์หรือตัวตั้งค่าที่ใช้ (เช่นฉันใช้ 'IBar') ด้วย ICloneable - เราผ่าน Framework ทั้งหมดและล้มเหลวในการค้นหาการใช้งานครั้งเดียวทุกที่ที่เป็นอย่างอื่นนอกเหนือจากการนำไปใช้งาน นอกจากนี้เรายังล้มเหลวในการค้นหาการใช้งานใด ๆ ใน 'โลกแห่งความจริง' ที่ทำสิ่งอื่นนอกเหนือจากการนำไปใช้ (ในแอป ~ 60,000 ที่เราสามารถเข้าถึงได้)

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

David Kean (ทีม BCL)


1
ICloneable ที่ไม่ใช่แบบทั่วไปไม่มีประโยชน์มาก แต่ICloneable<out T>อาจมีประโยชน์มากหากสืบทอดมาISelf<out T>ด้วยวิธีการSelfประเภทTเดียว เรามักไม่ต้องการ "สิ่งที่ลอกเลียนแบบได้" แต่บางครั้งก็อาจจำเป็นต้องมีสิ่งTที่ลอกเลียนแบบได้ หากวัตถุที่ลอกเลียนแบบใช้งานได้ISelf<itsOwnType>รูทีนที่ต้องการการลอกเลียนแบบTนั้นสามารถยอมรับพารามิเตอร์ประเภทICloneable<T>ได้แม้ว่าจะไม่ได้มีการลอกเลียนแบบอนุพันธ์ของTบรรพบุรุษร่วมกันก็ตาม
supercat

ขอบคุณ นั่นคล้ายกับสถานการณ์ที่ฉันพูดถึงข้างต้น (เช่น 'คลาสนี้รองรับ' IBar ') น่าเศร้าที่เราเกิดขึ้นกับสถานการณ์ที่ จำกัด และโดดเดี่ยวซึ่งคุณจะใช้ประโยชน์จากความจริงที่ว่า T สามารถลอกแบบได้ คุณมีสถานการณ์ในใจหรือไม่?
David Kean

สิ่งหนึ่งที่บางครั้งที่น่าอึดอัดใจใน. net คือการจัดการคอลเลกชันที่ไม่แน่นอนเมื่อเนื้อหาของคอลเลกชันควรจะถูกมองว่าเป็นค่า บางอย่างเช่นICloneable<T>นั้นอาจเป็นประโยชน์สำหรับสิ่งนั้นแม้ว่ากรอบงานที่กว้างขึ้นสำหรับการรักษาคลาสที่ไม่แน่นอนและไม่เปลี่ยนรูปแบบอาจจะมีประโยชน์มากกว่า กล่าวอีกนัยหนึ่งรหัสที่ต้องการดูว่ามีอะไรFooบรรจุอยู่บ้าง แต่จะไม่ทำให้กลายพันธุ์หรือคาดหวังว่ามันจะไม่มีการเปลี่ยนแปลงใด ๆ ที่สามารถใช้IReadableFooในขณะที่ ...
supercat

... รหัสซึ่งต้องการที่จะถือเนื้อหาของFooสามารถใช้รหัสในขณะนั้นเพื่อต้องการที่จะจัดการกับมันสามารถใช้ImmutableFoo MutableFooรหัสที่กำหนดประเภทใด ๆIReadableFooควรจะได้รับรุ่นที่ไม่แน่นอนหรือไม่เปลี่ยนรูป กรอบดังกล่าวจะดี แต่น่าเสียดายที่ฉันไม่สามารถหาวิธีที่ดีในการตั้งค่าในแบบทั่วไป หากมีวิธีที่สอดคล้องกันในการสร้าง wrapper แบบอ่านอย่างเดียวสำหรับชั้นเรียนสิ่งนี้สามารถนำมาใช้ร่วมกับICloneable<T>การทำสำเนาที่ไม่เปลี่ยนรูปของชั้นเรียนที่ถือT'
supercat

@supercat หากคุณต้องการที่จะโคลนList<T>เช่นว่าโคลนเป็นคอลเลกชันใหม่ที่ถือตัวชี้ไปยังวัตถุเดียวกันในคอลเลกชันเดิมมีสองวิธีที่ง่ายในการทำว่าไม่มีList<T> ICloneable<T>วิธีแรกคือEnumerable.ToList()วิธีการขยาย: List<foo> clone = original.ToList();ตัวที่สองคือตัวList<T>สร้างที่รับIEnumerable<T>: List<foo> clone = new List<foo>(original);ฉันสงสัยว่าวิธีการขยายอาจเป็นเพียงการเรียกตัวสร้าง แต่ทั้งสองสิ่งนี้จะทำสิ่งที่คุณร้องขอ ;)
CptRobby

12

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

แต่ส่วนใหญ่คุณสามารถทำมันเอง

public interface ICloneable<T> : ICloneable {
    new T Clone();
}

public abstract class CloneableBase<T> : ICloneable<T> where T : CloneableBase<T> {
    public abstract T Clone();
    object ICloneable.Clone() { return this.Clone(); }
}

public abstract class CloneableExBase<T> : CloneableBase<T> where T : CloneableExBase<T> {
    protected abstract T CreateClone();
    protected abstract void FillClone( T clone );
    public override T Clone() {
        T clone = this.CreateClone();
        if ( object.ReferenceEquals( clone, null ) ) { throw new NullReferenceException( "Clone was not created." ); }
        return clone
    }
}

public abstract class PersonBase<T> : CloneableExBase<T> where T : PersonBase<T> {
    public string Name { get; set; }

    protected override void FillClone( T clone ) {
        clone.Name = this.Name;
    }
}

public sealed class Person : PersonBase<Person> {
    protected override Person CreateClone() { return new Person(); }
}

public abstract class EmployeeBase<T> : PersonBase<T> where T : EmployeeBase<T> {
    public string Department { get; set; }

    protected override void FillClone( T clone ) {
        base.FillClone( clone );
        clone.Department = this.Department;
    }
}

public sealed class Employee : EmployeeBase<Employee> {
    protected override Employee CreateClone() { return new Employee(); }
}

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

@supercat ทำไมไม่ให้คำตอบแล้ว?
nawfal

"มีอินเทอร์เฟซ / คลาส / ฯลฯ จำนวนมาก ... ซึ่งเป็นประโยชน์อย่างมาก แต่ไม่ได้เป็นส่วนหนึ่งของไลบรารีพื้นฐาน. NET Frameworku" คุณยกตัวอย่างให้เราได้ไหม
Krythic

1
@Krythic ie: โครงสร้างข้อมูลขั้นสูงเช่น b-tree, ต้นไม้สีแดงดำ, บัฟเฟอร์วงกลม ใน '09 มี tuples ไม่มีทั่วไปอ่อนแออ้างอิงคอลเลกชันพร้อมกัน ...
TcKs

10

การเขียนอินเทอร์เฟซนั้นค่อนข้างง่ายถ้าคุณต้องการ:

public interface ICloneable<T> : ICloneable
        where T : ICloneable<T>
{
    new T Clone();
}

ฉันรวมตัวอย่างเล็ก ๆ น้อย ๆ เนื่องจากเป็นเกียรติของสิทธิ์การใช้งานลิงก์เสีย ...
Shimmy Weitzhandler

2
และอินเตอร์เฟสนั้นควรทำงานอย่างไร สำหรับผลลัพธ์ของการดำเนินการโคลนที่สามารถ castable เพื่อพิมพ์ T วัตถุที่ถูกโคลนจะต้องได้รับมาจาก T ไม่มีวิธีตั้งค่าข้อ จำกัด ประเภทดังกล่าว สิ่งเดียวที่ฉันเห็นได้จากผลลัพธ์ของการคืน iCloneable คือประเภท iCloneable
supercat

@supercat: ใช่นั่นคือสิ่งที่ Clone () ส่งคืน: T ซึ่งใช้ ICloneable <T> MbUnit ใช้อินเทอร์เฟซนี้มาหลายปีแล้วใช่มันใช้งานได้ สำหรับการติดตั้งใช้งานลองดูที่แหล่งที่มาของ MbUnit
Mauricio Scheffer

2
@Mauricio Scheffer: ฉันเห็นสิ่งที่เกิดขึ้น ไม่มีประเภทที่ใช้ iCloneable (ของ T) แต่ละประเภทใช้ iCloneable (ของตัวเอง) แทน ฉันคิดว่าแม้ว่ามันจะเป็นแบบ typecast มันแย่มากที่ไม่มีทางประกาศเขตข้อมูลว่ามีข้อ จำกัด ในการสืบทอดและส่วนต่อประสานหนึ่ง มันจะมีประโยชน์ที่จะให้วิธีการโคลนส่งคืนออบเจ็กต์ประเภทกึ่งโคลน (การเปิดเผยเป็นวิธีการป้องกันเท่านั้น) ประเภทฐาน แต่มีข้อ จำกัด ประเภทการโคลน นั่นจะทำให้วัตถุยอมรับการลอกเลียนแบบอนุพันธ์ของการลอกเลียนแบบกึ่งชนิดของฐาน
supercat

@Mauricio Scheffer: BTW ฉันขอแนะนำให้ ICloneable (ของ T) ยังสนับสนุนคุณสมบัติ Self แบบอ่านอย่างเดียว (ของ return type T) สิ่งนี้จะอนุญาตให้วิธีการยอมรับ ICloneable (ของ Foo) และใช้ (ผ่านคุณสมบัติ Self) เป็น Foo
supercat

9

ต้องอ่านเมื่อเร็ว ๆ นี้บทความทำไมการคัดลอกวัตถุเป็นสิ่งที่น่ากลัว? ฉันคิดว่าคำถามนี้ต้องการ clafirication เพิ่มเติม คำตอบอื่น ๆ ที่นี่ให้คำแนะนำที่ดี แต่ยังคงคำตอบไม่สมบูรณ์ - ทำไมICloneable<T>ไม่

  1. การใช้

    ดังนั้นคุณมีคลาสที่ใช้งาน ขณะที่ก่อนหน้านี้คุณมีวิธีการที่อยากได้ในขณะนี้จะต้องมีการทั่วไปที่จะยอมรับICloneable ICloneable<T>คุณจะต้องแก้ไข

    is ICloneableจากนั้นคุณจะได้มีวิธีการที่จะตรวจสอบว่าวัตถุ เกิดอะไรขึ้น คุณไม่สามารถทำได้is ICloneable<>และเมื่อคุณไม่ทราบประเภทของวัตถุที่คอมไพล์ประเภทคุณไม่สามารถสร้างวิธีการทั่วไป ปัญหาแรกที่แท้จริง

    ดังนั้นคุณต้องมีทั้งICloneable<T>และICloneableก่อนดำเนินการหลัง ดังนั้น implementer จะต้องใช้วิธีการทั้งสอง - และobject Clone() ไม่ขอบคุณเราแล้วมีความสุขมากพอกับT Clone()IEnumerable

    ตามที่ระบุไว้แล้วยังมีความซับซ้อนของมรดก แม้ว่าความแปรปรวนร่วมอาจดูเหมือนว่าจะแก้ปัญหานี้ได้ แต่ชนิดที่ได้มานั้นจำเป็นต้องใช้ICloneable<T>ชนิดของตัวเอง แต่มีวิธีการที่มีลายเซ็นเดียวกัน (= พารามิเตอร์โดยทั่วไป) อยู่แล้ว - Clone()ของคลาสพื้นฐาน ICloneable<T>อินเตอร์เฟซการทำวิธีการโคลนใหม่ของคุณอย่างชัดเจนจะไม่มีจุดหมายคุณจะสูญเสียประโยชน์ที่คุณขอเมื่อมีการสร้าง ดังนั้นเพิ่มnewคำหลัก แต่อย่าลืมว่าคุณจะต้องแทนที่คลาสพื้นฐาน ' Clone()(การนำไปใช้จะต้องเหมือนกันสำหรับคลาสที่ได้รับทั้งหมดคือการส่งคืนออบเจ็กต์เดียวกันจากทุกวิธีการโคลนดังนั้นจึงต้องใช้วิธีการโคลนพื้นฐานvirtual)! แต่น่าเสียดายที่คุณไม่สามารถทั้งสองoverrideและnewวิธีการที่มีลายเซ็นเดียวกัน ICloneable<T>การเลือกคำหลักคำแรกที่คุณจะสูญเสียเป้าหมายที่คุณต้องการที่จะมีเมื่อมีการเพิ่ม ทำให้ชิ้นที่สองคุณทำลายส่วนต่อประสานเองโดยสร้างวิธีการที่ควรทำสิ่งที่แตกต่างกันในการส่งคืนวัตถุ

  2. จุด

    คุณต้องการความICloneable<T>สะดวกสบาย แต่ความสะดวกสบายไม่ใช่สิ่งที่ออกแบบมาสำหรับอินเทอร์เฟซความหมายของมันคือ (โดยทั่วไป OOP) เพื่อรวมพฤติกรรมของวัตถุ (แม้ว่าใน C # จะ จำกัด อยู่ที่การรวมพฤติกรรมภายนอกเช่นวิธีการและคุณสมบัติ ผลงานของพวกเขา)

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

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

อย่างที่คุณเห็นนี่เป็นการพิสูจน์ว่าการสร้างอินเทอร์เฟซทั่วไปนั้นยากที่จะนำไปใช้งานอย่างเต็มที่และยังไม่จำเป็นและไร้ประโยชน์จริงๆ

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

วิธีการเพิ่มเติม

public class Base : ICloneable
{
    public Base Clone()
    {
        return this.CloneImpl() as Base;
    }

    object ICloneable.Clone()
    {
        return this.CloneImpl();
    }

    protected virtual object CloneImpl()
    {
        return new Base();
    }
}

public class Derived : Base
{
    public new Derived Clone()
    {
        return this.CloneImpl() as Derived;
    }

    protected override object CloneImpl()
    {
        return new Derived();
    }
}

โซลูชันนี้มอบทั้งความสะดวกสบายและพฤติกรรมที่ตั้งใจให้กับผู้ใช้ แต่ยังใช้งานได้นานเกินไป ถ้าเราไม่ได้ต้องการที่จะมีวิธีการ "สบาย" public virtual object Clone()กลับชนิดปัจจุบันก็เป็นมากขึ้นง่ายที่จะมีเพียง

ถ้าอย่างนั้นเรามาดูวิธีแก้ปัญหา "สุดยอด" - สิ่งใดใน C # ที่มีเจตนาเพื่อให้ความสะดวกสบายแก่เรา?

วิธีการขยาย!

public class Base : ICloneable
{
    public virtual object Clone()
    {
        return new Base();
    }
}

public class Derived : Base
{
    public override object Clone()
    {
        return new Derived();
    }
}

public static T Copy<T>(this T obj) where T : class, ICloneable
{
    return obj.Clone() as T;
}

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

ICloneable<T>ฉันหวังว่าชี้แจงนี้เหตุผลที่จะไม่ทำ อย่างไรก็ตามขอแนะนำว่าอย่าใช้งานICloneableเลย


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

2

แม้ว่าคำถามจะเก่ามาก (5 ปีจากการเขียนคำตอบนี้ :) และได้รับคำตอบแล้ว แต่ฉันพบว่าบทความนี้ตอบคำถามได้ค่อนข้างดีตรวจสอบได้ที่นี่

แก้ไข:

นี่คือคำพูดจากบทความที่ตอบคำถาม (ให้แน่ใจว่าได้อ่านบทความเต็มรวมถึงสิ่งที่น่าสนใจอื่น ๆ ):

มีการอ้างอิงจำนวนมากบนอินเทอร์เน็ตที่ชี้ไปยังบล็อกโพสต์ในปี 2003 โดยแบรดเอบรัมส์ซึ่งเป็นเวลาที่ไมโครซอฟท์ใช้ในการหารือเกี่ยวกับความคิดบางอย่างเกี่ยวกับ ICloneable รายการบล็อกสามารถพบได้ที่อยู่นี้: การใช้ ICloneable แม้จะมีชื่อที่ทำให้เข้าใจผิด แต่บล็อกนี้ยังไม่เรียกใช้ ICloneable ส่วนใหญ่เป็นเพราะความสับสนตื้น / ลึก บทความลงท้ายด้วยคำแนะนำแบบตรง: หากคุณต้องการกลไกการโคลนกำหนดวิธีการโคลนหรือคัดลอกของคุณเองและตรวจสอบให้แน่ใจว่าเอกสารของคุณชัดเจนว่าเป็นสำเนาที่ลึกหรือตื้น รูปแบบที่เหมาะสมคือ:public <type> Copy();


1

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

interface IClonable<T>
{
    T Clone();
}

class Dog : IClonable<JackRabbit>
{
    //not what you would expect, but possible
    JackRabbit Clone()
    {
        return new JackRabbit();
    }

}

พวกเขาต้องการการ จำกัด พารามิเตอร์เช่น:

interfact IClonable<T> where T : implementing_type

8
ดูเหมือนจะไม่เลวเท่า class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
Nikola Novak

ฉันไม่เห็นว่าปัญหาคืออะไร ไม่มีอินเทอร์เฟซที่สามารถบังคับให้การใช้งานมีพฤติกรรม แม้ว่าICloneable<T>จะสามารถ จำกัดTให้ตรงกับประเภทของตัวเอง แต่ก็ไม่บังคับให้มีการใช้งานClone()เพื่อส่งคืนสิ่งใดจากระยะไกลซึ่งคล้ายกับวัตถุที่ถูกโคลน นอกจากนี้ฉันขอแนะนำว่าหากมีใครใช้ความแปรปรวนร่วมของอินเตอร์เฟซมันอาจจะดีที่สุดที่จะมีชั้นเรียนที่ใช้งานICloneableได้รับการปิดผนึกมีอินเทอร์เฟซICloneable<out T>รวมถึงSelfคุณสมบัติที่คาดว่าจะส่งคืนตัวเอง ...
supercat

... ได้ผู้บริโภคโยนหรือ จำกัด การหรือICloneable<BaseType> ในคำถามควรจะมีวิธีการในการโคลนซึ่งจะเรียกว่าตามประเภทที่ดำเนินการ การออกแบบนี้จะช่วยให้มีความเป็นไปได้ที่เราอาจต้องการมี a , a , a และ a ซึ่งภายหลังนั้นสามารถใช้งานได้ในรหัสซึ่งต้องใช้การลอกเลียนแบบของหรือต้องการ(แต่ไม่สนใจว่าจะลอกเลียนแบบ) ICloneable<ICloneable<BaseType>>BaseTypeprotectedICloneableContainerCloneableContainerFancyContainerCloneableFancyContainerContainerFancyContainer
supercat

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

+1 - คำตอบนี้เป็นคำตอบเดียวจากที่ฉันเคยเห็นที่นี่ที่ตอบคำถาม
IllidanS4 ต้องการโมนิก้ากลับ

0

มันเป็นคำถามที่ดีมาก ... คุณสามารถสร้างของคุณเองแม้ว่า:

interface ICloneable<T> : ICloneable
{
  new T Clone ( );
}

Andrey กล่าวว่ามันถือว่าเป็น API ที่ไม่ดี แต่ฉันไม่เคยได้ยินอะไรเกี่ยวกับอินเทอร์เฟซนี้ที่เลิกใช้แล้ว และนั่นจะทำลายการเชื่อมต่อมากมาย ... วิธีการโคลนควรทำสำเนาแบบตื้น หากวัตถุมีการทำสำเนาแบบลึกก็สามารถใช้ Clone ที่มีการโหลดมากเกินไป (bool deep) ได้

แก้ไข: รูปแบบที่ฉันใช้สำหรับ "การโคลน" วัตถุกำลังผ่านต้นแบบในตัวสร้าง

class C
{
  public C ( C prototype )
  {
    ...
  }
}

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


"ไม่ดี" ไม่ได้หมายความว่าเลิกใช้แล้ว มันแปลว่า "คุณดีกว่าไม่ใช้" มันไม่คัดค้านในแง่ที่ว่า "เราจะลบอินเทอร์เฟซ ICloneable ในอนาคต" พวกเขาเพียงสนับสนุนให้คุณไม่ใช้และจะไม่อัปเดตด้วยข้อมูลทั่วไปหรือคุณสมบัติใหม่อื่น ๆ
jalf

Dennis: ดูคำตอบของ Marc ปัญหาความแปรปรวนร่วม / การสืบทอดทำให้อินเทอร์เฟซนี้ใช้ไม่ได้กับสถานการณ์ที่ซับซ้อนอย่างน้อยที่สุด
Tamas Czinege

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