วัตถุโคลนนิ่งลึก


2226

ฉันต้องการทำสิ่งที่ชอบ:

MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();

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

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

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


81
อาจมีประโยชน์: "ทำไมการคัดลอกวัตถุเป็นสิ่งที่น่ากลัว?" agiledeveloper.com/articles/cloning072002.htm
Pedro77

stackoverflow.com/questions/8025890/…โซลูชันอื่น ...
Felix K.

18
คุณควรดู AutoMapper
Daniel Little

3
ทางออกของคุณซับซ้อนกว่านี้ฉันหลงทางอ่าน ... เฮ่อ ฉันใช้อินเทอร์เฟซ DeepClone ส่วนต่อประสานสาธารณะ IDeepCloneable <T> {T DeepClone (); }
Pedro77

1
@ Pedro77 - แต่ที่น่าสนใจที่ปลายบทความขึ้นพูดในการสร้างวิธีการในชั้นเรียนแล้วมีมันโทรภายในคอนสตรัคเอกชนที่ได้รับผ่านclone thisดังนั้นการทำสำเนาจึงเป็นเรื่องที่น่ากลัว แต่การทำสำเนาอย่างระมัดระวัง (และบทความควรอ่านอย่างแน่นอน) ไม่ใช่ ; ^)
รัฟฟิน

คำตอบ:


1715

ในขณะที่การปฏิบัติมาตรฐานคือการใช้ICloneableอินเทอร์เฟซ (อธิบายไว้ที่นี่ดังนั้นฉันจะไม่สำรอก) นี่คือเครื่องถ่ายเอกสารวัตถุโคลนที่ดีที่ฉันพบในThe Code Projectเมื่อไม่นานมานี้และรวมไว้ในเนื้อหาของเรา

ดังที่กล่าวไว้ที่อื่นมันไม่จำเป็นต้องวัตถุของคุณจะเป็นแบบ

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

/// <summary>
/// Reference Article http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
/// Provides a method for performing a deep copy of an object.
/// Binary Serialization is used to perform the copy.
/// </summary>
public static class ObjectCopier
{
    /// <summary>
    /// Perform a deep Copy of the object.
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T Clone<T>(T source)
    {
        if (!typeof(T).IsSerializable)
        {
            throw new ArgumentException("The type must be serializable.", nameof(source));
        }

        // Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        IFormatter formatter = new BinaryFormatter();
        Stream stream = new MemoryStream();
        using (stream)
        {
            formatter.Serialize(stream, source);
            stream.Seek(0, SeekOrigin.Begin);
            return (T)formatter.Deserialize(stream);
        }
    }
}

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

และด้วยการใช้วิธีการขยาย (เช่นจากแหล่งอ้างอิงเดิม):

ในกรณีที่คุณต้องการใช้วิธีการขยายใหม่ของ C # 3.0 ให้เปลี่ยนวิธีการที่มีลายเซ็นต่อไปนี้:

public static T Clone<T>(this T source)
{
   //...
}

objectBeingCloned.Clone();ตอนนี้เรียกวิธีก็จะกลายเป็น

แก้ไข (10 มกราคม 2558) คิดว่าฉันจะทบทวนสิ่งนี้เพื่อพูดถึงว่าฉันเพิ่งเริ่มใช้ (Newtonsoft) Json เพื่อทำสิ่งนี้ควรเบาลงและหลีกเลี่ยงแท็ก [Serializable] ( NB @atconway ได้ชี้ให้เห็นในความคิดเห็นที่สมาชิกส่วนตัวไม่ได้โคลนโดยใช้วิธีการ JSON)

/// <summary>
/// Perform a deep Copy of the object, using Json as a serialisation method. NOTE: Private members are not cloned using this method.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneJson<T>(this T source)
{            
    // Don't serialize a null object, simply return the default for that object
    if (Object.ReferenceEquals(source, null))
    {
        return default(T);
    }

    // initialize inner objects individually
    // for example in default constructor some list property initialized with some values,
    // but in 'source' these items are cleaned -
    // without ObjectCreationHandling.Replace default constructor values will be added to result
    var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};

    return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}

24
stackoverflow.com/questions/78536/cloning-objects-in-c/...มีการเชื่อมโยงไปโค้ดข้างต้น [อ้างอิงและสองการใช้งานอื่น ๆ เช่นคนหนึ่งซึ่งมีความเหมาะสมมากขึ้นในบริบทของฉัน] อีกต่อไป
Ruben Bartelink

102
อนุกรม / deserialization เกี่ยวข้องกับค่าใช้จ่ายที่สำคัญที่ไม่จำเป็น ดูส่วนต่อประสาน ICloneable และ. Clone ในวิธีการ Clone ใน C #
3 มิติ

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

16
@Amir: จริง ๆ แล้วไม่ใช่: typeof(T).IsSerializableก็เป็นจริงเช่นกันหากมีการทำเครื่องหมายประเภทด้วย[Serializable]แอตทริบิวต์ ไม่จำเป็นต้องใช้ISerializableอินเทอร์เฟซ
Daniel Gehriger

11
แค่คิดว่าฉันจะพูดถึงว่าในขณะที่วิธีนี้มีประโยชน์และฉันใช้ด้วยตัวเองหลายครั้งมันไม่เข้ากันได้กับ Medium Trust - ดังนั้นโปรดระวังถ้าคุณเขียนโค้ดที่ต้องการความเข้ากันได้ BinaryFormatter เข้าถึงฟิลด์ส่วนตัวและดังนั้นจึงไม่สามารถทำงานในสิทธิ์เริ่มต้นสำหรับสภาพแวดล้อมที่เชื่อถือได้บางส่วน คุณสามารถลอง serializer อื่น แต่ให้แน่ใจว่าโทรของคุณรู้ว่าโคลนอาจไม่สมบูรณ์แบบถ้าวัตถุที่เข้ามาอาศัยในเขตข้อมูลส่วนตัว
Alex Norcliffe

298

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

public static T Clone<T>(T source)
{
    var serialized = JsonConvert.SerializeObject(source);
    return JsonConvert.DeserializeObject<T>(serialized);
}

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

public static class SystemExtension
{
    public static T Clone<T>(this T source)
    {
        var serialized = JsonConvert.SerializeObject(source);
        return JsonConvert.DeserializeObject<T>(serialized);
    }
}

13
solutiojn เป็นได้เร็วขึ้นกว่าการแก้ปัญหา BinaryFormatter ที่.NET อันดับการเปรียบเทียบผลการดำเนินงาน
esskar

3
ขอบคุณสำหรับสิ่งนี้. ฉันสามารถทำสิ่งเดียวกันกับ BSON serializer ที่มาพร้อมกับไดรเวอร์ MongoDB สำหรับ C #
Mark Ewer

3
นี่เป็นวิธีที่ดีที่สุดสำหรับฉันอย่างไรก็ตามฉันใช้Newtonsoft.Json.JsonConvertแต่มันก็เหมือนกัน
Pierre

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

2
ฉันคิดว่านี่เป็นทางออกที่ดีที่สุดเนื่องจากการติดตั้งสามารถใช้กับภาษาโปรแกรมส่วนใหญ่ได้
mr5

178

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

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

สิ่งที่ฉันมักจะทำก็คือทำCopyวิธีที่ทำในสิ่งที่ฉันต้องการ


