Vật thể nhân bản sâu


2226

Tôi muốn làm một cái gì đó như:

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

Và sau đó thực hiện thay đổi đối tượng mới không được phản ánh trong đối tượng ban đầu.

Tôi thường không cần chức năng này, vì vậy khi cần thiết, tôi đã dùng đến việc tạo một đối tượng mới và sau đó sao chép từng thuộc tính riêng lẻ, nhưng nó luôn mang lại cho tôi cảm giác rằng có cách xử lý tốt hơn hoặc thanh lịch hơn tình huống.

Làm cách nào tôi có thể sao chép hoặc sao chép sâu một đối tượng để có thể sửa đổi đối tượng nhân bản mà không có bất kỳ thay đổi nào được phản ánh trong đối tượng ban đầu?


81
Có thể hữu ích: "Tại sao Sao chép một đối tượng là một điều khủng khiếp phải làm?" agiledeveloper.com/articles/claming072002.htm
Pedro77


18
Bạn nên xem AutoMapper
Daniel Little

3
Giải pháp của bạn phức tạp hơn nhiều, tôi bị lạc khi đọc nó ... hehehe. Tôi đang sử dụng giao diện DeepClone. giao diện công cộng IDeepClonizable <T> {T DeepClone (); }
Pedro77

1
@ Pedro77 - Mặc dù, thật thú vị, bài báo đó kết thúc bằng cách tạo ra một clonephương thức trên lớp, sau đó gọi nó là một hàm tạo riêng, nội bộ được thông qua this. Vì vậy, sao chép là khủng khiếp [sic], nhưng sao chép cẩn thận (và chắc chắn là đáng đọc) của bài viết. ; ^)
ruffin

Câu trả lời:


1715

Trong khi thực tiễn tiêu chuẩn là triển khai ICloneablegiao diện (được mô tả ở đây , vì vậy tôi sẽ không hồi sinh), đây là một máy photocopy đối tượng nhân bản sâu đẹp mà tôi đã tìm thấy trên Dự án Code trước đây và kết hợp nó vào công cụ của chúng tôi.

Như đã đề cập ở nơi khác, nó yêu cầu các đối tượng của bạn phải được tuần tự hóa.

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);
        }
    }
}

Ý tưởng là nó tuần tự hóa đối tượng của bạn và sau đó giải tuần tự hóa nó thành một đối tượng mới. Lợi ích là bạn không phải lo lắng về việc nhân bản mọi thứ khi một đối tượng trở nên quá phức tạp.

Và với việc sử dụng các phương thức mở rộng (cũng từ nguồn được tham chiếu ban đầu):

Trong trường hợp bạn muốn sử dụng các phương thức mở rộng mới của C # 3.0, hãy thay đổi phương thức để có chữ ký sau:

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

Bây giờ cuộc gọi phương thức đơn giản trở thành objectBeingCloned.Clone();.

EDIT (ngày 10 tháng 1 năm 2015) Tôi nghĩ rằng tôi nên xem lại điều này, để đề cập đến việc gần đây tôi đã bắt đầu sử dụng (Newtonsoft) Json để làm điều này, nó sẽ nhẹ hơn và tránh được chi phí của các thẻ [Nối tiếp]. ( NB @atconway đã chỉ ra trong các ý kiến ​​rằng các thành viên tư nhân không được nhân bản bằng phương thức 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/claming-objects-in-c/ khuyên có một liên kết đến mã ở trên [và tham khảo hai cách triển khai khác, một trong số đó phù hợp hơn trong ngữ cảnh của tôi]
Ruben Bartelink

102
Tuần tự hóa / giải tuần tự liên quan đến chi phí đáng kể không cần thiết. Xem giao diện IClonizable và các phương thức nhân bản .MemberWise () trong C #.
3Dave

18
@David, được cấp, nhưng nếu các đối tượng nhẹ và hiệu suất đạt được khi sử dụng nó không quá cao so với yêu cầu của bạn, thì đó là một mẹo hữu ích. Tôi đã không sử dụng nó với số lượng lớn dữ liệu trong một vòng lặp, tôi thừa nhận, nhưng tôi chưa bao giờ thấy một mối quan tâm hiệu suất nào.
johnc

16
@Amir: thật ra, không: typeof(T).IsSerializablecũng đúng nếu loại đã được đánh dấu bằng [Serializable]thuộc tính. Nó không phải thực hiện ISerializablegiao diện.
Daniel Gehriger

11
Chỉ cần nghĩ rằng tôi đã đề cập rằng trong khi phương pháp này hữu ích và tôi đã sử dụng nó nhiều lần, nó hoàn toàn không tương thích với Medium Trust - vì vậy hãy xem nếu bạn đang viết mã cần tương thích. BinaryFormatter truy cập các trường riêng tư và do đó không thể hoạt động trong quyền truy cập mặc định cho các môi trường tin cậy một phần. Bạn có thể thử một serializer khác, nhưng hãy chắc chắn rằng người gọi của bạn biết rằng bản sao có thể không hoàn hảo nếu đối tượng đến phụ thuộc vào các trường riêng.
Alex Norcliffe

298

Tôi muốn một bản sao cho các đối tượng rất đơn giản của hầu hết các nguyên thủy và danh sách. Nếu đối tượng của bạn nằm ngoài hộp JSON tuần tự hóa thì phương thức này sẽ thực hiện thủ thuật. Điều này không yêu cầu sửa đổi hoặc triển khai các giao diện trên lớp nhân bản, chỉ cần một trình tuần tự hóa JSON như JSON.NET.

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

Ngoài ra, bạn có thể sử dụng phương pháp mở rộng này

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

13
solutiojn thậm chí còn nhanh hơn giải pháp BinaryFormatter, So sánh hiệu suất tuần tự hóa .NET
esskar

3
Cám ơn vì cái này. Về cơ bản, tôi đã có thể làm điều tương tự với bộ nối tiếp BSON đi kèm với trình điều khiển MongoDB cho C #.
Đánh dấu Ewer

3
Đây là cách tốt nhất đối với tôi, tuy nhiên, tôi sử dụng Newtonsoft.Json.JsonConvertnhưng nó giống nhau
Pierre

1
Để làm việc này, đối tượng cần sao chép cần phải được tuần tự hóa như đã đề cập - điều này cũng có nghĩa là nó có thể không có phụ thuộc vòng tròn
radomeit

2
Tôi nghĩ rằng đây là giải pháp tốt nhất vì việc triển khai có thể được áp dụng trên hầu hết các ngôn ngữ lập trình.
mr5

178

Lý do không sử dụng ICloneablekhông bởi vì nó không có một giao diện chung. Lý do không sử dụng nó là vì nó mơ hồ . Nó không làm rõ liệu bạn đang nhận được một bản sao nông hay sâu; đó là tùy thuộc vào người thực hiện.

Vâng, MemberwiseClonetạo một bản sao nông, nhưng ngược lại MemberwiseClonethì không Clone; có lẽ DeepClone, nó sẽ không tồn tại Khi bạn sử dụng một đối tượng thông qua giao diện IClonizable của nó, bạn không thể biết loại nhân bản nào mà đối tượng cơ bản thực hiện. (Và các nhận xét XML sẽ không làm rõ, bởi vì bạn sẽ nhận được các nhận xét về giao diện thay vì các nhận xét trên phương thức Clone của đối tượng.)

Những gì tôi thường làm chỉ đơn giản là tạo ra một Copyphương thức thực hiện chính xác những gì tôi muốn.


Tôi không rõ tại sao IClonizable được coi là mơ hồ. Với một loại như Từ điển (Of T, U), tôi hy vọng rằng IClonizable.Clone nên làm bất cứ mức độ sao chép sâu và nông nào là cần thiết để làm cho từ điển mới trở thành một từ điển độc lập có cùng chữ T và nội dung cấu trúc, và / hoặc tham chiếu đối tượng) làm bản gốc. Sự mơ hồ ở đâu? Để chắc chắn, một IClonizable (Of T) chung, kế thừa ISelf (Of T), bao gồm phương pháp "Tự", sẽ tốt hơn nhiều, nhưng tôi không thấy sự mơ hồ về nhân bản sâu so với nông.
supercat

