ฉันจะโคลนรายการทั่วไปใน C # ได้อย่างไร


592

ฉันมีรายการวัตถุทั่วไปใน C # และต้องการโคลนรายการ รายการภายในรายการมี cloneable list.Clone()แต่มีไม่ได้ดูเหมือนจะเป็นตัวเลือกที่จะทำ

มีวิธีง่ายๆในการแก้ไขปัญหานี้หรือไม่?


44
คุณควรพูดว่าหากคุณกำลังมองหาสำเนาลึกหรือสำเนาตื้น
orip

10
สำเนาลึกและตื้นคืออะไร
พันเอก Panic


3
@orip ไม่ใช่clone()คำจำกัดความที่ลึกซึ้งใช่ไหม ใน C # คุณสามารถส่งพอยน์เตอร์ได้อย่างง่ายดายด้วย = ฉันคิดว่า
Chris

13
@Chris คัดลอกตื้นหนึ่งระดับลึกกว่าตัวชี้คัดลอก เช่นสำเนาตื้นของรายการจะมีองค์ประกอบเดียวกัน แต่จะเป็นรายการอื่น
orip

คำตอบ:


385

คุณสามารถใช้วิธีการขยาย

static class Extensions
{
    public static IList<T> Clone<T>(this IList<T> listToClone) where T: ICloneable
    {
        return listToClone.Select(item => (T)item.Clone()).ToList();
    }
}

71
ฉันคิดว่า List.ConvertAll อาจทำสิ่งนี้ในเวลาที่เร็วขึ้นเนื่องจากสามารถจัดสรรอาร์เรย์ทั้งหมดสำหรับรายการล่วงหน้าได้เมื่อเทียบกับการปรับขนาดตลอดเวลา
MichaelGG

2
@MichaelGG หากคุณไม่ต้องการแปลง แต่เพียงโคลน / ทำซ้ำรายการในรายการ จะใช้งานได้ไหม || var clonedList = ListOfStrings.ConvertAll (p => p);
IbrarMumtaz

29
@IbrarMumtaz: นั่นเหมือนกับ var clonedList = new List <string> (ListOfStrings)
Brandon Arnold

4
ทางออกที่ดี! โดยวิธีที่ฉันชอบรายการคงที่สาธารณะ <T> CLone <T> ... มันมีประโยชน์มากกว่าในกรณีเช่นนี้เพราะไม่จำเป็นต้องใช้การร่ายเพิ่มเติม: รายการ <MyType> cloned = listToClone.Clone ();
Plutoz

2
นี่เป็นการโคลนนิ่งลึก
George Birbilis

512

หากองค์ประกอบของคุณเป็นประเภทค่าคุณสามารถทำได้ดังนี้

List<YourType> newList = new List<YourType>(oldList);

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

List<ICloneable> oldList = new List<ICloneable>();
List<ICloneable> newList = new List<ICloneable>(oldList.Count);

oldList.ForEach((item) =>
    {
        newList.Add((ICloneable)item.Clone());
    });

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

หากประเภทองค์ประกอบของคุณไม่สนับสนุนICloneableแต่มีตัวสร้างสำเนาคุณสามารถทำสิ่งนี้แทน:

List<YourType> oldList = new List<YourType>();
List<YourType> newList = new List<YourType>(oldList.Count);

oldList.ForEach((item)=>
    {
        newList.Add(new YourType(item));
    });

ส่วนตัวฉันจะหลีกเลี่ยงICloneableเพราะจำเป็นต้องรับประกันสำเนาลึกของสมาชิกทุกคน แต่ฉันขอแนะนำตัวสร้างสำเนาหรือวิธีการจากโรงงานเช่นYourType.CopyFrom(YourType itemToCopy)ที่ส่งกลับตัวอย่างใหม่ของYourTypeผลตอบแทนว่าตัวอย่างใหม่ของ

ตัวเลือกใด ๆ เหล่านี้สามารถห่อด้วยวิธี (นามสกุลหรืออื่น ๆ )


