มีเหตุผลบางประการที่ทำไม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_type
class 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>>
BaseType
protected
ICloneable
Container
CloneableContainer
FancyContainer
CloneableFancyContainer
Container
FancyContainer
FancyList
ชนิดที่สามารถโคลนได้อย่างสมเหตุสมผล แต่อนุพันธ์อาจยังคงสถานะโดยอัตโนมัติในไฟล์ดิสก์ (ที่ระบุในตัวสร้าง) ชนิดที่ได้รับไม่สามารถโคลนได้เนื่องจากสถานะของมันจะถูกแนบกับของซิงเกิลที่ไม่แน่นอน (ไฟล์) แต่ไม่ควรห้ามการใช้ประเภทที่ได้มาในสถานที่ซึ่งต้องการคุณสมบัติส่วนใหญ่FancyList
แต่ไม่ต้องการ เพื่อโคลนมัน
มันเป็นคำถามที่ดีมาก ... คุณสามารถสร้างของคุณเองแม้ว่า:
interface ICloneable<T> : ICloneable
{
new T Clone ( );
}
Andrey กล่าวว่ามันถือว่าเป็น API ที่ไม่ดี แต่ฉันไม่เคยได้ยินอะไรเกี่ยวกับอินเทอร์เฟซนี้ที่เลิกใช้แล้ว และนั่นจะทำลายการเชื่อมต่อมากมาย ... วิธีการโคลนควรทำสำเนาแบบตื้น หากวัตถุมีการทำสำเนาแบบลึกก็สามารถใช้ Clone ที่มีการโหลดมากเกินไป (bool deep) ได้
แก้ไข: รูปแบบที่ฉันใช้สำหรับ "การโคลน" วัตถุกำลังผ่านต้นแบบในตัวสร้าง
class C
{
public C ( C prototype )
{
...
}
}
สิ่งนี้จะลบสถานการณ์การใช้โค้ดซ้ำซ้อนที่อาจเกิดขึ้น BTW กำลังพูดถึงข้อ จำกัด ของ ICloneable จริง ๆ หรือไม่ขึ้นอยู่กับวัตถุที่จะตัดสินใจว่าควรทำโคลนแบบตื้นหรือแบบโคลนนิ่งลึกหรือแม้แต่แบบโคลนแบบตื้น / ลึกบางส่วน เราควรจะสนใจจริง ๆ ตราบใดที่วัตถุทำงานตามที่ตั้งใจไว้? ในบางโอกาสการใช้งานโคลนที่ดีอาจรวมถึงการโคลนทั้งแบบตื้นและแบบลึก