ฉันยังไม่ชัดเจนว่าทำไม ICloneable จึงถือว่าคลุมเครือ ให้ประเภทเช่นพจนานุกรม (ของ T, U) ฉันคาดหวังว่า ICloneable.Clone ควรทำสิ่งที่ระดับของการคัดลอกลึกและตื้นมีความจำเป็นเพื่อให้พจนานุกรมใหม่เป็นพจนานุกรมอิสระที่ประกอบด้วย T's และ U เดียวกัน (เนื้อหา struct, และ / หรือการอ้างอิงวัตถุ) เป็นต้นฉบับ ความกำกวมอยู่ที่ไหน เพื่อให้แน่ใจว่า ICloneable ทั่วไป (ของ T) ซึ่งสืบทอด ISelf (ของ T) ซึ่งรวมถึงวิธีการ "ตัวเอง" จะดีกว่ามาก แต่ฉันไม่เห็นความคลุมเครือเกี่ยวกับการโคลนนิ่งตื้นและลึก
supercat

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

@Kyralessa บทความ Microsoft MSDN จริง ๆ แล้วระบุปัญหานี้มากโดยไม่รู้ว่าถ้าคุณกำลังขอสำเนาลึกหรือตื้น
บดขยี้

คำตอบจากstackoverflow.com/questions/129389/ …ซ้ำอธิบายส่วนขยายการคัดลอกอ้างอิงจากการเรียกซ้ำ MembershipClone
Michael Freidgeim

123

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

ดังนั้นฉันจะคัดลอกส่วนที่เกี่ยวข้องของการอ้างอิงทั้งสองที่นี่ ด้วยวิธีนี้เราสามารถมี:

สิ่งที่ดีที่สุดสำหรับการโคลนวัตถุใน C sharp!

ก่อนอื่นสิ่งเหล่านี้คือตัวเลือกทั้งหมดของเรา:

บทความได้อย่างรวดเร็วลึกคัดลอกโดย Expression ต้นไม้ ยังมีการเปรียบเทียบประสิทธิภาพของการโคลนโดยอันดับสะท้อนและต้นไม้แสดงออก

ทำไมฉันถึงเลือกICloneable (เช่นด้วยตนเอง)

นาย Venkat Subramaniam (ลิงค์ซ้ำซ้อนที่นี่) อธิบายในรายละเอียดมากว่าทำไม

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

นี่เป็นข้อสรุปที่แก้ไขเล็กน้อยของฉัน:

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

หวังว่าการติดตั้งนี้จะทำให้สิ่งต่าง ๆ ชัดเจน:

public class Person : ICloneable
{
    private final Brain brain; // brain is final since I do not want 
                // any transplant on it once created!
    private int age;
    public Person(Brain aBrain, int theAge)
    {
        brain = aBrain; 
        age = theAge;
    }
    protected Person(Person another)
    {
        Brain refBrain = null;
        try
        {
            refBrain = (Brain) another.brain.clone();
            // You can set the brain in the constructor
        }
        catch(CloneNotSupportedException e) {}
        brain = refBrain;
        age = another.age;
    }
    public String toString()
    {
        return "This is person with " + brain;
        // Not meant to sound rude as it reads!
    }
    public Object clone()
    {
        return new Person(this);
    }
    
}

ตอนนี้ให้พิจารณาคลาสที่ได้รับจากบุคคล

public class SkilledPerson extends Person
{
    private String theSkills;
    public SkilledPerson(Brain aBrain, int theAge, String skills)
    {
        super(aBrain, theAge);
        theSkills = skills;
    }
    protected SkilledPerson(SkilledPerson another)
    {
        super(another);
        theSkills = another.theSkills;
    }

    public Object clone()
    {
        return new SkilledPerson(this);
    }
    public String toString()
    {
        return "SkilledPerson: " + super.toString();
    }
}

คุณอาจลองใช้รหัสต่อไปนี้:

public class User
{
    public static void play(Person p)
    {
        Person another = (Person) p.clone();
        System.out.println(p);
        System.out.println(another);
    }
    public static void main(String[] args)
    {
        Person sam = new Person(new Brain(), 1);
        play(sam);
        SkilledPerson bob = new SkilledPerson(new SmarterBrain(), 1, "Writer");
        play(bob);
    }
}

ผลผลิตที่ได้จะเป็น:

This is person with Brain@1fcc69
This is person with Brain@253498
SkilledPerson: This is person with SmarterBrain@1fef6f
SkilledPerson: This is person with SmarterBrain@209f4e

สังเกตว่าถ้าเรานับจำนวนของวัตถุโคลนที่นำมาใช้ที่นี่จะเก็บจำนวนของวัตถุที่ถูกต้อง


6
MS แนะนำว่าอย่าใช้ICloneableสำหรับสมาชิกสาธารณะ "เนื่องจากผู้โทร Clone ไม่สามารถพึ่งพาวิธีการดำเนินการโคลนที่คาดการณ์ได้เราขอแนะนำว่า ICloneable ไม่สามารถใช้งานใน API สาธารณะ" msdn.microsoft.com/en-us/library/…อย่างไรก็ตามตามคำอธิบายของ Venkat Subramaniam ในบทความที่เชื่อมโยงของคุณฉันคิดว่ามันสมเหตุสมผลที่จะใช้ในสถานการณ์นี้ตราบใดที่ผู้สร้างวัตถุ ICloneable มีความลึก การทำความเข้าใจกับคุณสมบัติที่ควรจะลึกและคัดลอกตื้น (เช่นสำเนาลึกสมอง, สำเนาเมืองตื้น)
BateTech

ก่อนอื่นฉันยังห่างไกลจากผู้เชี่ยวชาญในหัวข้อนี้ (API สาธารณะ) ฉันคิดว่าครั้งเดียวที่คำพูด MS ทำให้รู้สึกมาก และฉันไม่คิดว่ามันปลอดภัยที่จะสมมติว่าผู้ใช้ API นั้นจะมีความเข้าใจอย่างลึกซึ้ง ดังนั้นจึงเป็นการสมเหตุสมผลที่จะนำไปใช้กับAPI สาธารณะหากไม่เป็นไรสำหรับผู้ที่จะใช้ ฉันเดาว่าการมี UML บางประเภทจะทำให้ความแตกต่างของทรัพย์สินแต่ละอย่างชัดเจนช่วยได้ แต่ฉันต้องการที่จะได้ยินจากคนที่มีประสบการณ์มากขึ้น : P
cregox

คุณสามารถใช้CGbR Clone Generatorและรับผลลัพธ์ที่คล้ายกันโดยไม่ต้องเขียนโค้ดด้วยตนเอง
Toxantron

การใช้ภาษาระดับกลางมีประโยชน์
Michael Freidgeim

ไม่มีรอบชิงชนะเลิศใน C #
Konrad

84

ฉันชอบตัวสร้างการคัดลอกไปยังโคลน เจตนาชัดเจนขึ้น


5
.Net ไม่มีตัวสร้างสำเนา
Pop Catalin

48
แน่ใจว่ามันทำ: ใหม่ MyObject (objToCloneFrom) เพียงแค่ประกาศ ctor ซึ่งใช้วัตถุที่จะโคลนเป็นพารามิเตอร์
นิค

30
มันไม่เหมือนกัน คุณต้องเพิ่มมันในทุกชั้นเรียนด้วยตัวเองและคุณไม่รู้ด้วยซ้ำว่าคุณกำลังทำสำเนาลึก ๆ
Dave Van den Eynde

15
+1 สำหรับสำเนา ctor คุณต้องเขียนฟังก์ชัน clone () ด้วยตนเองสำหรับแต่ละประเภทของวัตถุด้วยและขอให้โชคดีเมื่อระดับชั้นของคุณลึกลงไปเล็กน้อย
Andrew Grant

3
ด้วยตัวสร้างการคัดลอกคุณสูญเสียลำดับชั้นแม้ว่า agiledeveloper.com/articles/cloning072002.htm
จะ

41