1
ฉันคิดว่ารายการ <T> แปลงทั้งหมดอาจดูดีกว่าการสร้างรายการใหม่และทำ foreach + add
MichaelGG

2
@Dimitri: ไม่นั่นไม่ใช่ความจริง ปัญหาคือเมื่อICloneableมีการกำหนดนิยามไม่เคยระบุว่าโคลนนั้นลึกหรือตื้นดังนั้นคุณจึงไม่สามารถกำหนดประเภทของการดำเนินการโคลนที่จะทำเมื่อวัตถุนำไปใช้ ซึ่งหมายความว่าหากคุณต้องการทำโคลนแบบลึกList<T>คุณจะต้องทำโดยไม่ICloneableต้องแน่ใจว่ามันเป็นสำเนาลึก
Jeff Yates

5
ทำไมไม่ใช้วิธี AddRange ( newList.AddRange(oldList.Select(i => i.Clone())หรือnewList.AddRange(oldList.Select(i => new YourType(i))
phoog

5
@phoog: ฉันคิดว่ามันน้อยกว่าอ่าน / เข้าใจได้เมื่อสแกนโค้ดนั่นคือทั้งหมด การอ่านจะชนะสำหรับฉัน
Jeff Yates

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

84

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

List<int> oldList = new List<int>( );
// Populate oldList...

List<int> newList = oldList.GetRange(0, oldList.Count);

ยกมาจาก: สูตรทั่วไป


43
คุณยังสามารถทำสิ่งนี้ได้โดยใช้โครงสร้างของ List <T> เพื่อระบุ List <T> ที่จะคัดลอก เช่น var mustowClonedList = new List <MyObject> (originalList);
Arkiliknam

9
List<int> newList = oldList.ToList()ฉันมักจะใช้ ผลเหมือนกัน อย่างไรก็ตามทางออกของ Arkiliknam นั้นดีที่สุดสำหรับการอ่านในความคิดของฉัน
Dan Bechard

82
public static object DeepClone(object obj) 
{
  object objResult = null;
  using (MemoryStream  ms = new MemoryStream())
  {
    BinaryFormatter  bf =   new BinaryFormatter();
    bf.Serialize(ms, obj);

    ms.Position = 0;
    objResult = bf.Deserialize(ms);
  }
  return objResult;
}

นี่เป็นวิธีหนึ่งในการทำกับ C # และ. NET 2.0 [Serializable()]วัตถุของคุณต้องการที่จะเป็น เป้าหมายคือการสูญเสียการอ้างอิงทั้งหมดและสร้างใหม่


11
+1 - ฉันชอบคำตอบนี้ - รวดเร็วสกปรกน่ารังเกียจและมีประสิทธิภาพมาก ฉันใช้ใน Silverlight และใช้ DataContractSerializer เนื่องจาก BinarySerializer ไม่พร้อมใช้งาน ใครจำเป็นต้องเขียนหน้าของรหัสโคลนวัตถุเมื่อคุณสามารถทำได้? :)
slugster

3
ฉันชอบสิ่งนี้. แม้ว่าการทำสิ่งที่ถูกต้องเป็นเรื่องที่ดี แต่ก็รวดเร็วและสกปรกบ่อยครั้งที่มีประโยชน์
Odrade

3
ด่วน! แต่: ทำไมสกปรก
raiserle

2
โคลนอันนี้ลึกและง่ายและรวดเร็ว ระวังคำแนะนำอื่น ๆ ในหน้านี้ ฉันลองหลายครั้งแล้วก็ไม่ลอกแบบลึก
Randall ถึง

2
เฉพาะด้านลบหากคุณสามารถเรียกได้ว่านั่นคือคลาสของคุณจะต้องทำเครื่องหมาย Serializable เพื่อให้สามารถใช้งานได้
Tuukka Haapaniemi

29

หากต้องการโคลนรายการเพียงแค่เรียก. ToList () สิ่งนี้สร้างสำเนาตื้น

Microsoft (R) Roslyn C# Compiler version 2.3.2.62116
Loading context from 'CSharpInteractive.rsp'.
Type "#help" for more information.
> var x = new List<int>() { 3, 4 };
> var y = x.ToList();
> x.Add(5)
> x
List<int>(3) { 3, 4, 5 }
> y
List<int>(2) { 3, 4 }
> 

3
วิธีแก้ปัญหาที่ง่ายที่สุดโดยไกล
curveorzos

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

22

หลังจากทำการดัดแปลงเล็กน้อยคุณสามารถโคลนได้:

public static T DeepClone<T>(T obj)
{
    T objResult;
    using (MemoryStream ms = new MemoryStream())
    {
        BinaryFormatter bf = new BinaryFormatter();
        bf.Serialize(ms, obj);
        ms.Position = 0;
        objResult = (T)bf.Deserialize(ms);
    }
    return objResult;
}

อย่าลืมว่า T ควรเป็นแบบอนุกรมมิฉะนั้นคุณจะได้รับ System.Runtime.Serialization.SerializationException
Bence Végert

คำตอบที่ดี. หนึ่งคำใบ้:คุณสามารถเพิ่มif (!obj.GetType().IsSerializable) return default(T);เป็นคำสั่งแรกที่ป้องกันข้อยกเว้น และถ้าคุณเปลี่ยนเป็นวิธีเสริมคุณสามารถใช้ตัวดำเนินการ Elvis เช่นvar b = a?.DeepClone();( var a = new List<string>() { "a", "b" }; ยกตัวอย่าง)
แมตต์

15

List<T>วิธีที่ดีที่สุดในการโคลนรายการคือการสร้างรายการใหม่ที่มีรายการเก่าเป็นพารามิเตอร์การรวบรวมเว้นแต่ว่าคุณต้องการโคลนจริงของทุกวัตถุ

List<T> myList = ...;
List<T> cloneOfMyList = new List<T>(myList);

การเปลี่ยนแปลงmyListเช่นการแทรกหรือลบจะไม่ส่งผลกระทบcloneOfMyListและในทางกลับกัน

วัตถุที่แท้จริงของรายการทั้งสองนั้นยังคงเหมือนเดิม


ฉันเห็นด้วยกับผู้ใช้ 49126 ฉันเห็นว่ามันเป็นสำเนาตื้น ๆ และการเปลี่ยนแปลงที่ทำกับรายการหนึ่งจะปรากฏในรายการอื่น
Seidleroni

1
@ Seidleroni คุณผิด การเปลี่ยนแปลงที่เกิดขึ้นกับรายการที่เกิดขึ้นจะอยู่ในรายการอื่นการเปลี่ยนแปลงในรายการจะไม่เกิดขึ้น
เวลลิงตัน Zanelli

นี่คือสำเนาตื้น ๆ
Elliot Chen

นี่เป็นสำเนาตื้น ๆ อย่างไร
mko

2
@WellingtonZanelli เพิ่งยืนยันว่าการลบองค์ประกอบจาก myList ลบออกจาก cloneOfMyList เช่นกัน
Nick Gallimore

13

ใช้ AutoMapper (หรือการแมปใดก็ได้ที่คุณต้องการ) การโคลนนั้นง่ายและบำรุงรักษาได้มาก

กำหนดแผนที่ของคุณ:

Mapper.CreateMap<YourType, YourType>();

ทำเวทมนตร์:

YourTypeList.ConvertAll(Mapper.Map<YourType, YourType>);

13

หากคุณสนใจเฉพาะประเภทค่า ...

และคุณรู้ประเภท:

List<int> newList = new List<int>(oldList);

หากคุณไม่เคยรู้จักประเภทนี้มาก่อนคุณจะต้องใช้ฟังก์ชันตัวช่วย:

List<T> Clone<T>(IEnumerable<T> oldList)
{
    return newList = new List<T>(oldList);
}

เพียงแค่:

List<string> myNewList = Clone(myOldList);

15
สิ่งนี้ไม่ได้ลอกแบบองค์ประกอบต่างๆ
Jeff Yates

10

หากคุณอ้างอิง Newtonsoft.Json ในโครงการของคุณอยู่แล้วและวัตถุของคุณเป็นอนุกรมคุณสามารถใช้:

List<T> newList = JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(listToCopy))

อาจไม่ใช่วิธีที่มีประสิทธิภาพที่สุดในการทำ แต่ถ้าคุณไม่ทำ 100s จาก 1,000 ครั้งคุณอาจไม่เห็นความแตกต่างของความเร็ว


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

1
รหัสนี้ทำงานได้อย่างยอดเยี่ยมสำหรับฉันสำหรับการโคลนนิ่งลึก แอพกำลังย้ายเอกสารสำเร็จรูปจาก Dev เป็น QA ไปยัง Prod แต่ละวัตถุเป็นแพ็กเก็ตของวัตถุแม่แบบเอกสารหลายชุดและแต่ละเอกสารจะประกอบด้วยรายการของวัตถุย่อหน้า รหัสนี้ให้ฉันเป็นอนุกรมวัตถุ. NET "แหล่งที่มา" และ deserialize พวกเขาทันทีไปยังวัตถุ "เป้าหมาย" ใหม่ซึ่งได้รับการบันทึกลงในฐานข้อมูล SQL ในสภาพแวดล้อมที่แตกต่างกัน หลังจากการวิจัยมากมายฉันพบหลายสิ่งหลายอย่างซึ่งค่อนข้างยุ่งยากและตัดสินใจลอง วิธีการที่สั้นและยืดหยุ่นนี้คือ "ถูกต้อง"!
Developer63

3
public static Object CloneType(Object objtype)
{
    Object lstfinal = new Object();

    using (MemoryStream memStream = new MemoryStream())
    {
        BinaryFormatter binaryFormatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone));
        binaryFormatter.Serialize(memStream, objtype); memStream.Seek(0, SeekOrigin.Begin);
        lstfinal = binaryFormatter.Deserialize(memStream);
    }

    return lstfinal;
}