31
Ví dụ của bạn minh họa vấn đề. Giả sử bạn có Từ điển <chuỗi, Khách hàng>. Từ điển nhân bản nên có cùng đối tượng Khách hàng với bản gốc hoặc bản sao của các đối tượng Khách hàng đó? Có trường hợp sử dụng hợp lý cho một trong hai. Nhưng IClonizable không làm rõ cái nào bạn sẽ nhận được. Đó là lý do tại sao nó không hữu ích.
Ryan Lundy

@Kyralessa Bài viết Microsoft MSDN thực sự nêu vấn đề rất khó biết là bạn đang yêu cầu một bản sao sâu hay nông.
đè bẹp

Câu trả lời từ stackoverflow.com/questions/129389/ từ mô tả Sao chép tiện ích mở rộng, dựa trên
MemberhipClone

123

Sau khi đọc nhiều về nhiều tùy chọn được liên kết ở đây và các giải pháp khả thi cho vấn đề này, tôi tin rằng tất cả các tùy chọn được tóm tắt khá tốt tại liên kết của Ian P (tất cả các tùy chọn khác là các biến thể của chúng) và giải pháp tốt nhất được cung cấp bởi Liên kết của Pedro77 về các ý kiến ​​câu hỏi.

Vì vậy, tôi sẽ chỉ sao chép các phần có liên quan của 2 tài liệu tham khảo ở đây. Bằng cách đó chúng ta có thể có:

Điều tốt nhất để làm nhân bản các đối tượng trong C sharp!

Đầu tiên và quan trọng nhất, đó là tất cả các lựa chọn của chúng tôi:

Các bài viết nhanh Sâu Sao chép bởi Trees Biểu cũng có so sánh hiệu suất của nhân bản bởi serialization, Reflection và Cây Expression.

Tại sao tôi chọn IClonizable (tức là thủ công)

Ông Venkat Subramaniam (liên kết dự phòng ở đây) giải thích chi tiết tại sao .

Tất cả các bài viết của ông xoay quanh một ví dụ cố gắng áp dụng cho hầu hết các trường hợp, sử dụng 3 đối tượng: Người , NãoThành phố . Chúng tôi muốn nhân bản một người, sẽ có bộ não riêng nhưng cùng một thành phố. Bạn có thể hình dung tất cả các vấn đề bất kỳ phương pháp nào khác ở trên có thể mang lại hoặc đọc bài viết.

Đây là phiên bản sửa đổi một chút của tôi về kết luận của anh ấy:

Sao chép một đối tượng bằng cách chỉ định Newtheo sau là tên lớp thường dẫn đến mã không thể mở rộng. Sử dụng clone, ứng dụng của mẫu thử nghiệm, là một cách tốt hơn để đạt được điều này. Tuy nhiên, việc sử dụng bản sao vì nó được cung cấp trong C # (và Java) cũng có thể khá khó khăn. Tốt hơn là cung cấp một hàm tạo sao chép được bảo vệ (không công khai) và gọi nó từ phương thức sao chép. Điều này cho chúng ta khả năng ủy thác nhiệm vụ tạo một đối tượng cho một thể hiện của chính lớp đó, do đó cung cấp khả năng mở rộng và đồng thời, tạo các đối tượng một cách an toàn bằng cách sử dụng hàm tạo sao chép được bảo vệ.