วิธีการขยายอย่างง่ายเพื่อคัดลอกคุณสมบัติสาธารณะทั้งหมด การทำงานสำหรับวัตถุใด ๆ และไม่[Serializable]จำเป็นต้องมีระดับที่จะเป็น สามารถขยายได้สำหรับระดับการเข้าถึงอื่น ๆ

public static void CopyTo( this object S, object T )
{
    foreach( var pS in S.GetType().GetProperties() )
    {
        foreach( var pT in T.GetType().GetProperties() )
        {
            if( pT.Name != pS.Name ) continue;
            ( pT.GetSetMethod() ).Invoke( T, new object[] 
            { pS.GetGetMethod().Invoke( S, null ) } );
        }
    };
}

15
โชคไม่ดีที่นี่มีข้อบกพร่อง มันเทียบเท่ากับการเรียก objectOne.MyProperty = objectTwo.MyProperty (เช่นมันจะคัดลอกการอ้างอิงข้าม) มันจะไม่ลอกแบบค่าของคุณสมบัติ
Alex Norcliffe

1
ถึง Alex Norcliffe: ผู้เขียนคำถามถามเกี่ยวกับ "การคัดลอกแต่ละคุณสมบัติ" แทนที่จะทำการโคลน ในกรณีส่วนใหญ่ไม่จำเป็นต้องทำซ้ำคุณสมบัติที่แน่นอน
Konstantin Salavatov

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

33

ฉันเพิ่งสร้างโครงการCloneExtensionsห้องสมุด มันทำการโคลนอย่างรวดเร็วและลึกโดยใช้การมอบหมายอย่างง่ายที่สร้างขึ้นโดยการรวบรวมรหัสรันไทม์ของ Expression Tree

วิธีใช้งาน

แทนการเขียนของคุณเองCloneหรือCopyวิธีการด้วยเสียงของการกำหนดระหว่างเขตข้อมูลและคุณสมบัติทำให้โปรแกรมทำด้วยตัวคุณเองโดยใช้ Expression Tree GetClone<T>()วิธีการทำเครื่องหมายเป็นวิธีการขยายช่วยให้คุณสามารถเรียกมันได้บนอินสแตนซ์ของคุณ:

var newInstance = source.GetClone();

คุณสามารถเลือกสิ่งที่ควรคัดลอกจากsourceไปยังnewInstanceใช้CloningFlagsenum:

var newInstance 
    = source.GetClone(CloningFlags.Properties | CloningFlags.CollectionItems);

สามารถโคลนอะไรได้บ้าง

  • ดั้งเดิม (int, uint, byte, double, char, ฯลฯ ), ประเภทที่ไม่เปลี่ยนรูปที่รู้จัก (DateTime, TimeSpan, String) และผู้ได้รับมอบหมาย (รวมถึง Action, Func, ฯลฯ )
  • nullable
  • T [] อาร์เรย์
  • คลาสและโครงสร้างแบบกำหนดเองรวมถึงคลาสและโครงสร้างทั่วไป

สมาชิกคลาส / struct ต่อไปนี้ถูกโคลนภายใน:

  • ค่าของสาธารณะไม่ใช่ฟิลด์แบบอ่านอย่างเดียว
  • ค่าของคุณสมบัติสาธารณะที่มีทั้งรับและตั้งค่า accessors
  • รายการรวบรวมสำหรับประเภทการใช้ ICollection

มันเร็วแค่ไหน?

การแก้ปัญหาคือแล้วเร็วขึ้นสะท้อนเนื่องจากข้อมูลสมาชิกจะต้องมีการรวมตัวกันเพียงครั้งเดียวก่อนที่จะถูกนำมาใช้เป็นครั้งแรกสำหรับประเภทที่กำหนดGetClone<T>T

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

และอื่น ๆ...

อ่านเพิ่มเติมเกี่ยวกับการแสดงออกที่สร้างขึ้นบนเอกสาร

ตัวอย่างรายการดีบักนิพจน์สำหรับList<int>:

.Lambda #Lambda1<System.Func`4[System.Collections.Generic.List`1[System.Int32],CloneExtensions.CloningFlags,System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]],System.Collections.Generic.List`1[System.Int32]]>(
    System.Collections.Generic.List`1[System.Int32] $source,
    CloneExtensions.CloningFlags $flags,
    System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]] $initializers) {
    .Block(System.Collections.Generic.List`1[System.Int32] $target) {
        .If ($source == null) {
            .Return #Label1 { null }
        } .Else {
            .Default(System.Void)
        };
        .If (
            .Call $initializers.ContainsKey(.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32]))
        ) {
            $target = (System.Collections.Generic.List`1[System.Int32]).Call ($initializers.Item[.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32])]
            ).Invoke((System.Object)$source)
        } .Else {
            $target = .New System.Collections.Generic.List`1[System.Int32]()
        };
        .If (
            ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)
        ) {
            .Default(System.Void)
        } .Else {
            .Default(System.Void)
        };
        .If (
            ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)
        ) {
            .Block() {
                $target.Capacity = .Call CloneExtensions.CloneFactory.GetClone(
                    $source.Capacity,
                    $flags,
                    $initializers)
            }
        } .Else {
            .Default(System.Void)
        };
        .If (
            ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)
        ) {
            .Block(
                System.Collections.Generic.IEnumerator`1[System.Int32] $var1,
                System.Collections.Generic.ICollection`1[System.Int32] $var2) {
                $var1 = (System.Collections.Generic.IEnumerator`1[System.Int32]).Call $source.GetEnumerator();
                $var2 = (System.Collections.Generic.ICollection`1[System.Int32])$target;
                .Loop  {
                    .If (.Call $var1.MoveNext() != False) {
                        .Call $var2.Add(.Call CloneExtensions.CloneFactory.GetClone(
                                $var1.Current,
                                $flags,


                         $initializers))
                } .Else {
                    .Break #Label2 { }
                }
            }
            .LabelTarget #Label2:
        }
    } .Else {
        .Default(System.Void)
    };
    .Label
        $target
    .LabelTarget #Label1:
}

}

อะไรมีความหมายเหมือนกันดังต่อไปนี้รหัส c #:

(source, flags, initializers) =>
{
    if(source == null)
        return null;

    if(initializers.ContainsKey(typeof(List<int>))
        target = (List<int>)initializers[typeof(List<int>)].Invoke((object)source);
    else
        target = new List<int>();

    if((flags & CloningFlags.Properties) == CloningFlags.Properties)
    {
        target.Capacity = target.Capacity.GetClone(flags, initializers);
    }

    if((flags & CloningFlags.CollectionItems) == CloningFlags.CollectionItems)
    {
        var targetCollection = (ICollection<int>)target;
        foreach(var item in (ICollection<int>)source)
        {
            targetCollection.Add(item.Clone(flags, initializers));
        }
    }

    return target;
}

ไม่เหมือนที่คุณจะเขียนCloneวิธีการของคุณเองList<int>ใช่ไหม


2
โอกาสในการได้รับ NuGet นี้มีอะไรบ้าง ดูเหมือนว่าทางออกที่ดีที่สุด เปรียบเทียบกับNCloneอย่างไร
บดขยี้

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

ไม่เลยคุณผิดเกี่ยวกับการสะท้อนคุณควรแคชอย่างถูกต้อง ตรวจสอบคำตอบของฉันด้านล่างstackoverflow.com/a/34368738/4711853
Roma Borodov

31

ฉันมีปัญหาในการใช้ ICloneable ใน Silverlight แต่ฉันชอบความคิดของการทำให้เป็นซีลลาไรซ์ฉันสามารถทำให้เป็น XML ได้ดังนั้นฉันจึงทำสิ่งนี้:

static public class SerializeHelper
{
    //Michael White, Holly Springs Consulting, 2009
    //michael@hollyspringsconsulting.com
    public static T DeserializeXML<T>(string xmlData) where T:new()
    {
        if (string.IsNullOrEmpty(xmlData))
            return default(T);

        TextReader tr = new StringReader(xmlData);
        T DocItms = new T();
        XmlSerializer xms = new XmlSerializer(DocItms.GetType());
        DocItms = (T)xms.Deserialize(tr);

        return DocItms == null ? default(T) : DocItms;
    }

    public static string SeralizeObjectToXML<T>(T xmlObject)
    {
        StringBuilder sbTR = new StringBuilder();
        XmlSerializer xmsTR = new XmlSerializer(xmlObject.GetType());
        XmlWriterSettings xwsTR = new XmlWriterSettings();

        XmlWriter xmwTR = XmlWriter.Create(sbTR, xwsTR);
        xmsTR.Serialize(xmwTR,xmlObject);

        return sbTR.ToString();
    }

    public static T CloneObject<T>(T objClone) where T:new()
    {
        string GetString = SerializeHelper.SeralizeObjectToXML<T>(objClone);
        return SerializeHelper.DeserializeXML<T>(GetString);
    }
}

31

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

MyObject oldObj; // The existing object to clone

MyObject newObj = new MyObject();
newObj.InjectFrom(oldObj); // Using ValueInjecter syntax

ใช้วิธีนี้คุณไม่จำเป็นต้องนำไปใช้ISerializableหรือICloneableบนวัตถุของคุณ นี่เป็นเรื่องปกติกับรูปแบบ MVC / MVVM ดังนั้นเครื่องมือง่ายๆเช่นนี้จึงถูกสร้างขึ้น

ดูตัวอย่าง ValueInjecter โคลนลึกบน GitHub


25

ที่ดีที่สุดคือการใช้วิธีการขยายเช่น

public static T DeepClone<T>(this T originalObject)
{ /* the cloning code */ }

และใช้งานได้ทุกที่ในโซลูชันโดย

var copy = anyObject.DeepClone();

เราสามารถใช้งานได้สามแบบต่อไปนี้:

  1. โดยการทำให้เป็นอันดับ (รหัสสั้นที่สุด)
  2. โดย Reflection -เร็วกว่า 5x
  3. โดย Expression Trees -เร็วกว่า 20x

วิธีการเชื่อมโยงทั้งหมดทำงานได้ดีและได้รับการทดสอบอย่างล้ำลึก


การโคลนรหัสโดยใช้ต้นไม้ Expression ที่คุณโพสต์codeproject.com/Articles/1111658/ … , ล้มเหลวด้วย. NET Framework รุ่นใหม่ที่มีข้อยกเว้นด้านความปลอดภัย, การดำเนินการอาจทำให้เสถียรรันไทม์ , โดยทั่วไปแล้วเป็นข้อยกเว้นเนื่องจากทรีนิพจน์ที่ผิดรูปแบบ, ซึ่งใช้ในการสร้าง Func ที่รันไทม์โปรดตรวจสอบว่าคุณมีวิธีแก้ปัญหาหรือไม่ในความเป็นจริงฉันได้เห็นปัญหาเฉพาะกับวัตถุที่ซับซ้อนที่มีลำดับชั้นลึกซึ่งง่าย ๆ ที่จะคัดลอกได้ง่าย
Mrinal Kamboj

1
ExpressionTree ใช้งานได้ดีมาก มันยังทำงานได้กับการอ้างอิงแบบวงกลมและสมาชิกส่วนตัว ไม่มีคุณสมบัติที่จำเป็น คำตอบที่ดีที่สุดที่ฉันพบ
N73k

คำตอบที่ดีที่สุดทำงานได้ดีมากคุณประหยัดวันของฉันได้
Adel Mourad

23

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

สำหรับคำอธิบายรายละเอียดเพิ่มเติมเกี่ยวกับการโคลนใช้ ICloneable ตรวจสอบบทความนี้

ยาวคำตอบคือ "มันขึ้นอยู่กับ" ตามที่กล่าวไว้โดยคนอื่น ๆ ICloneable ไม่ได้รับการสนับสนุนโดย generics ต้องมีการพิจารณาเป็นพิเศษสำหรับการอ้างอิงระดับวงกลมและบางคนก็มองว่าเป็น"ความผิดพลาด"ใน. NET Framework วิธีการทำให้เป็นอนุกรมนั้นขึ้นอยู่กับวัตถุของคุณที่จะทำให้เป็นอนุกรมซึ่งอาจไม่เป็นและคุณอาจไม่สามารถควบคุมได้ ยังมีการถกเถียงกันอย่างมากในชุมชนซึ่งเป็นการปฏิบัติที่ "ดีที่สุด" ในความเป็นจริงไม่มีวิธีแก้ปัญหาใดที่มีขนาดเดียวที่เหมาะกับการปฏิบัติที่ดีที่สุดสำหรับทุกสถานการณ์เช่น ICloneable แต่เดิมถูกตีความว่าเป็น

ดูบทความมุมนักพัฒนานี้สำหรับตัวเลือกเพิ่มเติมอีกสองสาม (เครดิตถึงเอียน)


1
ICloneable ไม่มีอินเทอร์เฟซทั่วไปดังนั้นจึงไม่แนะนำให้ใช้อินเทอร์เฟซนั้น
Karg

โซลูชันของคุณจะทำงานจนกว่าจะต้องการจัดการการอ้างอิงแบบวงกลมจากนั้นสิ่งต่าง ๆ เริ่มมีความซับซ้อนยิ่งขึ้นลองใช้การโคลนแบบลึกโดยใช้การจัดลำดับแบบลึก
Pop Catalin

น่าเสียดายที่ไม่ใช่วัตถุทั้งหมดที่สามารถทำให้เป็นอนุกรมได้ดังนั้นคุณจึงไม่สามารถใช้วิธีการนั้นได้เช่นกัน ลิงก์ของ Ian เป็นคำตอบที่ครอบคลุมที่สุด
Zach Burlingame

19
  1. โดยทั่วไปคุณต้องใช้ส่วนต่อประสาน ICloneable จากนั้นจึงทำการคัดลอกโครงสร้างวัตถุ
  2. ถ้ามันเป็นสำเนาลึกของสมาชิกทุกคนคุณต้องทำประกัน (ไม่เกี่ยวข้องกับวิธีแก้ปัญหาที่คุณเลือก) ว่าเด็กทุกคนมีความสามารถเช่นกัน
  3. บางครั้งคุณจำเป็นต้องตระหนักถึงข้อ จำกัด บางอย่างในระหว่างกระบวนการนี้ตัวอย่างเช่นถ้าคุณคัดลอกวัตถุ ORM กรอบงานส่วนใหญ่อนุญาตให้มีวัตถุเพียงชิ้นเดียวที่แนบมากับเซสชันและคุณไม่ต้องทำโคลนของวัตถุนี้หรือถ้าเป็นไปได้ เกี่ยวกับการแนบเซสชันของวัตถุเหล่านี้

ไชโย


4
ICloneable ไม่มีอินเทอร์เฟซทั่วไปดังนั้นจึงไม่แนะนำให้ใช้อินเทอร์เฟซนั้น
Karg

คำตอบที่ง่ายและกระชับนั้นดีที่สุด
DavidGuaita

17

แก้ไข: โครงการถูกยกเลิก

หากคุณต้องการโคลนจริงชนิดที่ไม่รู้จักคุณสามารถดูที่ fastclone

นั่นคือการโคลนแบบนิพจน์ที่ทำงานเร็วกว่าการทำซีเรียลไลซ์เซชั่นไบนารีประมาณ 10 เท่าและยังคงความสมบูรณ์ของกราฟวัตถุ

นั่นหมายความว่าถ้าคุณอ้างถึงวัตถุเดียวกันหลายครั้งในลำดับชั้นของคุณโคลนจะมีการอ้างอิงอินสแตนซ์เดียว

ไม่จำเป็นต้องมีอินเทอร์เฟซแอตทริบิวต์หรือการปรับเปลี่ยนอื่น ๆ กับวัตถุที่ถูกโคลน


อันนี้ดูเหมือนว่าจะมีประโยชน์สวย
LuckyLikey

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

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

ฉันลองแล้วไม่ได้ผลเลยสำหรับฉัน โยนข้อยกเว้น MemberAccess
Michael Brown

มันไม่ทำงานกับ. NET เวอร์ชั่นใหม่กว่าและถูกยกเลิก
Michael Sander

14

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

MyType source = new MyType();
Mapper.CreateMap<MyType, MyType>();
MyType target = Mapper.Map<MyType, MyType>(source);

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

public static T Copy<T>(this T source)
{
    T copy = default(T);
    Mapper.CreateMap<T, T>();
    copy = Mapper.Map<T, T>(source);
    return copy;
}

วิธีการขยายสามารถใช้ดังต่อไปนี้:

MyType copy = source.Copy();

ระวังด้วยอันนี้มันทำงานได้ไม่ดีจริงๆ ฉันสิ้นสุดการเปลี่ยนเป็นคำตอบ johnc ซึ่งสั้นที่สุดเท่านี้และมีประสิทธิภาพดีกว่ามาก
Agorilla

1
นี่เป็นเพียงสำเนาตื้น ๆ เท่านั้น
N73k

11

ฉันมาพร้อมกับสิ่งนี้เพื่อเอาชนะข้อบกพร่องของ. NET ที่ต้องทำการคัดลอกรายการลึก ๆ <T>

ฉันใช้สิ่งนี้:

static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
{
    foreach (SpotPlacement sp in spotPlacements)
    {
        yield return (SpotPlacement)sp.Clone();
    }
}

และที่อื่น:

public object Clone()
{
    OrderItem newOrderItem = new OrderItem();
    ...
    newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
    ...
    return newOrderItem;
}

ฉันพยายามหา oneliner ที่ทำสิ่งนี้ แต่มันเป็นไปไม่ได้เนื่องจากผลผลิตไม่ทำงานในบล็อกเมธอดที่ไม่ระบุชื่อ

ยังดีกว่าใช้รายการทั่วไป <T> cloner:

class Utility<T> where T : ICloneable
{
    static public IEnumerable<T> CloneList(List<T> tl)
    {
        foreach (T t in tl)
        {
            yield return (T)t.Clone();
        }
    }
}

10

ถาม: ทำไมฉันถึงเลือกคำตอบนี้

  • เลือกคำตอบนี้หากคุณต้องการให้ NET ความเร็วสูงสุดมีความสามารถ
  • ไม่ต้องสนใจคำตอบนี้หากคุณต้องการวิธีการโคลนนิ่งที่ง่ายและสะดวกจริงๆ

ในคำอื่น ๆไปกับคำตอบอื่นจนกว่าคุณจะมีคอขวดประสิทธิภาพการทำงานที่ตอบสนองความต้องการการแก้ไขและคุณสามารถพิสูจน์ได้ด้วย Profiler

เร็วกว่าวิธีอื่น 10 เท่า

วิธีการโคลนแบบลึกต่อไปนี้คือ:

  • เร็วกว่าสิ่งใด ๆ ที่เกี่ยวข้องกับซีเรียลไลซ์เซชั่น /
  • ค่อนข้างใกล้เคียงกับความเร็วสูงสุดตามทฤษฎี. NET ที่มีความสามารถ

และวิธีการ ...

สำหรับความเร็วสูงสุดคุณสามารถใช้Nested MemberwiseClone เพื่อทำสำเนาลึกที่จะทำสำเนาลึก มันเกือบจะเร็วเหมือนการคัดลอก struct ตามตัวอักษรและเร็วกว่า (a) reflection หรือ (b) การทำให้เป็นอนุกรม (ดังที่อธิบายไว้ในคำตอบอื่น ๆ ในหน้านี้)

โปรดทราบว่าถ้าคุณใช้Nested MemberwiseClone สำหรับสำเนาลึกคุณต้องใช้ ShallowCopy สำหรับแต่ละระดับที่ซ้อนกันในคลาสและ DeepCopy ซึ่งเรียกเมธอด ShallowCopy ทั้งหมดเพื่อสร้างโคลนที่สมบูรณ์ ง่ายมากเพียงไม่กี่บรรทัดดูรหัสสาธิตด้านล่าง

นี่คือผลลัพธ์ของโค้ดที่แสดงความแตกต่างของประสิทธิภาพสัมพัทธ์สำหรับ 100,000 โคลนนิ่ง:

  • 1.08 วินาทีสำหรับ Nested MemberwiseClone บนโครงสร้างที่ซ้อนกัน
  • 4.77 วินาทีสำหรับ Nested MemberwiseClone ในคลาสที่ซ้อนกัน
  • 39.93 วินาทีสำหรับซีเรียลไลเซชั่น / ดีซีเรียลไลเซชัน

การใช้ Nested MemberwiseClone ในคลาสเกือบจะเร็วเท่ากับการคัดลอก struct และการคัดลอก struct นั้นค่อนข้างใกล้เคียงกับความเร็วสูงสุดตามทฤษฎีที่มีความสามารถ

Demo 1 of shallow and deep copy, using classes and MemberwiseClone:
  Create Bob
    Bob.Age=30, Bob.Purchase.Description=Lamborghini
  Clone Bob >> BobsSon
  Adjust BobsSon details
    BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
    Bob.Age=30, Bob.Purchase.Description=Lamborghini
  Elapsed time: 00:00:04.7795670,30000000

Demo 2 of shallow and deep copy, using structs and value copying:
  Create Bob
    Bob.Age=30, Bob.Purchase.Description=Lamborghini
  Clone Bob >> BobsSon
  Adjust BobsSon details:
    BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
    Bob.Age=30, Bob.Purchase.Description=Lamborghini
  Elapsed time: 00:00:01.0875454,30000000

Demo 3 of deep copy, using class and serialize/deserialize:
  Elapsed time: 00:00:39.9339425,30000000

เพื่อให้เข้าใจวิธีการทำสำเนาอย่างลึกโดยใช้ MemberwiseCopy ต่อไปนี้เป็นโครงการตัวอย่างที่ใช้ในการสร้างเวลาด้านบน:

// Nested MemberwiseClone example. 
// Added to demo how to deep copy a reference class.
[Serializable] // Not required if using MemberwiseClone, only used for speed comparison using serialization.
public class Person
{
    public Person(int age, string description)
    {
        this.Age = age;
        this.Purchase.Description = description;
    }
    [Serializable] // Not required if using MemberwiseClone
    public class PurchaseType
    {
        public string Description;
        public PurchaseType ShallowCopy()
        {
            return (PurchaseType)this.MemberwiseClone();
        }
    }
    public PurchaseType Purchase = new PurchaseType();
    public int Age;
    // Add this if using nested MemberwiseClone.
    // This is a class, which is a reference type, so cloning is more difficult.
    public Person ShallowCopy()
    {
        return (Person)this.MemberwiseClone();
    }
    // Add this if using nested MemberwiseClone.
    // This is a class, which is a reference type, so cloning is more difficult.
    public Person DeepCopy()
    {
            // Clone the root ...
        Person other = (Person) this.MemberwiseClone();
            // ... then clone the nested class.
        other.Purchase = this.Purchase.ShallowCopy();
        return other;
    }
}
// Added to demo how to copy a value struct (this is easy - a deep copy happens by default)
public struct PersonStruct
{
    public PersonStruct(int age, string description)
    {
        this.Age = age;
        this.Purchase.Description = description;
    }
    public struct PurchaseType
    {
        public string Description;
    }
    public PurchaseType Purchase;
    public int Age;
    // This is a struct, which is a value type, so everything is a clone by default.
    public PersonStruct ShallowCopy()
    {
        return (PersonStruct)this;
    }
    // This is a struct, which is a value type, so everything is a clone by default.
    public PersonStruct DeepCopy()
    {
        return (PersonStruct)this;
    }
}
// Added only for a speed comparison.
public class MyDeepCopy
{
    public static T DeepCopy<T>(T obj)
    {
        object result = null;
        using (var ms = new MemoryStream())
        {
            var formatter = new BinaryFormatter();
            formatter.Serialize(ms, obj);
            ms.Position = 0;
            result = (T)formatter.Deserialize(ms);
            ms.Close();
        }
        return (T)result;
    }
}

จากนั้นเรียกใช้การสาธิตจาก main:

void MyMain(string[] args)
{
    {
        Console.Write("Demo 1 of shallow and deep copy, using classes and MemberwiseCopy:\n");
        var Bob = new Person(30, "Lamborghini");
        Console.Write("  Create Bob\n");
        Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
        Console.Write("  Clone Bob >> BobsSon\n");
        var BobsSon = Bob.DeepCopy();
        Console.Write("  Adjust BobsSon details\n");
        BobsSon.Age = 2;
        BobsSon.Purchase.Description = "Toy car";
        Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
        Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
        Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
        Debug.Assert(Bob.Age == 30);
        Debug.Assert(Bob.Purchase.Description == "Lamborghini");
        var sw = new Stopwatch();
        sw.Start();
        int total = 0;
        for (int i = 0; i < 100000; i++)
        {
            var n = Bob.DeepCopy();
            total += n.Age;
        }
        Console.Write("  Elapsed time: {0},{1}\n\n", sw.Elapsed, total);
    }
    {               
        Console.Write("Demo 2 of shallow and deep copy, using structs:\n");
        var Bob = new PersonStruct(30, "Lamborghini");
        Console.Write("  Create Bob\n");
        Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
        Console.Write("  Clone Bob >> BobsSon\n");
        var BobsSon = Bob.DeepCopy();
        Console.Write("  Adjust BobsSon details:\n");
        BobsSon.Age = 2;
        BobsSon.Purchase.Description = "Toy car";
        Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
        Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
        Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);                
        Debug.Assert(Bob.Age == 30);
        Debug.Assert(Bob.Purchase.Description == "Lamborghini");
        var sw = new Stopwatch();
        sw.Start();
        int total = 0;
        for (int i = 0; i < 100000; i++)
        {
            var n = Bob.DeepCopy();
            total += n.Age;
        }
        Console.Write("  Elapsed time: {0},{1}\n\n", sw.Elapsed, total);
    }
    {
        Console.Write("Demo 3 of deep copy, using class and serialize/deserialize:\n");
        int total = 0;
        var sw = new Stopwatch();
        sw.Start();
        var Bob = new Person(30, "Lamborghini");
        for (int i = 0; i < 100000; i++)
        {
            var BobsSon = MyDeepCopy.DeepCopy<Person>(Bob);
            total += BobsSon.Age;
        }
        Console.Write("  Elapsed time: {0},{1}\n", sw.Elapsed, total);
    }
    Console.ReadKey();
}