3
public class CloneableList<T> : List<T>, ICloneable where T : ICloneable
{
  public object Clone()
  {
    var clone = new List<T>();
    ForEach(item => clone.Add((T)item.Clone()));
    return clone;
  }
}

3
    public List<TEntity> Clone<TEntity>(List<TEntity> o1List) where TEntity : class , new()
    {
        List<TEntity> retList = new List<TEntity>();
        try
        {
            Type sourceType = typeof(TEntity);
            foreach(var o1 in o1List)
            {
                TEntity o2 = new TEntity();
                foreach (PropertyInfo propInfo in (sourceType.GetProperties()))
                {
                    var val = propInfo.GetValue(o1, null);
                    propInfo.SetValue(o2, val);
                }
                retList.Add(o2);
            }
            return retList;
        }
        catch
        {
            return retList;
        }
    }

3

เพื่อนของฉัน Gregor Martinovic และฉันคิดวิธีแก้ปัญหาง่ายๆนี้โดยใช้ JavaScript Serializer ไม่จำเป็นต้องตั้งค่าสถานะคลาสเป็น Serializable และในการทดสอบของเราโดยใช้ Newtonsoft JsonSerializer เร็วกว่าการใช้ BinaryFormatter ด้วยวิธีการขยายสามารถใช้งานได้กับทุกวัตถุ