Hy vọng việc thực hiện này có thể làm cho mọi thứ rõ ràng:

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);
    }
    
}

Bây giờ hãy xem xét việc có một lớp xuất phát từ Người.

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();
    }
}

Bạn có thể thử chạy đoạn mã sau:

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);
    }
}

Đầu ra được sản xuất sẽ là:

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

Quan sát rằng, nếu chúng ta giữ một số lượng đối tượng, bản sao được thực hiện ở đây sẽ giữ một số lượng chính xác của số lượng đối tượng.


6
MS khuyên không nên sử dụng ICloneablecho các thành viên công cộng. "Bởi vì người gọi Clone không thể phụ thuộc vào phương thức thực hiện thao tác nhân bản có thể dự đoán được, chúng tôi khuyên rằng IClonizable không được triển khai trong các API công khai." msdn.microsoft.com/en-us/l Library / Mạnh Tuy nhiên, dựa trên lời giải thích được đưa ra bởi Venkat Subramaniam trong bài viết được liên kết của bạn, tôi nghĩ sẽ hợp lý khi sử dụng trong tình huống này miễn là người tạo ra các đối tượng IClonizable có chiều sâu sự hiểu biết về các thuộc tính nên sâu so với các bản sao nông (tức là bản sao sâu Brain, bản sao nông Thành phố)
BateTech

Trước hết, tôi là một chuyên gia trong chủ đề này (API công khai). Tôi nghĩ rằng một lần rằng nhận xét MS có rất nhiều ý nghĩa. Và tôi không nghĩ sẽ an toàn khi cho rằng người dùng API đó sẽ có sự hiểu biết sâu sắc như vậy. Vì vậy, nó chỉ có ý nghĩa khi triển khai nó trên API công khai nếu nó thực sự không quan trọng đối với bất kỳ ai sẽ sử dụng nó. Tôi đoán rằng có một số loại UML rất rõ ràng làm cho sự khác biệt trên mỗi thuộc tính có thể giúp ích. Nhưng tôi muốn nghe từ một người có nhiều kinh nghiệm hơn. : P
cregox

Bạn có thể sử dụng Trình tạo bản sao CGbR và nhận được kết quả tương tự mà không cần viết mã theo cách thủ công.
Toxantron

Triển khai ngôn ngữ trung gian rất hữu ích
Michael Freidgeim

Không có trận chung kết nào ở C #
Konrad

84

Tôi thích một constructor sao chép vào một bản sao. Ý định rõ ràng hơn.


5
.Net không có các nhà xây dựng sao chép.
Pop Catalin

48
Chắc chắn là có: MyObject mới (objToCloneFrom) Chỉ cần khai báo một ctor sẽ lấy đối tượng để sao chép làm tham số.
Nick

30
Đó không phải là điều tương tự. Bạn phải thêm nó vào mỗi lớp theo cách thủ công và thậm chí bạn không biết nếu bạn đang kiểm soát một bản sao sâu.
Dave Van den Eynde

15
+1 cho ctor sao chép. Bạn cũng phải tự viết một hàm clone () cho từng loại đối tượng và chúc may mắn với điều đó khi hệ thống phân cấp lớp của bạn có một vài cấp độ sâu.
Andrew Grant

3
Với các nhà xây dựng sao chép, bạn mất phân cấp mặc dù. agiledeveloper.com/articles/claming072002.htm
Sẽ

42

Phương pháp mở rộng đơn giản để sao chép tất cả các thuộc tính công cộng. Hoạt động cho bất kỳ đối tượng và không yêu cầu lớp [Serializable]. Có thể được mở rộng cho cấp độ truy cập khác.

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
Điều này, không may, là thiếu sót. Nó tương đương với việc gọi objectOne.MyProperty = objectTwo.MyProperty (nghĩa là nó sẽ chỉ sao chép tham chiếu qua). Nó sẽ không nhân bản các giá trị của các thuộc tính.
Alex Norcliffe

1
cho Alex Norcliffe: tác giả của câu hỏi hỏi về "sao chép từng tài sản" thay vì nhân bản. trong hầu hết các trường hợp không cần sao chép chính xác các thuộc tính.
Konstantin Salavatov

1
tôi nghĩ về việc sử dụng phương pháp này nhưng với đệ quy. vì vậy nếu giá trị của thuộc tính là tham chiếu, hãy tạo một đối tượng mới và gọi lại CopyTo. Tôi chỉ thấy một vấn đề, rằng tất cả các lớp được sử dụng phải có một hàm tạo không có tham số. Có ai đã thử cái này chưa? tôi cũng tự hỏi nếu điều này thực sự sẽ làm việc với các thuộc tính có chứa các lớp .net như DataRow và DataTable?
Koryu

33

Tôi vừa tạo dự án CloneExtensionsthư viện . Nó thực hiện sao chép nhanh, sâu bằng cách sử dụng các thao tác gán đơn giản được tạo bởi quá trình biên dịch mã thời gian chạy của Expression Tree.

Làm thế nào để sử dụng nó?

Thay vì viết các phương thức Clonehoặc Copyphương thức của riêng bạn với một giai điệu của các bài tập giữa các trường và thuộc tính, hãy tạo chương trình cho chính bạn, sử dụng Expression Tree. GetClone<T>()phương thức được đánh dấu là phương thức mở rộng cho phép bạn chỉ cần gọi nó theo thể hiện của bạn:

var newInstance = source.GetClone();

Bạn có thể chọn những gì nên được sao chép từ sourceđể newInstancesử dụng CloningFlagsenum:

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

Những gì có thể được nhân bản?

  • Nguyên thủy (int, uint, byte, double, char, v.v.), các loại không thay đổi đã biết (DateTime, TimeSpan, String) và các đại biểu (bao gồm Action, Func, v.v.)
  • Không thể
  • T [] mảng
  • Các lớp và cấu trúc tùy chỉnh, bao gồm các lớp và cấu trúc chung.

Các thành viên lớp / cấu trúc sau được nhân bản nội bộ:

  • Giá trị của các lĩnh vực công cộng, không chỉ đọc
  • Giá trị của các thuộc tính công cộng với cả bộ truy cập get và set
  • Bộ sưu tập cho các loại thực hiện ICollection

Nó nhanh như thế nào?

Giải pháp nhanh hơn là phản xạ, bởi vì thông tin thành viên chỉ được thu thập một lần, trước khi GetClone<T>được sử dụng lần đầu tiên cho loại đã cho T.

Nó cũng nhanh hơn giải pháp dựa trên tuần tự hóa khi bạn sao chép nhiều hơn một vài trường hợp cùng loại T.

và hơn thế nữa...

Đọc thêm về các biểu thức được tạo ra trên tài liệu .

Danh sách gỡ lỗi biểu thức mẫu cho 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:
}

}

Điều gì có ý nghĩa tương tự như sau mã 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;
}

Nó không giống như cách bạn viết Clonephương pháp của riêng mình List<int>sao?


2
Cơ hội của việc này trên NuGet là gì? Có vẻ như là giải pháp tốt nhất. Làm thế nào để nó so sánh với NClone ?
nghiền nát

Tôi nghĩ rằng câu trả lời này nên được nâng cao hơn nhiều lần. Việc triển khai thủ công IClonizable rất tẻ nhạt và dễ bị lỗi, sử dụng phản xạ hoặc tuần tự hóa chậm nếu hiệu suất là quan trọng và bạn cần sao chép hàng ngàn đối tượng trong một khoảng thời gian ngắn.
nightcoder

Hoàn toàn không, bạn đã sai về sự phản chiếu, bạn chỉ cần lưu trữ bộ đệm này đúng cách. Kiểm tra câu trả lời của tôi dưới đây stackoverflow.com/a
432368738/4711853

31

Chà, tôi đã gặp vấn đề khi sử dụng IClonizable trong Silverlight, nhưng tôi thích ý tưởng về seralization, tôi có thể seral hóa XML, vì vậy tôi đã làm điều này:

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

Nếu bạn đã sử dụng ứng dụng của bên thứ 3 như ValueInjecter hoặc Automapper , bạn có thể làm một cái gì đó như thế này:

MyObject oldObj; // The existing object to clone

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

Sử dụng phương pháp này bạn không phải thực hiện ISerializablehoặc ICloneabletrên các đối tượng của mình. Điều này là phổ biến với mẫu MVC / MVVM, vì vậy các công cụ đơn giản như thế này đã được tạo.

xem mẫu nhân bản sâu ValueInjecter trên GitHub .


25

Tốt nhất là thực hiện một phương thức mở rộng như

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

và sau đó sử dụng nó ở bất cứ đâu trong giải pháp

var copy = anyObject.DeepClone();

Chúng ta có thể có ba triển khai sau:

  1. Bằng cách tuần tự hóa (mã ngắn nhất)
  2. Bằng phản xạ - nhanh hơn 5x
  3. Bằng cây biểu hiện - nhanh hơn 20 lần

Tất cả các phương pháp liên kết đều hoạt động tốt và đã được thử nghiệm sâu sắc.


Mã nhân bản bằng cách sử dụng cây Expression mà bạn đã đăng codeproject.com/Articles/1111658/ , không thành công với các phiên bản mới hơn của khung .Net với ngoại lệ bảo mật, Thao tác có thể làm mất ổn định thời gian chạy , về cơ bản là ngoại lệ do cây biểu hiện không đúng định dạng, được sử dụng để tạo Func khi chạy, vui lòng kiểm tra xem bạn có giải pháp nào không. Thực tế tôi chỉ thấy vấn đề với các đối tượng phức tạp với hệ thống phân cấp sâu, đơn giản dễ dàng bị sao chép
Mrinal Kamboj 24/12/17

1
Triển khai ExpressionTree có vẻ rất tốt. Nó thậm chí hoạt động với các tài liệu tham khảo tròn và các thành viên tư nhân. Không có thuộc tính cần thiết. Câu trả lời hay nhất tôi đã tìm thấy.
N73k

Câu trả lời hay nhất, hoạt động rất tốt, bạn đã cứu ngày của tôi
Adel Mourad

23

Câu trả lời ngắn gọn là bạn kế thừa từ giao diện IClonizable và sau đó triển khai chức năng .clone. Clone nên thực hiện sao chép thành viên và thực hiện sao chép sâu vào bất kỳ thành viên nào yêu cầu, sau đó trả về đối tượng kết quả. Đây là một hoạt động đệ quy (nó yêu cầu tất cả các thành viên của lớp bạn muốn sao chép là loại giá trị hoặc thực hiện IClonizable và các thành viên của chúng là loại giá trị hoặc thực hiện IClonizable, v.v.).

Để được giải thích chi tiết hơn về Nhân bản bằng IClonizable, hãy xem bài viết này .