อีกครั้งโปรดทราบว่าถ้าคุณใช้Nested MemberwiseClone สำหรับสำเนาลึกคุณต้องใช้ ShallowCopy สำหรับแต่ละระดับที่ซ้อนกันในชั้นเรียนด้วยตนเองและ DeepCopy ซึ่งเรียกเมธอด ShallowCopy ทั้งหมดเพื่อสร้างโคลนที่สมบูรณ์ ง่ายมาก: มีเพียงไม่กี่บรรทัดเท่านั้นดูรหัสสาธิตด้านบน

ประเภทค่าเทียบกับประเภทการอ้างอิง

โปรดทราบว่าเมื่อมันมาถึงการโคลนวัตถุมีความแตกต่างใหญ่ระหว่าง " struct " และ " คลาส ":

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

ดูความแตกต่างระหว่างค่าชนิดและประเภทการอ้างอิง

ตรวจสอบผลรวมเพื่อช่วยในการดีบัก

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

มีประโยชน์จริง ๆ สำหรับ decoupling หลายเธรดจากเธรดอื่น ๆ

กรณีใช้งานที่ยอดเยี่ยมอย่างหนึ่งสำหรับรหัสนี้คือการป้อนโคลนของคลาสที่ซ้อนกันหรือโครงสร้างลงในคิวเพื่อใช้รูปแบบผู้ผลิต / ผู้บริโภค

  • เราสามารถมีหนึ่ง (หรือมากกว่า) ConcurrentQueueหัวข้อการปรับเปลี่ยนระดับที่พวกเขาเป็นเจ้าของแล้วผลักดันสำเนาสมบูรณ์ของชั้นนี้เป็น
  • จากนั้นเรามีเธรดหนึ่ง (หรือมากกว่า) ดึงสำเนาของคลาสเหล่านี้ออกมาและจัดการกับพวกเขา