ตัวเลือก JavascriptSerializer มาตรฐาน. NET:

public static T DeepCopy<T>(this T value)
{
    JavaScriptSerializer js = new JavaScriptSerializer();

    string json = js.Serialize(value);

    return js.Deserialize<T>(json);
}

ตัวเลือกที่เร็วกว่าโดยใช้Newtonsoft JSON :

public static T DeepCopy<T>(this T value)
{
    string json = JsonConvert.SerializeObject(value);

    return JsonConvert.DeserializeObject<T>(json);
}

2
สมาชิกส่วนตัวไม่ได้ถูกโคลนโดยใช้วิธี JSON stackoverflow.com/a/78612/885627
himanshupareek66


3

ฉันจะโชคดีถ้าใครอ่านสิ่งนี้ ... แต่เพื่อไม่ให้ส่งคืนรายการประเภทวัตถุในวิธีการโคลนของฉันฉันได้สร้างส่วนต่อประสาน:

public interface IMyCloneable<T>
{
    T Clone();
}

จากนั้นฉันระบุนามสกุล:

public static List<T> Clone<T>(this List<T> listToClone) where T : IMyCloneable<T>
{
    return listToClone.Select(item => (T)item.Clone()).ToList();
}

และนี่คือการใช้อินเทอร์เฟซในซอฟต์แวร์ทำเครื่องหมาย A / V ของฉัน ฉันต้องการให้ Clone () method คืนรายการของ VidMark (ในขณะที่ส่วนต่อประสาน ICloneable ต้องการให้วิธีการคืนค่ารายการวัตถุ):