Các lâu câu trả lời là "nó phụ thuộc". Như những người khác đã đề cập, IClonizable không được hỗ trợ bởi generic, đòi hỏi phải cân nhắc đặc biệt đối với các tham chiếu lớp tròn và thực sự được một số người coi là "lỗi" trong .NET Framework. Phương thức tuần tự hóa phụ thuộc vào các đối tượng của bạn được tuần tự hóa, chúng có thể không và bạn có thể không kiểm soát được. Vẫn còn nhiều tranh luận trong cộng đồng về việc đó là cách thực hành "tốt nhất". Trong thực tế, không có giải pháp nào là một kích thước phù hợp với tất cả các thực tiễn tốt nhất cho tất cả các tình huống như IClonizable ban đầu được hiểu là.

Xem bài viết Góc của Nhà phát triển này để biết thêm một số tùy chọn (ghi có cho Ian).


1
IClonizable không có giao diện chung, vì vậy không nên sử dụng giao diện đó.
Karg

Giải pháp của bạn hoạt động cho đến khi cần xử lý các tham chiếu vòng tròn, sau đó mọi thứ bắt đầu phức tạp, tốt hơn là thử thực hiện nhân bản sâu bằng cách sử dụng tuần tự hóa sâu.
Pop Catalin

Thật không may, không phải tất cả các đối tượng đều được tuần tự hóa, vì vậy bạn cũng không thể luôn sử dụng phương pháp đó. Liên kết của Ian là câu trả lời toàn diện nhất cho đến nay.
Zach Burlingame

19
  1. Về cơ bản, bạn cần thực hiện giao diện IClonizable và sau đó nhận ra việc sao chép cấu trúc đối tượng.
  2. Nếu đó là bản sao sâu của tất cả các thành viên, bạn cần đảm bảo (không liên quan đến giải pháp bạn chọn) rằng tất cả trẻ em cũng có thể được nhân bản.
  3. Đôi khi bạn cần lưu ý một số hạn chế trong quá trình này, ví dụ: nếu bạn sao chép các đối tượng ORM, hầu hết các khung chỉ cho phép một đối tượng được gắn vào phiên và bạn KHÔNG PHẢI tạo bản sao của đối tượng này hoặc nếu có thể bạn cần quan tâm về phiên đính kèm của các đối tượng này.

Chúc mừng.


4
IClonizable không có giao diện chung, vì vậy không nên sử dụng giao diện đó.
Karg

Câu trả lời đơn giản và súc tích là tốt nhất.
DavidGuaita

17

EDIT: dự án bị ngưng

Nếu bạn muốn nhân bản thật sự vào các loại không xác định, bạn có thể xem fastclone .

Đó là nhân bản dựa trên biểu thức hoạt động nhanh hơn khoảng 10 lần so với tuần tự nhị phân và duy trì tính toàn vẹn của biểu đồ đối tượng.

Điều đó có nghĩa là: nếu bạn giới thiệu nhiều lần cho cùng một đối tượng trong hierachy của mình, bản sao cũng sẽ có một trường hợp ong được tham chiếu duy nhất.

Không cần giao diện, thuộc tính hoặc bất kỳ sửa đổi nào khác cho các đối tượng được nhân bản.


Cái này có vẻ khá hữu ích
LuckyLikey

Thật dễ dàng để bắt đầu làm việc từ một ảnh chụp nhanh hơn so với toàn bộ hệ thống, đặc biệt là một mã đóng. Điều khá dễ hiểu là không có thư viện nào có thể giải quyết tất cả các vấn đề chỉ bằng một lần chụp. Một số thư giãn nên được thực hiện.
TarmoPikaro

1
Tôi đã thử giải pháp của bạn và nó có vẻ hoạt động tốt, cảm ơn! Tôi nghĩ rằng câu trả lời này nên được nâng cao hơn nhiều lần. Việc triển khai thủ công IClonizable rất tẻ nhạt và dễ bị lỗi, sử dụng phản xạ hoặc tuần tự hóa chậm nếu hiệu suất là quan trọng và bạn cần sao chép hàng ngàn đối tượng trong một khoảng thời gian ngắn.
nightcoder

Tôi đã thử nó và nó không làm việc cho tôi. Ném một ngoại lệ MemberAccess.
Michael Brown

Nó không hoạt động với các phiên bản .NET mới hơn và đã ngừng sản xuất
Michael Sander

14

Giữ mọi thứ đơn giản và sử dụng AutoMapper như những người khác đã đề cập, đó là một thư viện nhỏ đơn giản để ánh xạ đối tượng này sang đối tượng khác ... Để sao chép một đối tượng sang đối tượng khác có cùng loại, tất cả những gì bạn cần là ba dòng mã:

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

Đối tượng đích bây giờ là một bản sao của đối tượng nguồn. Không đủ đơn giản? Tạo một phương thức mở rộng để sử dụng ở mọi nơi trong giải pháp của bạn:

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

Phương pháp mở rộng có thể được sử dụng như sau:

MyType copy = source.Copy();

Hãy cẩn thận với điều này, nó thực hiện rất kém. Tôi đã kết thúc việc chuyển sang câu trả lời johnc ngắn như câu trả lời này và thực hiện tốt hơn rất nhiều.
Agorilla

1
Điều này chỉ làm một bản sao nông.
N73k

11

Tôi đã đưa ra điều này để khắc phục một thiếu sót .NET phải sao chép sâu Danh sách thủ công <T>.

Tôi sử dụng cái này:

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

Và ở một nơi khác:

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

Tôi đã cố gắng đưa ra oneliner thực hiện điều này, nhưng không thể, do năng suất không hoạt động trong các khối phương thức ẩn danh.