วิธีนี้ใช้งานได้ดีมากในทางปฏิบัติและช่วยให้เราแยกเธรดจำนวนมาก (ผู้ผลิต) จากหนึ่งเธรดขึ้นไป (ผู้บริโภค)

และวิธีการนี้ก็รวดเร็วเช่นกัน: ถ้าเราใช้โครงสร้างซ้อนกันมันเร็วกว่าคลาสอนุกรม / deserializing 35x และทำให้เราสามารถใช้ประโยชน์จากเธรดทั้งหมดที่มีในเครื่อง

ปรับปรุง

เห็นได้ชัดว่า ExpressMapper นั้นเร็วหากไม่เร็วกว่าการเขียนด้วยมือเช่นด้านบน ฉันอาจต้องดูว่าพวกเขาเปรียบเทียบกับ profiler อย่างไร


หากคุณคัดลอกโครงสร้างที่คุณได้รับสำเนาตื้นคุณยังอาจต้องใช้งานเฉพาะสำหรับสำเนาลึก
Lasse V. Karlsen

@ Lasse V. Karlsen ใช่คุณถูกต้องแน่นอนฉันได้อัปเดตคำตอบเพื่อให้ชัดเจนยิ่งขึ้น วิธีนี้สามารถใช้ในการทำสำเนา structs และคลาสที่ลึก คุณสามารถเรียกใช้โค้ดตัวอย่างตัวอย่างที่รวมไว้เพื่อแสดงวิธีการทำมันมีตัวอย่างของการโคลนแบบลึกโครงสร้างที่ซ้อนกันและอีกตัวอย่างของการโคลนแบบลึกคลาสที่ซ้อนกัน
Contango

9

โดยทั่วไปคุณใช้งานอินเทอร์เฟซสำหรับ ICloneable และนำ Clone ไปใช้ด้วยตนเอง วัตถุ C # มีวิธี MemberwiseClone ในตัวที่ทำสำเนาแบบตื้นที่สามารถช่วยคุณได้สำหรับการตรวจสอบเบื้องต้นทั้งหมด

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


ICloneable ไม่มีอินเทอร์เฟซทั่วไปดังนั้นจึงไม่แนะนำให้ใช้อินเทอร์เฟซนั้น
Karg

8

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


8

นี่คือการดำเนินการคัดลอกลึก:

public static object CloneObject(object opSource)
{
    //grab the type and create a new instance of that type
    Type opSourceType = opSource.GetType();
    object opTarget = CreateInstanceOfType(opSourceType);