public class VidMark : IMyCloneable<VidMark>
{
    public long Beg { get; set; }
    public long End { get; set; }
    public string Desc { get; set; }
    public int Rank { get; set; } = 0;

    public VidMark Clone()
    {
        return (VidMark)this.MemberwiseClone();
    }
}

และในที่สุดการใช้ส่วนขยายภายในชั้นเรียน:

private List<VidMark> _VidMarks;
private List<VidMark> _UndoVidMarks;

//Other methods instantiate and fill the lists

private void SetUndoVidMarks()
{
    _UndoVidMarks = _VidMarks.Clone();
}

มีใครชอบไหม การปรับปรุงใด ๆ


2

คุณยังสามารถเพียงแค่แปลงรายการไปยังอาร์เรย์ใช้แล้วโคลนอาร์เรย์ใช้ToArray Array.Clone(...)วิธีการที่รวมอยู่ในคลาส Array นั้นขึ้นอยู่กับความต้องการของคุณ


สิ่งนี้ไม่ทำงาน การเปลี่ยนแปลงค่าในอาร์เรย์โคลนยังคงเปลี่ยนค่าในรายการเดิม
Bernoulli Lizard

คุณสามารถใช้ var clonedList = ListOfStrings.ConvertAll (p => p); ตามที่กำหนดโดย @IbrarMumtaz .... ทำงานได้อย่างมีประสิทธิภาพ ... การเปลี่ยนแปลงในรายการหนึ่งจะถูกเก็บไว้ในตัวเองและจะไม่สะท้อนในรายการอื่น
zainul

2

คุณสามารถใช้วิธีการขยาย:

namespace extension
{
    public class ext
    {
        public static List<double> clone(this List<double> t)
        {
            List<double> kop = new List<double>();
            int x;
            for (x = 0; x < t.Count; x++)
            {
                kop.Add(t[x]);
            }
            return kop;
        }
   };

}

คุณสามารถโคลนวัตถุทั้งหมดโดยใช้สมาชิกประเภทค่าของพวกเขาพิจารณาคลาสนี้:

public class matrix
{
    public List<List<double>> mat;
    public int rows,cols;
    public matrix clone()
    { 
        // create new object
        matrix copy = new matrix();
        // firstly I can directly copy rows and cols because they are value types
        copy.rows = this.rows;  
        copy.cols = this.cols;
        // but now I can no t directly copy mat because it is not value type so
        int x;
        // I assume I have clone method for List<double>
        for(x=0;x<this.mat.count;x++)
        {
            copy.mat.Add(this.mat[x].clone());
        }
        // then mat is cloned
        return copy; // and copy of original is returned 
    }
};

หมายเหตุ: หากคุณทำการเปลี่ยนแปลงใด ๆ กับสำเนา (หรือโคลน) จะไม่มีผลกับวัตถุต้นฉบับ


2

หากคุณต้องการรายการที่มีความจุเท่ากันคุณสามารถลองทำสิ่งนี้:

public static List<T> Clone<T>(this List<T> oldList)
{
    var newList = new List<T>(oldList.Capacity);
    newList.AddRange(oldList);
    return newList;
}

1

ฉันสร้างส่วนขยายของตัวเองขึ้นมาเพื่อแปลง ICollection ของรายการที่ไม่ใช้ IClonable

static class CollectionExtensions
{
    public static ICollection<T> Clone<T>(this ICollection<T> listToClone)
    {
        var array = new T[listToClone.Count];
        listToClone.CopyTo(array,0);
        return array.ToList();
    }
}

ดูเหมือนว่าบางคอลเลกชัน (เช่น DataItem's SelectedItems at Silverlight) ข้ามการใช้งาน CopyTo ซึ่งเป็นปัญหาของวิธีการนี้
George Birbilis

1

ฉันใช้ automapper เพื่อคัดลอกวัตถุ ฉันเพิ่งตั้งค่าการแมปที่แมปวัตถุหนึ่งไปยังตัวเอง คุณสามารถปิดการดำเนินการนี้ในแบบที่คุณชอบ

http://automapper.codeplex.com/


1

การใช้งานนักแสดงอาจมีประโยชน์ในกรณีนี้สำหรับสำเนาตื้น ๆ :

IList CloneList(IList list)
{
    IList result;
    result = (IList)Activator.CreateInstance(list.GetType());
    foreach (object item in list) result.Add(item);
    return result;
}

นำไปใช้กับรายการทั่วไป:

List<T> Clone<T>(List<T> argument) => (List<T>)CloneList(argument);

1

สำหรับการทำสำเนาแบบลึก ICloneable เป็นวิธีการแก้ปัญหาที่ถูกต้อง แต่นี่เป็นวิธีการที่คล้ายกันกับ ICloneable โดยใช้ Constructor แทนที่จะเป็นส่วนต่อประสาน ICloneable

public class Student
{
  public Student(Student student)
  {
    FirstName = student.FirstName;
    LastName = student.LastName;
  }

  public string FirstName { get; set; }
  public string LastName { get; set; }
}

// wherever you have the list
List<Student> students;

// and then where you want to make a copy
List<Student> copy = students.Select(s => new Student(s)).ToList();

คุณจะต้องมีห้องสมุดต่อไปนี้ที่คุณทำสำเนา

using System.Linq

คุณสามารถใช้ for for loop แทน System.Linq แต่ Linq ทำให้รัดกุมและสะอาด ในทำนองเดียวกันคุณสามารถทำตามคำตอบอื่น ๆ ที่แนะนำและทำวิธีการขยาย ฯลฯ แต่ไม่มีสิ่งที่จำเป็น


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

2
แม้จะมี ICloneable คุณต้องมีวิธี "Clone" ในชั้นเรียนของคุณ ยกเว้นว่าคุณใช้การสะท้อน (ซึ่งคุณสามารถใช้ในวิธีการข้างต้น), วิธี Clone จะดูคล้ายกับวิธีการสร้างตัวคัดลอกด้านบนและจะประสบปัญหาเดียวกันกับที่ต้องอัปเดตเขตข้อมูลใหม่ / ที่เปลี่ยนแปลง แต่นั่นคือการพูดว่า "ชั้นจะต้องมีการปรับปรุงเมื่อสาขาของการเปลี่ยนแปลงชั้นเรียน" แน่นอนมัน;)
ztorstri

0

รหัสต่อไปนี้ควรโอนไปยังรายการที่มีการเปลี่ยนแปลงน้อยที่สุด

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

// Example Usage
int[] indexes = getRandomUniqueIndexArray(selectFrom.Length, toSet.Length);

for(int i = 0; i < toSet.Length; i++)
    toSet[i] = selectFrom[indexes[i]];