Vẫn tốt hơn, sử dụng Danh sách chung <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

Q. Tại sao tôi chọn câu trả lời này?

  • Chọn câu trả lời này nếu bạn muốn tốc độ nhanh nhất .NET có khả năng.
  • Bỏ qua câu trả lời này nếu bạn muốn một phương pháp nhân bản thực sự, thực sự dễ dàng.

Nói cách khác, đi với một câu trả lời khác trừ khi bạn có một nút cổ chai hiệu năng cần sửa chữa, và bạn có thể chứng minh điều đó bằng một hồ sơ .

Nhanh hơn gấp 10 lần so với các phương pháp khác

Phương pháp sau đây để thực hiện một bản sao sâu là:

  • Nhanh hơn 10 lần so với bất cứ điều gì liên quan đến tuần tự hóa / giải tuần tự hóa;
  • Khá gần với tốc độ tối đa lý thuyết .NET có khả năng.

Và phương pháp ...

Để có tốc độ tối đa, bạn có thể sử dụng Nested MemberwiseClone để sao chép sâu . Tốc độ của nó gần giống như sao chép một cấu trúc giá trị và nhanh hơn nhiều so với (a) phản chiếu hoặc (b) tuần tự hóa (như được mô tả trong các câu trả lời khác trên trang này).

Lưu ý rằng nếu bạn sử dụng Nested MemberwiseClone cho một bản sao sâu , bạn phải triển khai ShallowCopy theo cách thủ công cho từng cấp độ lồng nhau trong lớp và DeepCopy gọi tất cả các phương thức ShallowCopy đã nói để tạo một bản sao hoàn chỉnh. Điều này rất đơn giản: chỉ có một vài dòng trong tổng số, xem mã demo bên dưới.

Đây là đầu ra của mã hiển thị sự khác biệt hiệu suất tương đối cho 100.000 bản sao:

  • 1,08 giây cho Nested MemberwiseClone trên các cấu trúc lồng nhau
  • 4,77 giây cho Nested MemberwiseClone trên các lớp lồng nhau
  • 39,93 giây cho tuần tự hóa / khử lưu huỳnh

Sử dụng Nested MemberwiseClone trên một lớp gần như nhanh như sao chép một cấu trúc và sao chép một cấu trúc khá gần với tốc độ tối đa theo lý thuyết mà .NET có khả năng.

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

Để hiểu cách thực hiện một bản sao sâu bằng cách sử dụng MemberwiseCopy, đây là dự án demo được sử dụng để tạo thời gian ở trên:

// 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;
    }
}

Sau đó, gọi bản demo từ chính:

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();
}

Một lần nữa, lưu ý rằng nếu bạn sử dụng Nested MemberwiseClone cho một bản sao sâu , bạn phải triển khai ShallowCopy theo cách thủ công cho từng cấp độ lồng nhau trong lớp và DeepCopy gọi tất cả các phương thức ShallowCopy đã nói để tạo một bản sao hoàn chỉnh. Điều này rất đơn giản: chỉ có một vài dòng trong tổng số, xem mã demo ở trên.

Các loại giá trị so với các loại tài liệu tham khảo

Lưu ý rằng khi nói đến việc nhân bản một đối tượng, có một sự khác biệt lớn giữa " struct " và " class ":

  • Nếu bạn có " struct ", đó là một loại giá trị để bạn có thể sao chép nó và nội dung sẽ được sao chép (nhưng nó sẽ chỉ tạo một bản sao nông trừ khi bạn sử dụng các kỹ thuật trong bài đăng này).
  • Nếu bạn có một " lớp ", đó là một loại tham chiếu , vì vậy nếu bạn sao chép nó, tất cả những gì bạn đang làm là sao chép con trỏ vào nó. Để tạo một bản sao thực sự, bạn phải sáng tạo hơn và sử dụng sự khác biệt giữa các loại giá trị và các loại tham chiếu để tạo một bản sao khác của đối tượng gốc trong bộ nhớ.

Xem sự khác biệt giữa các loại giá trị và các loại tham chiếu .

Tổng kiểm tra để hỗ trợ gỡ lỗi

  • Nhân bản các đối tượng không chính xác có thể dẫn đến các lỗi rất khó xác định. Trong mã sản xuất, tôi có xu hướng thực hiện tổng kiểm tra để kiểm tra lại xem đối tượng đã được nhân bản đúng chưa và đã không bị hỏng bởi một tham chiếu khác đến nó. Tổng kiểm tra này có thể được tắt trong chế độ Phát hành.
  • Tôi thấy phương pháp này khá hữu ích: thông thường, bạn chỉ muốn sao chép các phần của đối tượng, không phải toàn bộ.

Thực sự hữu ích cho việc tách rời nhiều chủ đề từ nhiều chủ đề khác

Một trường hợp sử dụng tuyệt vời cho mã này là cho các bản sao của một lớp lồng nhau hoặc cấu trúc vào một hàng đợi, để thực hiện mô hình nhà sản xuất / người tiêu dùng.

  • Chúng ta có thể có một (hoặc nhiều) chủ đề sửa đổi một lớp mà họ sở hữu, sau đó đẩy một bản sao hoàn chỉnh của lớp này vào một ConcurrentQueue .
  • Sau đó chúng tôi có một (hoặc nhiều) chủ đề kéo các bản sao của các lớp này ra và xử lý chúng.

Điều này hoạt động rất tốt trong thực tế và cho phép chúng tôi tách rời nhiều chủ đề (nhà sản xuất) từ một hoặc nhiều chủ đề (người tiêu dùng).