    //grab the properties
    PropertyInfo[] opPropertyInfo = opSourceType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

    //iterate over the properties and if it has a 'set' method assign it from the source TO the target
    foreach (PropertyInfo item in opPropertyInfo)
    {
        if (item.CanWrite)
        {
            //value types can simply be 'set'
            if (item.PropertyType.IsValueType || item.PropertyType.IsEnum || item.PropertyType.Equals(typeof(System.String)))
            {
                item.SetValue(opTarget, item.GetValue(opSource, null), null);
            }
            //object/complex types need to recursively call this method until the end of the tree is reached
            else
            {
                object opPropertyValue = item.GetValue(opSource, null);
                if (opPropertyValue == null)
                {
                    item.SetValue(opTarget, null, null);
                }
                else
                {
                    item.SetValue(opTarget, CloneObject(opPropertyValue), null);
                }
            }
        }
    }
    //return the new item
    return opTarget;
}

2
ดูเหมือนว่าการเลียนแบบ Memberwise เนื่องจากไม่ทราบคุณสมบัติประเภทการอ้างอิง
sll

1
หากคุณต้องการประสิทธิภาพที่รวดเร็วอย่างไม่น่าเชื่ออย่าไปใช้งานในส่วนนี้: มันใช้การสะท้อนดังนั้นมันจะไม่เร็วขนาดนั้น ในทางกลับกัน "การยกเลิกการเลือกกำหนดล่วงหน้าเป็นความชั่วร้ายทั้งหมด" ดังนั้นให้ละเว้นด้านประสิทธิภาพจนกว่าคุณจะเรียกใช้ตัวสร้างโปรไฟล์
Contango

1
CreateInstanceOfType ไม่ได้ถูกกำหนดไว้?
MonsterMMORPG

มันล้มเหลวใน interger: "วิธีการไม่คงที่ต้องมีเป้าหมาย"
Mr.B

8

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

https://github.com/kalisohn/CloneBehave

มีให้ในรูปแบบแพ็คเกจ nuget: https://www.nuget.org/packages/Clone.Behave/1.0.0

ตัวอย่างเช่น: รหัสต่อไปนี้จะอยู่ DeepClone แต่ทำสำเนาตื้นของฟิลด์ _currentJob

public class Person 
{
  [DeepClone(DeepCloneBehavior.Shallow)]
  private Job _currentJob;      

  public string Name { get; set; }

  public Job CurrentJob 
  { 
    get{ return _currentJob; }
    set{ _currentJob = value; }
  }

  public Person Manager { get; set; }
}

public class Address 
{      
  public Person PersonLivingHere { get; set; }
}

Address adr = new Address();
adr.PersonLivingHere = new Person("John");
adr.PersonLivingHere.BestFriend = new Person("James");
adr.PersonLivingHere.CurrentJob = new Job("Programmer");

Address adrClone = adr.Clone();

//RESULT
adr.PersonLivingHere == adrClone.PersonLivingHere //false
adr.PersonLivingHere.Manager == adrClone.PersonLivingHere.Manager //false
adr.PersonLivingHere.CurrentJob == adrClone.PersonLivingHere.CurrentJob //true
adr.PersonLivingHere.CurrentJob.AnyProperty == adrClone.PersonLivingHere.CurrentJob.AnyProperty //true

7

ตัวสร้างรหัส

เราได้เห็นจำนวนมากคิดจากอนุกรมมากกว่าการดำเนินการด้วยตนเองเพื่อสะท้อนและฉันต้องการที่จะนำเสนอวิธีการที่แตกต่างกันโดยสิ้นเชิงโดยใช้เครื่องกำเนิดไฟฟ้ารหัส CGbR วิธีสร้างโคลนคือหน่วยความจำและซีพียูที่มีประสิทธิภาพและเร็วกว่า 300x เช่นเดียวกับ DataContractSerializer มาตรฐาน

สิ่งที่คุณต้องมีคือคำจำกัดความของคลาสบางส่วนด้วยICloneableและตัวกำเนิดทำหน้าที่ส่วนที่เหลือ:

public partial class Root : ICloneable
{
    public Root(int number)
    {
        _number = number;
    }
    private int _number;

    public Partial[] Partials { get; set; }

    public IList<ulong> Numbers { get; set; }

    public object Clone()
    {
        return Clone(true);
    }

    private Root()
    {
    }
} 

public partial class Root
{
    public Root Clone(bool deep)
    {
        var copy = new Root();
        // All value types can be simply copied
        copy._number = _number; 
        if (deep)
        {
            // In a deep clone the references are cloned 
            var tempPartials = new Partial[Partials.Length];
            for (var i = 0; i < Partials.Length; i++)
            {
                var value = Partials[i];
                value = value.Clone(true);
                tempPartials[i] = value;
            }
            copy.Partials = tempPartials;
            var tempNumbers = new List<ulong>(Numbers.Count);
            for (var i = 0; i < Numbers.Count; i++)
            {
                var value = Numbers[i];
                tempNumbers.Add(value);
            }
            copy.Numbers = tempNumbers;
        }
        else
        {
            // In a shallow clone only references are copied
            copy.Partials = Partials; 
            copy.Numbers = Numbers; 
        }
        return copy;
    }
}

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


6

ฉันชอบ Copyconstructors เช่นนั้น:

    public AnyObject(AnyObject anyObject)
    {
        foreach (var property in typeof(AnyObject).GetProperties())
        {
            property.SetValue(this, property.GetValue(anyObject));
        }
        foreach (var field in typeof(AnyObject).GetFields())
        {
            field.SetValue(this, field.GetValue(anyObject));
        }
    }

หากคุณมีสิ่งที่จะคัดลอกเพิ่มพวกเขา


6

วิธีนี้แก้ปัญหาให้ฉัน:

private static MyObj DeepCopy(MyObj source)
        {

            var DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };

            return JsonConvert.DeserializeObject<MyObj >(JsonConvert.SerializeObject(source), DeserializeSettings);

        }

ใช้มันแบบนี้: MyObj a = DeepCopy(b);


6

นี่เป็นวิธีแก้ปัญหาที่ง่ายและรวดเร็วที่ใช้งานได้สำหรับฉันโดยไม่ต้องต่อเนื่องกับ Serialization / Deserialization

public class MyClass
{
    public virtual MyClass DeepClone()
    {
        var returnObj = (MyClass)MemberwiseClone();
        var type = returnObj.GetType();
        var fieldInfoArray = type.GetRuntimeFields().ToArray();

        foreach (var fieldInfo in fieldInfoArray)
        {
            object sourceFieldValue = fieldInfo.GetValue(this);
            if (!(sourceFieldValue is MyClass))
            {
                continue;
            }

            var sourceObj = (MyClass)sourceFieldValue;
            var clonedObj = sourceObj.DeepClone();
            fieldInfo.SetValue(returnObj, clonedObj);
        }
        return returnObj;
    }
}

แก้ไข : ต้องการ

    using System.Linq;
    using System.Reflection;

นั่นคือวิธีที่ฉันใช้

public MyClass Clone(MyClass theObjectIneededToClone)
{
    MyClass clonedObj = theObjectIneededToClone.DeepClone();
}

5

ทำตามขั้นตอนเหล่านี้:

  • กำหนดISelf<T>ด้วยการอ่านอย่างเดียวSelfทรัพย์สินที่ผลตอบแทนTและICloneable<out T>ที่มาจากและรวมถึงวิธีการISelf<T>T Clone()
  • จากนั้นกำหนดCloneBaseประเภทที่ใช้การprotected virtual generic VirtualCloneส่งMemberwiseCloneไปยังประเภทที่ส่งผ่าน
  • แต่ละประเภทที่ได้รับควรนำไปใช้VirtualCloneโดยการเรียกวิธีการโคลนฐานแล้วทำสิ่งที่จำเป็นต้องทำเพื่อโคลนลักษณะของประเภทที่ได้รับอย่างถูกต้องซึ่งวิธีการหลัก VirtualClone ยังไม่ได้รับการจัดการ