private int[] getRandomUniqueIndexArray(int length, int count)
{
    if(count > length || count < 1 || length < 1)
        return new int[0];

    int[] toReturn = new int[count];
    if(count == length)
    {
        for(int i = 0; i < toReturn.Length; i++) toReturn[i] = i;
        return toReturn;
    }

    Random r = new Random();
    int startPos = count - 1;
    for(int i = startPos; i >= 0; i--)
    {
        int index = r.Next(length - i);
        for(int j = startPos; j > i; j--)
            if(toReturn[j] >= index)
                toReturn[j]++;
        toReturn[i] = index;
    }

    return toReturn;
}

0

อีกอย่าง: คุณสามารถใช้การสะท้อน หากคุณทำการแคชอย่างถูกต้องมันจะทำการโคลนนิ่งวัตถุ 1,000,000 รายการใน 5.6 วินาที (น่าเศร้า 16.4 วินาทีด้วยวัตถุภายใน)

[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
public class Person
{
       ...
      Job JobDescription
       ...
}

[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
public class Job
{...
}

private static readonly Type stringType = typeof (string);

public static class CopyFactory
{
    static readonly Dictionary<Type, PropertyInfo[]> ProperyList = new Dictionary<Type, PropertyInfo[]>();

    private static readonly MethodInfo CreateCopyReflectionMethod;

    static CopyFactory()
    {
        CreateCopyReflectionMethod = typeof(CopyFactory).GetMethod("CreateCopyReflection", BindingFlags.Static | BindingFlags.Public);
    }

    public static T CreateCopyReflection<T>(T source) where T : new()
    {
        var copyInstance = new T();
        var sourceType = typeof(T);

        PropertyInfo[] propList;
        if (ProperyList.ContainsKey(sourceType))
            propList = ProperyList[sourceType];
        else
        {
            propList = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            ProperyList.Add(sourceType, propList);
        }

        foreach (var prop in propList)
        {
            var value = prop.GetValue(source, null);
            prop.SetValue(copyInstance,
                value != null && prop.PropertyType.IsClass && prop.PropertyType != stringType ? CreateCopyReflectionMethod.MakeGenericMethod(prop.PropertyType).Invoke(null, new object[] { value }) : value, null);
        }

        return copyInstance;
    }

ฉันวัดด้วยวิธีง่ายๆโดยใช้คลาส Watcher

 var person = new Person
 {
     ...
 };

 for (var i = 0; i < 1000000; i++)
 {
    personList.Add(person);
 }
 var watcher = new Stopwatch();
 watcher.Start();
 var copylist = personList.Select(CopyFactory.CreateCopyReflection).ToList();
 watcher.Stop();
 var elapsed = watcher.Elapsed;

ผลลัพธ์:ด้วยวัตถุภายใน PersonInstance - 16.4, PersonInstance = null - 5.6

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

ฉันยังไม่ได้ทดสอบการทำให้เป็นอันดับ แต่ฉันสงสัยในการปรับปรุงด้วยการเรียนเป็นล้านครั้ง ฉันจะลอง protobuf / newton ที่รวดเร็ว

PS: เพื่อความง่ายในการอ่านฉันใช้ Auto-Property ที่นี่เท่านั้น ฉันสามารถอัปเดตด้วย FieldInfo หรือคุณควรใช้สิ่งนี้ด้วยตัวเอง

ฉันเพิ่งทดสอบProtocolizer บัฟเฟอร์ serializer กับฟังก์ชั่น DeepClone ออกจากกล่อง มันชนะด้วย 4.2 วินาทีในวัตถุเรียบง่ายนับล้านชิ้น แต่เมื่อมันมาถึงวัตถุภายในมันจะชนะด้วยผลลัพธ์ 7.4 วินาที

Serializer.DeepClone(personList);

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


0

มีวิธีที่ง่ายในการโคลนวัตถุใน C # โดยใช้ JSON serializer และ deserializer

คุณสามารถสร้างคลาสเสริม:

using Newtonsoft.Json;

static class typeExtensions
{
    [Extension()]
    public static T jsonCloneObject<T>(T source)
    {
    string json = JsonConvert.SerializeObject(source);
    return JsonConvert.DeserializeObject<T>(json);
    }
}

ในการโคลนและวัตถุ:

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