Và phương pháp này cũng rất nhanh: nếu chúng ta sử dụng các cấu trúc lồng nhau, nó nhanh hơn 35 lần so với các lớp lồng nhau / giải tuần tự hóa và cho phép chúng ta tận dụng tất cả các luồng có sẵn trên máy.

Cập nhật

Rõ ràng, ExpressMapper nhanh như vậy, nếu không nhanh hơn so với mã hóa tay như ở trên. Tôi có thể phải xem làm thế nào họ so sánh với một hồ sơ.


Nếu bạn sao chép một cấu trúc bạn nhận được một bản sao nông, bạn vẫn có thể cần triển khai cụ thể cho một bản sao sâu.
Lasse V. Karlsen

@Lasse V. Karlsen. Vâng, bạn hoàn toàn chính xác, tôi đã cập nhật câu trả lời để làm cho điều này rõ ràng hơn. Phương pháp này có thể được sử dụng để tạo các bản sao sâu của các cấu trúc các lớp. Bạn có thể chạy mã demo ví dụ được bao gồm để hiển thị cách thực hiện, nó có một ví dụ về nhân bản sâu một cấu trúc lồng nhau và một ví dụ khác về nhân bản sâu một lớp lồng nhau.
Contango

9

Nói chung, bạn thực hiện giao diện IClonizable và tự thực hiện Clone. Các đối tượng C # có một phương thức MemberwiseClone tích hợp thực hiện một bản sao nông có thể giúp bạn thoát khỏi tất cả các nguyên thủy.

Đối với một bản sao sâu, không có cách nào nó có thể biết cách tự động làm điều đó.


IClonizable không có giao diện chung, vì vậy không nên sử dụng giao diện đó.
Karg

8

Tôi đã thấy nó được thực hiện thông qua sự phản ánh là tốt. Về cơ bản, có một phương pháp sẽ lặp lại thông qua các thành viên của một đối tượng và sao chép chúng một cách thích hợp vào đối tượng mới. Khi nó đạt đến các kiểu tham chiếu hoặc bộ sưu tập, tôi nghĩ rằng nó đã thực hiện một cuộc gọi đệ quy trên chính nó. Phản xạ là đắt tiền, nhưng nó hoạt động khá tốt.


8

Đây là một bản sao thực hiện sâu:

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
Điều này trông giống như bản sao thành viên vì không biết các thuộc tính loại tham chiếu
sll

1
Nếu bạn muốn hiệu suất nhanh chóng, đừng thực hiện việc này: nó sử dụng sự phản chiếu, vì vậy nó sẽ không nhanh như vậy. Ngược lại, "tối ưu hóa sớm là điều xấu xa", vì vậy hãy bỏ qua khía cạnh hiệu suất cho đến khi bạn chạy một trình lược tả.
Contango

1
CreatInstanceOfType không được xác định?
MonsterMMORPG

Nó thất bại trên interger: "Phương thức không tĩnh yêu cầu một mục tiêu."
Mr.B

8

Vì tôi không thể tìm thấy một trình sao chép đáp ứng tất cả các yêu cầu của tôi trong các dự án khác nhau, tôi đã tạo ra một trình sao chép sâu có thể được cấu hình và điều chỉnh theo các cấu trúc mã khác nhau thay vì điều chỉnh mã của tôi để đáp ứng các yêu cầu của trình sao chép. Nó đạt được bằng cách thêm các chú thích vào mã sẽ được sao chép hoặc bạn chỉ cần để lại mã như nó có hành vi mặc định. Nó sử dụng sự phản chiếu, bộ nhớ cache và dựa trên quickflect . Quá trình nhân bản rất nhanh đối với một lượng dữ liệu khổng lồ và hệ thống phân cấp đối tượng cao (so với các thuật toán dựa trên phản xạ / tuần tự hóa khác).

https://github.com/kalisohn/CloneBehave

Cũng có sẵn dưới dạng gói nuget: https://www.nuget.org/packages/Clone.Behave/1.0.0

Ví dụ: Đoạn mã sau sẽ ghi địa chỉ deepClone, nhưng chỉ thực hiện một bản sao nông của trường _cienJob.

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

Trình tạo mã

Chúng tôi đã thấy rất nhiều ý tưởng từ việc tuần tự hóa qua triển khai thủ công đến phản ánh và tôi muốn đề xuất một cách tiếp cận hoàn toàn khác bằng cách sử dụng Trình tạo mã CGbR . Phương thức tạo bản sao là bộ nhớ và CPU hiệu quả và nhanh hơn 300 lần như DataContractSerializer tiêu chuẩn.

Tất cả những gì bạn cần là một định nghĩa lớp một phần ICloneablevà trình tạo thực hiện phần còn lại:

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;
    }
}

Lưu ý: Phiên bản mới nhất có nhiều kiểm tra null hơn, nhưng tôi đã bỏ qua chúng để hiểu rõ hơn.


6

Tôi thích Copyconstructor như thế:

    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));
        }
    }

Nếu bạn có nhiều thứ để sao chép, hãy thêm chúng


6

Phương pháp này đã giải quyết vấn đề cho tôi:

private static MyObj DeepCopy(MyObj source)
        {

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

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

        }

Sử dụng nó như thế này: MyObj a = DeepCopy(b);


6

Đây là một giải pháp nhanh chóng và dễ dàng mà hiệu quả với tôi mà không cần chuyển tiếp vào Tuần tự hóa / Giải trừ.

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;
    }
}

EDIT : yêu cầu

    using System.Linq;
    using System.Reflection;

Đó là cách tôi sử dụng nó

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

5

