มีเหตุผลบางประการที่ทำไมICloneable<T>ไม่มียาชื่อสามัญอยู่?
มันจะสะดวกสบายมากขึ้นถ้าฉันไม่จำเป็นต้องโยนมันทุกครั้งที่ฉันโคลนบางอย่าง
มีเหตุผลบางประการที่ทำไมICloneable<T>ไม่มียาชื่อสามัญอยู่?
มันจะสะดวกสบายมากขึ้นถ้าฉันไม่จำเป็นต้องโยนมันทุกครั้งที่ฉันโคลนบางอย่าง
คำตอบ:
ICloneable ถือเป็น API ที่ไม่ดีในขณะนี้เนื่องจากไม่ได้ระบุว่าผลลัพธ์นั้นเป็นสำเนาที่ลึกหรือตื้น ฉันคิดว่านี่เป็นเหตุผลที่พวกเขาไม่ปรับปรุงส่วนต่อประสานนี้
คุณอาจจะทำวิธีการขยายการโคลนนิ่งที่พิมพ์ แต่ฉันคิดว่ามันจะต้องมีชื่อที่แตกต่างกันเนื่องจากวิธีการขยายมีความสำคัญน้อยกว่าเดิม
List<T>มีวิธีการโคลนฉันจะคาดหวังให้ผลตอบแทนList<T>ซึ่งรายการนั้นมีตัวตนเดียวกันกับรายการในรายการเดิม แต่ฉันคาดหวังว่าโครงสร้างข้อมูลภายในใด ๆ จะถูกทำซ้ำตามต้องการเพื่อให้แน่ใจว่าไม่มีการทำรายการใดรายการหนึ่งอัตลักษณ์ของรายการที่เก็บไว้ในที่อื่น ๆ ความกำกวมอยู่ที่ไหน ปัญหาที่ใหญ่กว่าของการโคลนนิ่งมาพร้อมกับการเปลี่ยนแปลงของ "ปัญหาเพชร": หากCloneableFooสืบทอดมาจาก [ไม่ลอกเลียนแบบสาธารณะ] Fooควรได้CloneableDerivedFooมาจาก ...
                    identityของรายการตัวเองเช่น (ในกรณีของรายการของรายการ)? Cloneแต่ไม่สนใจที่คาดหวังของคุณไม่ได้เป็นไปได้เพียงคนคิดอาจจะมีเมื่อเรียกหรือการดำเนินการ เกิดอะไรขึ้นถ้าผู้เขียนห้องสมุดใช้รายการอื่นไม่ปฏิบัติตามความคาดหวังของคุณ? API ควรมีความชัดเจนน้อยมากไม่น่าสงสัย
                    Cloneไปยังทุกส่วนของคลาสนั้นจะไม่สามารถคาดเดาได้ ใครที่ชอบการโคลนนิ่งลึก ประเด็นของคุณเกี่ยวกับรูปแบบนั้นถูกต้อง แต่การมี IMHO ใน API นั้นไม่ชัดเจนเพียงพอ - มันควรถูกเรียกShallowCopyให้เน้นประเด็นหรือไม่ได้ระบุไว้เลย
                    นอกจากนี้ในการตอบกลับของอันเดรย์ (ซึ่งผมเห็นด้วยกับ +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>? คุณเริ่มใช้อินเทอร์เฟซที่เหมือนกันจำนวนมากได้อย่างรวดเร็ว ... เปรียบเทียบกับนักแสดง ... และมันแย่มากจริง ๆ เหรอ?
ฉันต้องถามว่าคุณจะทำอะไรกับส่วนต่อประสานอื่นที่ไม่ใช่ใช้งานจริง อินเทอร์เฟซมักจะมีประโยชน์เฉพาะเมื่อคุณส่งไปให้ (เช่นคลาสนี้รองรับ 'IBar') หรือมีพารามิเตอร์หรือตัวตั้งค่าที่ใช้ (เช่นฉันใช้ 'IBar') ด้วย ICloneable - เราผ่าน Framework ทั้งหมดและล้มเหลวในการค้นหาการใช้งานครั้งเดียวทุกที่ที่เป็นอย่างอื่นนอกเหนือจากการนำไปใช้งาน นอกจากนี้เรายังล้มเหลวในการค้นหาการใช้งานใด ๆ ใน 'โลกแห่งความจริง' ที่ทำสิ่งอื่นนอกเหนือจากการนำไปใช้ (ในแอป ~ 60,000 ที่เราสามารถเข้าถึงได้)
ตอนนี้ถ้าคุณต้องการที่จะบังคับใช้รูปแบบที่คุณต้องการให้วัตถุที่ 'ลอกเลียนแบบได้' ของคุณไปใช้งานนั่นเป็นการใช้งานที่สมบูรณ์แบบและไปข้างหน้า นอกจากนี้คุณยังสามารถตัดสินใจได้ว่า "การโคลนนิ่ง" มีความหมายต่อคุณอย่างไร (เช่นลึกหรือตื้น) อย่างไรก็ตามในกรณีนั้นเราไม่จำเป็นต้องกำหนด BCL เรากำหนด abstractions ใน BCL เฉพาะเมื่อมีความจำเป็นต้องแลกเปลี่ยนอินสแตนซ์ที่พิมพ์ว่าเป็นนามธรรมระหว่างไลบรารี่ที่ไม่เกี่ยวข้อง
David Kean (ทีม BCL)
ICloneable<out T>อาจมีประโยชน์มากหากสืบทอดมาISelf<out T>ด้วยวิธีการSelfประเภทTเดียว เรามักไม่ต้องการ "สิ่งที่ลอกเลียนแบบได้" แต่บางครั้งก็อาจจำเป็นต้องมีสิ่งTที่ลอกเลียนแบบได้ หากวัตถุที่ลอกเลียนแบบใช้งานได้ISelf<itsOwnType>รูทีนที่ต้องการการลอกเลียนแบบTนั้นสามารถยอมรับพารามิเตอร์ประเภทICloneable<T>ได้แม้ว่าจะไม่ได้มีการลอกเลียนแบบอนุพันธ์ของTบรรพบุรุษร่วมกันก็ตาม
                    ICloneable<T>นั้นอาจเป็นประโยชน์สำหรับสิ่งนั้นแม้ว่ากรอบงานที่กว้างขึ้นสำหรับการรักษาคลาสที่ไม่แน่นอนและไม่เปลี่ยนรูปแบบอาจจะมีประโยชน์มากกว่า กล่าวอีกนัยหนึ่งรหัสที่ต้องการดูว่ามีอะไรFooบรรจุอยู่บ้าง แต่จะไม่ทำให้กลายพันธุ์หรือคาดหวังว่ามันจะไม่มีการเปลี่ยนแปลงใด ๆ ที่สามารถใช้IReadableFooในขณะที่ ...
                    Fooสามารถใช้รหัสในขณะนั้นเพื่อต้องการที่จะจัดการกับมันสามารถใช้ImmutableFoo MutableFooรหัสที่กำหนดประเภทใด ๆIReadableFooควรจะได้รับรุ่นที่ไม่แน่นอนหรือไม่เปลี่ยนรูป กรอบดังกล่าวจะดี แต่น่าเสียดายที่ฉันไม่สามารถหาวิธีที่ดีในการตั้งค่าในแบบทั่วไป หากมีวิธีที่สอดคล้องกันในการสร้าง wrapper แบบอ่านอย่างเดียวสำหรับชั้นเรียนสิ่งนี้สามารถนำมาใช้ร่วมกับICloneable<T>การทำสำเนาที่ไม่เปลี่ยนรูปของชั้นเรียนที่ถือT'
                    List<T>เช่นว่าโคลนเป็นคอลเลกชันใหม่ที่ถือตัวชี้ไปยังวัตถุเดียวกันในคอลเลกชันเดิมมีสองวิธีที่ง่ายในการทำว่าไม่มีList<T> ICloneable<T>วิธีแรกคือEnumerable.ToList()วิธีการขยาย: List<foo> clone = original.ToList();ตัวที่สองคือตัวList<T>สร้างที่รับIEnumerable<T>: List<foo> clone = new List<foo>(original);ฉันสงสัยว่าวิธีการขยายอาจเป็นเพียงการเรียกตัวสร้าง แต่ทั้งสองสิ่งนี้จะทำสิ่งที่คุณร้องขอ ;)
                    ฉันคิดว่าคำถาม "ทำไม" ไม่มีความจำเป็น มีอินเตอร์เฟส / คลาส / 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(); }
}การเขียนอินเทอร์เฟซนั้นค่อนข้างง่ายถ้าคุณต้องการ:
public interface ICloneable<T> : ICloneable
        where T : ICloneable<T>
{
    new T Clone();
}ต้องอ่านเมื่อเร็ว ๆ นี้บทความทำไมการคัดลอกวัตถุเป็นสิ่งที่น่ากลัว? ฉันคิดว่าคำถามนี้ต้องการ clafirication เพิ่มเติม คำตอบอื่น ๆ ที่นี่ให้คำแนะนำที่ดี แต่ยังคงคำตอบไม่สมบูรณ์ - ทำไมICloneable<T>ไม่
การใช้
ดังนั้นคุณมีคลาสที่ใช้งาน ขณะที่ก่อนหน้านี้คุณมีวิธีการที่อยากได้ในขณะนี้จะต้องมีการทั่วไปที่จะยอมรับ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>การเลือกคำหลักคำแรกที่คุณจะสูญเสียเป้าหมายที่คุณต้องการที่จะมีเมื่อมีการเพิ่ม ทำให้ชิ้นที่สองคุณทำลายส่วนต่อประสานเองโดยสร้างวิธีการที่ควรทำสิ่งที่แตกต่างกันในการส่งคืนวัตถุ
จุด
คุณต้องการความ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เลย
ICloneableเป็นไปได้เพียงอย่างเดียวคือสำหรับประเภทค่าซึ่งสามารถหลีกเลี่ยงการชกมวยของวิธีการโคลนได้ และเนื่องจากโครงสร้างสามารถถูกโคลนได้ (ตื้น) โดยอัตโนมัติจึงไม่จำเป็นที่จะต้องใช้มัน (เว้นแต่คุณจะทำให้มันเจาะจงว่ามันหมายถึงการทำสำเนาลึก)
                    แม้ว่าคำถามจะเก่ามาก (5 ปีจากการเขียนคำตอบนี้ :) และได้รับคำตอบแล้ว แต่ฉันพบว่าบทความนี้ตอบคำถามได้ค่อนข้างดีตรวจสอบได้ที่นี่