สำหรับความสามารถในการสืบทอดสูงสุด, คลาสที่เปิดเผยฟังก์ชันการโคลนสาธารณะควรเป็นsealedแต่ได้มาจากคลาสพื้นฐานที่เหมือนกันยกเว้นการขาดการโคลนนิ่ง แทนที่จะผ่านตัวแปรของชนิด clonable ICloneable<theNonCloneableType>อย่างชัดเจนใช้พารามิเตอร์ของชนิด นี้จะช่วยให้ประจำที่คาดว่าจะมีอนุพันธ์ cloneable ของ Fooการทำงานที่มีอนุพันธ์ของ cloneable DerivedFooแต่ยังช่วยให้การสร้างของสัญญาซื้อขายล่วงหน้าที่ไม่ใช่ cloneable Fooของ


5

ฉันคิดว่าคุณสามารถลองสิ่งนี้

MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = new MyObject(myObj); //DeepClone it

4

ฉันสร้างรุ่นของคำตอบที่ยอมรับซึ่งใช้ได้กับทั้ง '[Serializable]' และ '[DataContract]' ฉันเขียนมันมาระยะหนึ่งแล้ว แต่ถ้าฉันจำได้อย่างถูกต้องว่า [DataContract] จำเป็นต้องมี serializer ที่แตกต่างกัน

ต้องใช้ระบบ System.IO, System.Runtime.Serialization, System.Runtime.Serialization.Formatters.Binary, System.Xml ;

public static class ObjectCopier
{

    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[Serializable]' or '[DataContract]'
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T Clone<T>(T source)
    {
        if (typeof(T).IsSerializable == true)
        {
            return CloneUsingSerializable<T>(source);
        }

        if (IsDataContract(typeof(T)) == true)
        {
            return CloneUsingDataContracts<T>(source);
        }

        throw new ArgumentException("The type must be Serializable or use DataContracts.", "source");
    }


    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[Serializable]'
    /// </summary>
    /// <remarks>
    /// Found on http://stackoverflow.com/questions/78536/cloning-objects-in-c-sharp
    /// Uses code found on CodeProject, which allows free use in third party apps
    /// - http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
    /// </remarks>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T CloneUsingSerializable<T>(T source)
    {
        if (!typeof(T).IsSerializable)
        {
            throw new ArgumentException("The type must be serializable.", "source");
        }

        // Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        IFormatter formatter = new BinaryFormatter();
        Stream stream = new MemoryStream();
        using (stream)
        {
            formatter.Serialize(stream, source);
            stream.Seek(0, SeekOrigin.Begin);
            return (T)formatter.Deserialize(stream);
        }
    }


    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[DataContract]'
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T CloneUsingDataContracts<T>(T source)
    {
        if (IsDataContract(typeof(T)) == false)
        {
            throw new ArgumentException("The type must be a data contract.", "source");
        }

        // ** Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        DataContractSerializer dcs = new DataContractSerializer(typeof(T));
        using(Stream stream = new MemoryStream())
        {
            using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream))
            {
                dcs.WriteObject(writer, source);
                writer.Flush();
                stream.Seek(0, SeekOrigin.Begin);
                using (XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max))
                {
                    return (T)dcs.ReadObject(reader);
                }
            }
        }
    }


    /// <summary>
    /// Helper function to check if a class is a [DataContract]
    /// </summary>
    /// <param name="type">The type of the object to check.</param>
    /// <returns>Boolean flag indicating if the class is a DataContract (true) or not (false) </returns>
    public static bool IsDataContract(Type type)
    {
        object[] attributes = type.GetCustomAttributes(typeof(DataContractAttribute), false);
        return attributes.Length == 1;
    }

} 

4

ตกลงมีตัวอย่างที่ชัดเจนกับการสะท้อนในโพสต์นี้ แต่การสะท้อนกลับมักจะช้าจนกว่าคุณจะเริ่มแคชอย่างถูกต้อง

ถ้าคุณจะทำการแคชอย่างถูกต้องมันจะทำการโคลนนิ่งวัตถุ 1000000 ลึก 4,6 วินาที (วัดโดย Watcher)

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

กว่าที่คุณจะใช้คุณสมบัติแคชหรือเพิ่มใหม่ในพจนานุกรมและใช้พวกเขาเพียงแค่

foreach (var prop in propList)
{
        var value = prop.GetValue(source, null);   
        prop.SetValue(copyInstance, value, null);
}

ตรวจสอบรหัสเต็มในโพสต์ของฉันในคำตอบอื่น

https://stackoverflow.com/a/34365709/4711853


2
การโทรprop.GetValue(...)ยังคงเป็นภาพสะท้อนและไม่สามารถแคชได้ ในต้นไม้นิพจน์มันรวบรวมได้เร็วขึ้น
เซิง

4

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

มาตรฐานละเว้นคุณลักษณะที่ได้รับการสนับสนุนโดยใช้,[IgnoreDataMember] [NonSerialized]รองรับการเก็บรวบรวมที่ซับซ้อนคุณสมบัติที่ไม่มีตัวตั้งค่าฟิลด์แบบอ่านอย่างเดียวเป็นต้น

ฉันหวังว่ามันจะช่วยคนอื่นที่นั่นซึ่งประสบปัญหาเดียวกันกับฉัน


4

คำเตือน: ฉันเป็นผู้เขียนของแพคเกจดังกล่าว

ฉันรู้สึกประหลาดใจที่คำตอบสูงสุดของคำถามนี้ในปี 2562 ยังคงใช้การจัดลำดับหรือการสะท้อนกลับ

การทำให้อนุกรมเป็น จำกัด (ต้องมีแอตทริบิวต์ตัวสร้างเฉพาะ ฯลฯ ) และช้ามาก

BinaryFormatterต้องการแอSerializableททริบิวต์JsonConverterต้องการคอนสตรัคเตอร์หรือแอททริบิวต์แบบไม่มีพารามิเตอร์ไม่ต้องจัดการกับฟิลด์หรืออินเทอร์เฟซแบบอ่านอย่างเดียวอย่างดีและทั้งคู่นั้นช้ากว่าที่จำเป็น 10-30 เท่า

ต้นไม้แสดงออก

คุณสามารถใช้Expression TreesหรือReflection.Emitเพื่อสร้างรหัสการโคลนเพียงครั้งเดียวจากนั้นใช้โค้ดที่คอมไพล์แทนการสะท้อนแบบช้าหรือการทำให้เป็นอนุกรม

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

คุณสามารถค้นหาโครงการบน GitHub: https://github.com/marcelltoth/ObjectCloner

การใช้

คุณสามารถติดตั้งได้จาก NuGet รับObjectClonerแพ็กเกจและใช้เป็น:

var clone = ObjectCloner.DeepClone(original);

หรือถ้าคุณไม่รังเกียจที่จะก่อมลพิษประเภทวัตถุของคุณด้วยส่วนขยายก็ObjectCloner.Extensionsทำได้เช่นกันและเขียน

var clone = original.DeepClone();

ประสิทธิภาพ

ถือเป็นเกณฑ์มาตรฐานที่เรียบง่ายของโคลนลำดับชั้นแสดงให้เห็นประสิทธิภาพ ~ 3x เร็วกว่าการใช้สะท้อน ~ 12 เท่าเร็วกว่า Newtonsoft.Json อนุกรมและ ~ 36x BinaryFormatterเร็วกว่าแนะนำอย่าง

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