Thực hiện theo các bước sau:

  • Xác định một ISelf<T>với một thuộc tính chỉ đọc Selftrả về TICloneable<out T>, xuất phát từ ISelf<T>và bao gồm một phương thứcT Clone() .
  • Sau đó, xác định một CloneBaseloại thực hiện protected virtual generic VirtualCloneđúcMemberwiseClone cho kiểu truyền vào.
  • Mỗi loại dẫn xuất nên thực hiện VirtualClonebằng cách gọi phương thức nhân bản cơ sở và sau đó làm bất cứ điều gì cần làm để sao chép chính xác các khía cạnh của loại dẫn xuất mà phương thức VirtualClone gốc chưa xử lý.

Để có tính linh hoạt kế thừa tối đa, các lớp phơi bày chức năng nhân bản công khai phải sealed, nhưng xuất phát từ một lớp cơ sở giống hệt nhau ngoại trừ việc thiếu nhân bản. Thay vì chuyển các biến của loại có thể sao chép rõ ràng, hãy lấy tham số của loại ICloneable<theNonCloneableType>. Điều này sẽ cho phép một thói quen mong đợi một dẫn xuất có Foothể nhân bản để làm việc với một dẫn xuất có thể nhân bản của DerivedFoo, nhưng cũng cho phép tạo ra các dẫn xuất không thể sao chép của Foo.


5

Tôi nghĩ bạn có thể thử điều này.

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

4

Tôi đã tạo một phiên bản của câu trả lời được chấp nhận hoạt động với cả '[Nối tiếp]' và '[DataContract]'. Đã được một thời gian kể từ khi tôi viết nó, nhưng nếu tôi nhớ chính xác [DataContract] thì cần một bộ nối tiếp khác.

Yêu cầu System, System.IO, System.R.78.Serialization, System.R.78.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

Ok, có một số ví dụ rõ ràng với sự phản chiếu trong bài đăng này, phản ánh NHƯNG thường chậm, cho đến khi bạn bắt đầu lưu trữ nó đúng cách.

nếu bạn sẽ lưu trữ bộ đệm đúng cách, thì nó sẽ sao chép sâu 1000000 đối tượng trong 4,6 giây (được đo bởi Người theo dõi).

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

hơn là bạn lấy các thuộc tính được lưu trong bộ nhớ cache hoặc thêm mới vào từ điển và sử dụng chúng một cách đơn giản

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

kiểm tra mã đầy đủ trong bài viết của tôi trong một câu trả lời khác

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


2
Gọi prop.GetValue(...)vẫn là phản ánh và không thể được lưu trữ. Trong một cây biểu thức, nó được biên dịch, vì vậy nhanh hơn
Tseng

4

Vì gần như tất cả các câu trả lời cho câu hỏi này đều không thỏa đáng hoặc hoàn toàn không hoạt động trong tình huống của tôi, tôi đã tạo ra AnyClone hoàn toàn được thực hiện với sự phản ánh và giải quyết tất cả các nhu cầu ở đây. Tôi không thể có được tuần tự hóa để làm việc trong một kịch bản phức tạp với cấu trúc phức tạp vàIClonable ít hơn lý tưởng - thực tế nó thậm chí không cần thiết.

Thuộc tính bỏ qua tiêu chuẩn được hỗ trợ bằng cách sử dụng [IgnoreDataMember],[NonSerialized] . Hỗ trợ các bộ sưu tập phức tạp, các thuộc tính không có setters, các trường chỉ đọc, v.v.

Tôi hy vọng nó sẽ giúp được người khác ngoài kia gặp phải những vấn đề tương tự tôi đã làm.


4

Tuyên bố miễn trừ trách nhiệm: Tôi là tác giả của gói được đề cập.

Tôi đã rất ngạc nhiên khi các câu trả lời hàng đầu cho câu hỏi này vào năm 2019 vẫn sử dụng tuần tự hóa hoặc phản ánh.

Tuần tự hóa là hạn chế (yêu cầu các thuộc tính, các hàm tạo cụ thể, v.v.) và rất chậm

BinaryFormatteryêu cầu Serializablethuộc tính, JsonConverteryêu cầu một hàm tạo hoặc tham số không tham số, không xử lý các trường chỉ đọc hoặc giao diện rất tốt và cả hai đều chậm hơn 10-30 lần so với cần thiết.

Cây biểu hiện

Thay vào đó, bạn có thể sử dụng Cây biểu thức hoặc Reflection.Emit để tạo mã nhân bản chỉ một lần, sau đó sử dụng mã được biên dịch đó thay vì phản xạ chậm hoặc tuần tự hóa.

Tự mình gặp phải vấn đề và không thấy giải pháp thỏa đáng, tôi quyết định tạo ra một gói thực hiện điều đó và hoạt động với mọi loại và nhanh như mã viết tùy chỉnh .

Bạn có thể tìm thấy dự án trên GitHub: https://github.com/marcelltoth/ObjectCloner

Sử dụng

Bạn có thể cài đặt nó từ NuGet. Hoặc nhận ObjectClonergói và sử dụng như:

var clone = ObjectCloner.DeepClone(original);

hoặc nếu bạn không ngại làm ô nhiễm loại đối tượng của mình bằng các tiện ích mở rộng ObjectCloner.Extensionscũng như vậy và viết:

var clone = original.DeepClone();

Hiệu suất

Một điểm chuẩn đơn giản để nhân bản một hệ thống phân cấp lớp cho thấy hiệu suất nhanh hơn ~ 3 lần so với sử dụng Reflection, nhanh hơn ~ 12 lần so với tuần tự hóa Newtonsoft.Json và nhanh hơn ~ 36 lần so với đề xuất cao BinaryFormatter.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.