แก้ไข:
นี่คือคำพูดจากบทความที่ตอบคำถาม (ให้แน่ใจว่าได้อ่านบทความเต็มรวมถึงสิ่งที่น่าสนใจอื่น ๆ ):
มีการอ้างอิงจำนวนมากบนอินเทอร์เน็ตที่ชี้ไปยังบล็อกโพสต์ในปี 2003 โดยแบรดเอบรัมส์ซึ่งเป็นเวลาที่ไมโครซอฟท์ใช้ในการหารือเกี่ยวกับความคิดบางอย่างเกี่ยวกับ ICloneable รายการบล็อกสามารถพบได้ที่อยู่นี้: การใช้ ICloneable แม้จะมีชื่อที่ทำให้เข้าใจผิด แต่บล็อกนี้ยังไม่เรียกใช้ ICloneable ส่วนใหญ่เป็นเพราะความสับสนตื้น / ลึก บทความลงท้ายด้วยคำแนะนำแบบตรง: หากคุณต้องการกลไกการโคลนกำหนดวิธีการโคลนหรือคัดลอกของคุณเองและตรวจสอบให้แน่ใจว่าเอกสารของคุณชัดเจนว่าเป็นสำเนาที่ลึกหรือตื้น รูปแบบที่เหมาะสมคือ:
public <type> Copy();
ปัญหาใหญ่คือพวกเขาไม่สามารถ จำกัด 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_typeclass A : ICloneable {      public object Clone() { return 1; } /* I can return whatever I want */     }
                    ICloneable<T>จะสามารถ จำกัดTให้ตรงกับประเภทของตัวเอง แต่ก็ไม่บังคับให้มีการใช้งานClone()เพื่อส่งคืนสิ่งใดจากระยะไกลซึ่งคล้ายกับวัตถุที่ถูกโคลน นอกจากนี้ฉันขอแนะนำว่าหากมีใครใช้ความแปรปรวนร่วมของอินเตอร์เฟซมันอาจจะดีที่สุดที่จะมีชั้นเรียนที่ใช้งานICloneableได้รับการปิดผนึกมีอินเทอร์เฟซICloneable<out T>รวมถึงSelfคุณสมบัติที่คาดว่าจะส่งคืนตัวเอง ...
                    ICloneable<BaseType> ในคำถามควรจะมีวิธีการในการโคลนซึ่งจะเรียกว่าตามประเภทที่ดำเนินการ การออกแบบนี้จะช่วยให้มีความเป็นไปได้ที่เราอาจต้องการมี a , a , a และ a ซึ่งภายหลังนั้นสามารถใช้งานได้ในรหัสซึ่งต้องใช้การลอกเลียนแบบของหรือต้องการ(แต่ไม่สนใจว่าจะลอกเลียนแบบ) ICloneable<ICloneable<BaseType>>BaseTypeprotectedICloneableContainerCloneableContainerFancyContainerCloneableFancyContainerContainerFancyContainer
                    FancyListชนิดที่สามารถโคลนได้อย่างสมเหตุสมผล แต่อนุพันธ์อาจยังคงสถานะโดยอัตโนมัติในไฟล์ดิสก์ (ที่ระบุในตัวสร้าง) ชนิดที่ได้รับไม่สามารถโคลนได้เนื่องจากสถานะของมันจะถูกแนบกับของซิงเกิลที่ไม่แน่นอน (ไฟล์) แต่ไม่ควรห้ามการใช้ประเภทที่ได้มาในสถานที่ซึ่งต้องการคุณสมบัติส่วนใหญ่FancyListแต่ไม่ต้องการ เพื่อโคลนมัน
                    มันเป็นคำถามที่ดีมาก ... คุณสามารถสร้างของคุณเองแม้ว่า:
interface ICloneable<T> : ICloneable
{
  new T Clone ( );
}Andrey กล่าวว่ามันถือว่าเป็น API ที่ไม่ดี แต่ฉันไม่เคยได้ยินอะไรเกี่ยวกับอินเทอร์เฟซนี้ที่เลิกใช้แล้ว และนั่นจะทำลายการเชื่อมต่อมากมาย ... วิธีการโคลนควรทำสำเนาแบบตื้น หากวัตถุมีการทำสำเนาแบบลึกก็สามารถใช้ Clone ที่มีการโหลดมากเกินไป (bool deep) ได้
แก้ไข: รูปแบบที่ฉันใช้สำหรับ "การโคลน" วัตถุกำลังผ่านต้นแบบในตัวสร้าง
class C
{
  public C ( C prototype )
  {
    ...
  }
}สิ่งนี้จะลบสถานการณ์การใช้โค้ดซ้ำซ้อนที่อาจเกิดขึ้น BTW กำลังพูดถึงข้อ จำกัด ของ ICloneable จริง ๆ หรือไม่ขึ้นอยู่กับวัตถุที่จะตัดสินใจว่าควรทำโคลนแบบตื้นหรือแบบโคลนนิ่งลึกหรือแม้แต่แบบโคลนแบบตื้น / ลึกบางส่วน เราควรจะสนใจจริง ๆ ตราบใดที่วัตถุทำงานตามที่ตั้งใจไว้? ในบางโอกาสการใช้งานโคลนที่ดีอาจรวมถึงการโคลนทั้งแบบตื้นและแบบลึก