it-swarm-vi.tech

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

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 các thay đổi đối vớ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, 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?

2042
NakedBrunch

Trong khi thực tiễn tiêu chuẩn là triển khai giao diện ICloneable (đượ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 một thời gian trước và kết hợp nó trong 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.", "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();.

CHỈNH SỬA(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ó nên nhẹ hơn và tránh được chi phí của [ Thẻ tuần tự]. (NB@atconway đã chỉ ra trong các nhận xét rằng các thành viên riêng tư 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);
}
1592
johnc

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

Lý do không sử dụng IClonizable is not vì nó không có 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.

Có, MemberwiseClone tạo một bản sao nông, nhưng ngược lại với MemberwiseClone không phải là Clone; có lẽ, có lẽ, DeepClone, 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 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 phương thức Copy thực hiện chính xác những gì tôi muốn.

162
Ryan Lundy

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 các tùy chọn đó ) và giải pháp tốt nhất được cung cấp bởi Pedro77 's link trên các 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 cho nhân bản các đối tượng trong c sắc nét!

Đầ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:

Bài viết Fast Deep Copy bởi Cây biểu hiện cũng có hiệu năng so sánh nhân bản bằng cây nối tiếp, cây phản xạ và cây biểu hiện.

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ả bài viết của anh 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: Person, BrainCity. 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 New theo 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à cũng có thể 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 [email protected]
This is person with [email protected]
SkilledPerson: This is person with [email protected]
SkilledPerson: This is person with [email protected]

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.

102
cregox

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

77
Nick

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 mọi đối tượng và không yêu cầu lớp phải là [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 ) } );
        }
    };
}
38
Konstantin Salavatov

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
    //[email protected]
    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);
    }
}
30
Michael White

Tôi vừa tạo thư việnCloneExtensionsdự á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 Clone hoặc Copy của riêng bạn với một giai điệu gán 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. Phương thức GetClone<T>() đượ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 sang newInstance bằng cách sử dụng CloningFlags enum:

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

Cái 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ạ, 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 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 phương thức Clone của riêng bạn cho List<int>?

27
MarcinJuraszek

Nếu bạn đã sử dụng ứng dụng 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 ISerializable hoặc IClonizable trê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 giải pháp nhân bản sâu của valueinjecter trên CodePlex .

26
Michael Cox

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âu trả lời dài là "nó phụ thuộc". Như đã đề cập bởi những người khác, 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 xem 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 này Góc dành cho nhà phát triển để biết thêm một vài lựa chọn (ghi có cho Ian).

20
Zach Burlingame

Cách tốt nhất là triển khai phương thức phần mở rộng like

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ạ - 5x nhanh hơn
  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.

19
frakon
  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 sắc 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.

16
dimarzionist

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 ví dụ duy nhất được tham chiếu.

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.

15
Michael Sander

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

Bằng cách sử dụng phương thức mở rộng, ba dòng trở thành một dòng:

MyType copy = source.Copy();
11
Stacked

Tôi đã đưa ra điều này để khắc phục .NET thiếu sót khi 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
Daniel Mošmondor

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 trình lược tả .

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 để tạo một bản sao sâu . Tốc độ của nó gần như tương tự 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 if 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 if 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 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 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à loại loại giá trị vì vậy bạn chỉ có thể sao chép nó và nội dung sẽ được sao chép (nhưng nó sẽ chỉ tạo bản sao nông trừ khi bạn sử dụng kỹ thuật trong bài này).
  • Nếu bạn có " class ", thì đó là 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 ra 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à 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ơ.

7
Contango

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.

7
xr280xr

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 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
kalisohn

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 đó.

7
HappyDude

Đâ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;
}
7
dougajmcdonald

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
JerryGoyal

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

5
LuckyLikey

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 với ICloneable và trình tạo 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ỏ chúng để hiểu rõ hơn.

5
Toxantron

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

CHỈNH SỬA: 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
Daniele D.

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
Sudhanva Kotabagi

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

  • Xác định ISelf<T> với thuộc tính Self chỉ đọc trả về TICloneable<out T>, xuất phát từ ISelf<T> và bao gồm một phương thức T Clone().
  • Sau đó, xác định loại CloneBase thực hiện protected virtual generic VirtualClone truyền MemberwiseClone thành loại truyền qua.
  • Mỗi loại dẫn xuất nên triển khai VirtualClone bằ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 là 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 đạo hàm có thể nhân bản của Foo hoạt động với đạo hàm 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.

4
supercat

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

} 
3
Jeroen Ritmeijer

Nếu Cây đối tượng của bạn có khả năng nối tiếp, bạn cũng có thể sử dụng cái gì đó như thế này

static public MyClass Clone(MyClass myClass)
{
    MyClass clone;
    XmlSerializer ser = new XmlSerializer(typeof(MyClass), _xmlAttributeOverrides);
    using (var ms = new MemoryStream())
    {
        ser.Serialize(ms, myClass);
        ms.Position = 0;
        clone = (MyClass)ser.Deserialize(ms);
    }
    return clone;
}

được thông báo rằng Giải pháp này khá dễ dàng nhưng nó không hiệu quả như các giải pháp khác.

Và hãy chắc chắn rằng nếu Class phát triển, vẫn sẽ chỉ có các trường được nhân bản, cũng được tuần tự hóa.

3
LuckyLikey

Để sao chép đối tượng lớp của bạn, bạn có thể sử dụng phương thức Object.MemberwiseClone,

chỉ cần thêm chức năng này vào lớp của bạn:

public class yourClass
{
    // ...
    // ...

    public yourClass DeepCopy()
    {
        yourClass othercopy = (yourClass)this.MemberwiseClone();
        return othercopy;
    }
}

sau đó để thực hiện một bản sao độc lập sâu, chỉ cần gọi phương thức DeepCopy:

yourClass newLine = oldLine.DeepCopy();

hi vọng điêu nay co ich.

3
Chtiwi Malek

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

mã đầy đủ kiểm tra 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

3
Roma Borodov

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 đã viết AnyClone được thực hiện hoàn toà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.

2
Michael Brown

Khi sử dụng Marc Gravells protobuf-net làm bộ nối tiếp của bạn, câu trả lời được chấp nhận cần một số sửa đổi nhỏ, vì đối tượng để sao chép sẽ không được quy cho [Serializable] và do đó, không được tuần tự hóa và phương thức Clone sẽ đưa ra một ngoại lệ.
[.__.] Tôi đã sửa đổi nó để hoạt động với protobuf-net:

public static T Clone<T>(this T source)
{
    if(Attribute.GetCustomAttribute(typeof(T), typeof(ProtoBuf.ProtoContractAttribute))
           == null)
    {
        throw new ArgumentException("Type has no ProtoContract!", "source");
    }

    if(Object.ReferenceEquals(source, null))
    {
        return default(T);
    }

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

Cái này kiểm tra sự hiện diện của thuộc tính [ProtoContract] và sử dụng bộ định dạng riêng của protobuf để tuần tự hóa đối tượng.

1
Basti M

Tiện ích mở rộng C # sẽ hỗ trợ cho các loại "không ISerializable ".

 public static class AppExtensions
 {                                                                      
       public static T DeepClone<T>(this T a)
       {
           using (var stream = new MemoryStream())
           {
               var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));

               serializer.Serialize(stream, a);
               stream.Position = 0;
               return (T)serializer.Deserialize(stream);
           }
       }                                                                    
 }

Sử dụng

       var obj2 = obj1.DeepClone()
1
Sameera R.

Thật không thể tin được bạn có thể dành bao nhiêu nỗ lực với giao diện IClonable - đặc biệt nếu bạn có hệ thống phân cấp hạng nặng. Ngoài ra MemberwiseClone hoạt động một cách kỳ lạ - nó không chính xác nhân bản ngay cả loại cấu trúc loại Danh sách bình thường.

Và tất nhiên, vấn đề nan giải thú vị nhất đối với việc xê-ri hóa là xê-ri hóa các tài liệu tham khảo trở lại - ví dụ: hệ thống phân cấp lớp nơi bạn có mối quan hệ cha-con. Tôi nghi ngờ rằng serializer nhị phân sẽ có thể giúp bạn trong trường hợp này. (Nó sẽ kết thúc với các vòng lặp đệ quy + tràn stack).

Tôi bằng cách nào đó thích giải pháp được đề xuất ở đây: Làm thế nào để bạn làm một bản sao sâu của một đối tượng trong .NET (cụ thể là C #)?

tuy nhiên - nó không hỗ trợ Danh sách, thêm hỗ trợ đó, cũng đã tính đến việc nuôi dạy lại. Đối với quy tắc chỉ dành cho cha mẹ mà tôi đã tạo trường hoặc thuộc tính đó phải được đặt tên là "cha mẹ", thì nó sẽ bị DeepClone bỏ qua. Bạn có thể muốn quyết định các quy tắc của riêng mình cho các tham chiếu ngược - đối với phân cấp cây, nó có thể là "trái/phải", v.v ...

Đây là toàn bộ đoạn mã bao gồm mã kiểm tra:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;

namespace TestDeepClone
{
    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            a.name = "main_A";
            a.b_list.Add(new B(a) { name = "b1" });
            a.b_list.Add(new B(a) { name = "b2" });

            A a2 = (A)a.DeepClone();
            a2.name = "second_A";

            // Perform re-parenting manually after deep copy.
            foreach( var b in a2.b_list )
                b.parent = a2;


            Debug.WriteLine("ok");

        }
    }

    public class A
    {
        public String name = "one";
        public List<String> list = new List<string>();
        public List<String> null_list;
        public List<B> b_list = new List<B>();
        private int private_pleaseCopyMeAsWell = 5;

        public override string ToString()
        {
            return "A(" + name + ")";
        }
    }

    public class B
    {
        public B() { }
        public B(A _parent) { parent = _parent; }
        public A parent;
        public String name = "two";
    }


    public static class ReflectionEx
    {
        public static Type GetUnderlyingType(this MemberInfo member)
        {
            Type type;
            switch (member.MemberType)
            {
                case MemberTypes.Field:
                    type = ((FieldInfo)member).FieldType;
                    break;
                case MemberTypes.Property:
                    type = ((PropertyInfo)member).PropertyType;
                    break;
                case MemberTypes.Event:
                    type = ((EventInfo)member).EventHandlerType;
                    break;
                default:
                    throw new ArgumentException("member must be if type FieldInfo, PropertyInfo or EventInfo", "member");
            }
            return Nullable.GetUnderlyingType(type) ?? type;
        }

        /// <summary>
        /// Gets fields and properties into one array.
        /// Order of properties / fields will be preserved in order of appearance in class / struct. (MetadataToken is used for sorting such cases)
        /// </summary>
        /// <param name="type">Type from which to get</param>
        /// <returns>array of fields and properties</returns>
        public static MemberInfo[] GetFieldsAndProperties(this Type type)
        {
            List<MemberInfo> fps = new List<MemberInfo>();
            fps.AddRange(type.GetFields());
            fps.AddRange(type.GetProperties());
            fps = fps.OrderBy(x => x.MetadataToken).ToList();
            return fps.ToArray();
        }

        public static object GetValue(this MemberInfo member, object target)
        {
            if (member is PropertyInfo)
            {
                return (member as PropertyInfo).GetValue(target, null);
            }
            else if (member is FieldInfo)
            {
                return (member as FieldInfo).GetValue(target);
            }
            else
            {
                throw new Exception("member must be either PropertyInfo or FieldInfo");
            }
        }

        public static void SetValue(this MemberInfo member, object target, object value)
        {
            if (member is PropertyInfo)
            {
                (member as PropertyInfo).SetValue(target, value, null);
            }
            else if (member is FieldInfo)
            {
                (member as FieldInfo).SetValue(target, value);
            }
            else
            {
                throw new Exception("destinationMember must be either PropertyInfo or FieldInfo");
            }
        }

        /// <summary>
        /// Deep clones specific object.
        /// Analogue can be found here: https://stackoverflow.com/questions/129389/how-do-you-do-a-deep-copy-an-object-in-net-c-specifically
        /// This is now improved version (list support added)
        /// </summary>
        /// <param name="obj">object to be cloned</param>
        /// <returns>full copy of object.</returns>
        public static object DeepClone(this object obj)
        {
            if (obj == null)
                return null;

            Type type = obj.GetType();

            if (obj is IList)
            {
                IList list = ((IList)obj);
                IList newlist = (IList)Activator.CreateInstance(obj.GetType(), list.Count);

                foreach (object elem in list)
                    newlist.Add(DeepClone(elem));

                return newlist;
            } //if

            if (type.IsValueType || type == typeof(string))
            {
                return obj;
            }
            else if (type.IsArray)
            {
                Type elementType = Type.GetType(type.FullName.Replace("[]", string.Empty));
                var array = obj as Array;
                Array copied = Array.CreateInstance(elementType, array.Length);

                for (int i = 0; i < array.Length; i++)
                    copied.SetValue(DeepClone(array.GetValue(i)), i);

                return Convert.ChangeType(copied, obj.GetType());
            }
            else if (type.IsClass)
            {
                object toret = Activator.CreateInstance(obj.GetType());

                MemberInfo[] fields = type.GetFieldsAndProperties();
                foreach (MemberInfo field in fields)
                {
                    // Don't clone parent back-reference classes. (Using special kind of naming 'parent' 
                    // to indicate child's parent class.
                    if (field.Name == "parent")
                    {
                        continue;
                    }

                    object fieldValue = field.GetValue(obj);

                    if (fieldValue == null)
                        continue;

                    field.SetValue(toret, DeepClone(fieldValue));
                }

                return toret;
            }
            else
            {
                // Don't know that type, don't know how to clone it.
                if (Debugger.IsAttached)
                    Debugger.Break();

                return null;
            }
        } //DeepClone
    }

}
1
TarmoPikaro

Một câu trả lời JSON.NET khác. Phiên bản này hoạt động với các lớp không triển khai ISerializable.

public static class Cloner
{
    public static T Clone<T>(T source)
    {
        if (ReferenceEquals(source, null))
            return default(T);

        var settings = new JsonSerializerSettings { ContractResolver = new ContractResolver() };

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

    class ContractResolver : DefaultContractResolver
    {
        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                .Select(p => base.CreateProperty(p, memberSerialization))
                .Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                    .Select(f => base.CreateProperty(f, memberSerialization)))
                .ToList();
            props.ForEach(p => { p.Writable = true; p.Readable = true; });
            return props;
        }
    }
}
1
Matthew Watson

Một người vẽ bản đồ thực hiện một bản sao sâu. Foreach thành viên của đối tượng bạn tạo ra một đối tượng mới và gán tất cả các giá trị của nó. Nó hoạt động đệ quy trên mỗi thành viên bên trong không nguyên thủy.

Tôi đề nghị bạn một trong những người nhanh nhất, hiện đang tích cực phát triển. Tôi đề nghị UltraMapper https://github.com/maurosampietro/UltraMapper

Các gói Nuget: https://www.nuget.org/packages/UltraMapper/

1
Mauro Sampietro

Nhân bản sâu là về sao chép trạng thái. Với .netbang có nghĩa là các trường.

Hãy nói rằng một người có một hệ thống phân cấp:

static class RandomHelper
{
    private static readonly Random random = new Random();

    public static int Next(int maxValue) => random.Next(maxValue);
}

class A
{
    private readonly int random = RandomHelper.Next(100);

    public override string ToString() => $"{typeof(A).Name}.{nameof(random)} = {random}";
}

class B : A
{
    private readonly int random = RandomHelper.Next(100);

    public override string ToString() => $"{typeof(B).Name}.{nameof(random)} = {random} {base.ToString()}";
}

class C : B
{
    private readonly int random = RandomHelper.Next(100);

    public override string ToString() => $"{typeof(C).Name}.{nameof(random)} = {random} {base.ToString()}";
}

Nhân bản có thể được thực hiện:

static class DeepCloneExtension
{
    // consider instance fields, both public and non-public
    private static readonly BindingFlags bindingFlags =
        BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

    public static T DeepClone<T>(this T obj) where T : new()
    {
        var type = obj.GetType();
        var result = (T)Activator.CreateInstance(type);

        do
            // copy all fields
            foreach (var field in type.GetFields(bindingFlags))
                field.SetValue(result, field.GetValue(obj));
        // for every level of hierarchy
        while ((type = type.BaseType) != typeof(object));

        return result;
    }
}

Demo1:

Console.WriteLine(new C());
Console.WriteLine(new C());

var c = new C();
Console.WriteLine($"{Environment.NewLine}Image: {c}{Environment.NewLine}");

Console.WriteLine(new C());
Console.WriteLine(new C());

Console.WriteLine($"{Environment.NewLine}Clone: {c.DeepClone()}{Environment.NewLine}");

Console.WriteLine(new C());
Console.WriteLine(new C());

Kết quả:

C.random = 92 B.random = 66 A.random = 71
C.random = 36 B.random = 64 A.random = 17

Image: C.random = 96 B.random = 18 A.random = 46

C.random = 60 B.random = 7 A.random = 37
C.random = 78 B.random = 11 A.random = 18

Clone: C.random = 96 B.random = 18 A.random = 46

C.random = 33 B.random = 63 A.random = 38
C.random = 4 B.random = 5 A.random = 79

Lưu ý, tất cả các đối tượng mới đều có các giá trị ngẫu nhiên cho trường random, nhưng clone khớp chính xác với image

Demo2:

class D
{
    public event EventHandler Event;
    public void RaiseEvent() => Event?.Invoke(this, EventArgs.Empty);
}

// ...

var image = new D();
Console.WriteLine($"Created obj #{image.GetHashCode()}");

image.Event += (sender, e) => Console.WriteLine($"Event from obj #{sender.GetHashCode()}");
Console.WriteLine($"Subscribed to event of obj #{image.GetHashCode()}");

image.RaiseEvent();
image.RaiseEvent();

var clone = image.DeepClone();
Console.WriteLine($"obj #{image.GetHashCode()} cloned to obj #{clone.GetHashCode()}");

clone.RaiseEvent();
image.RaiseEvent();

Kết quả:

Created obj #46104728
Subscribed to event of obj #46104728
Event from obj #46104728
Event from obj #46104728
obj #46104728 cloned to obj #12289376
Event from obj #12289376
Event from obj #46104728

Lưu ý, trường sao lưu sự kiện cũng được sao chép và khách hàng cũng được đăng ký để nhân bản sự kiện.

1
Ted Mucuzany

Các cách tiếp cận chung đều có giá trị về mặt kỹ thuật, nhưng tôi chỉ muốn thêm một ghi chú từ chính mình vì chúng tôi hiếm khi thực sự cần một bản sao sâu thực sự và tôi sẽ phản đối mạnh mẽ việc sử dụng sao chép sâu chung trong các ứng dụng kinh doanh thực tế vì điều đó khiến bạn có thể có nhiều những nơi mà các đối tượng được sao chép và sau đó được sửa đổi một cách rõ ràng, rất dễ bị lạc.

Trong hầu hết các tình huống thực tế, bạn cũng muốn có quyền kiểm soát chi tiết nhất đối với quá trình sao chép vì bạn không chỉ được kết hợp với khung truy cập dữ liệu mà còn trong thực tế, các đối tượng kinh doanh được sao chép hiếm khi giống nhau 100%. Hãy suy nghĩ một tham chiếu ví dụ mà ORM sử dụng để xác định các tham chiếu đối tượng, một bản sao sâu đầy đủ cũng sẽ sao chép id này vì vậy trong khi trong bộ nhớ, các đối tượng sẽ khác, ngay khi bạn gửi nó đến kho dữ liệu, nó sẽ phàn nàn, vì vậy bạn sẽ phàn nàn phải sửa đổi các thuộc tính này bằng tay sau khi sao chép và nếu đối tượng thay đổi, bạn cần điều chỉnh nó ở tất cả các vị trí sử dụng sao chép sâu chung chung.

Mở rộng câu trả lời @cregox với IClonizable, thực sự đâu là bản sao sâu? Nó chỉ là một đối tượng mới được phân bổ trên heap giống hệt với đối tượng ban đầu nhưng chiếm một không gian bộ nhớ khác, như vậy thay vì sử dụng chức năng cloner chung tại sao không chỉ tạo một đối tượng mới?

Cá nhân tôi sử dụng ý tưởng về các phương thức tĩnh của nhà máy trên các đối tượng miền của mình.

Thí dụ:

    public class Client
    {
        public string Name { get; set; }

        protected Client()
        {
        }

        public static Client Clone(Client copiedClient)
        {
            return new Client
            {
                Name = copiedClient.Name
            };
        }
    }

    public class Shop
    {
        public string Name { get; set; }

        public string Address { get; set; }

        public ICollection<Client> Clients { get; set; }

        public static Shop Clone(Shop copiedShop, string newAddress, ICollection<Client> clients)
        {
            var copiedClients = new List<Client>();
            foreach (var client in copiedShop.Clients)
            {
                copiedClients.Add(Client.Clone(client));
            }

            return new Shop
            {
                Name = copiedShop.Name,
                Address = newAddress,
                Clients = copiedClients
            };
        }
    }

Nếu ai đó đang tìm cách làm thế nào anh ta có thể cấu trúc việc khởi tạo đối tượng trong khi vẫn duy trì toàn quyền kiểm soát quá trình sao chép thì đó là một giải pháp mà cá nhân tôi đã rất thành công. Các nhà xây dựng được bảo vệ cũng làm cho nó như vậy, các nhà phát triển khác buộc phải sử dụng các phương thức xuất xưởng, đưa ra một điểm khởi tạo đối tượng gọn gàng, gói gọn logic xây dựng bên trong đối tượng. Bạn cũng có thể quá tải phương thức và có một số logic nhân bản cho các vị trí khác nhau nếu cần thiết.

0
Piotr Jerzy Mamenas

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

BinaryFormatter yêu cầu thuộc tính Serializable, JsonConverter yêu cầu một hàm tạo hoặc tham số không tham số, không xử lý tốt các trường chỉ đọc hoặc giao diện và cả hai đều chậm hơn 10-30x 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 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 gói ObjectCloner và sử dụng gói đó như sau:

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, hãy lấy ObjectCloner.Extensions 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 gấp 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 BinaryFormatter được đề xuất cao.

0
Marcell Toth

Sử dụng System.Text.Json:

https://devbloss.Microsoft.com/dotnet/try-the-new-system-text-json-apis/

public static T DeepCopy<T>(this T source)
{
    return source == null ? default : JsonSerializer.Parse<T>(JsonSerializer.ToString(source));
}

API mới đang sử dụng Span<T>. Điều này sẽ được nhanh chóng, sẽ được tốt đẹp để làm một số điểm chuẩn.

Lưu ý: không cần ObjectCreationHandling.Replace như trong Json.NET vì nó sẽ thay thế các giá trị bộ sưu tập theo mặc định. Bây giờ bạn nên quên Json.NET vì mọi thứ sẽ được thay thế bằng API chính thức mới.

Tôi không chắc chắn điều này sẽ làm việc với các lĩnh vực tư nhân.

0
Konrad

Tôi tìm thấy một cách mới để làm điều đó là Emit.

Chúng ta có thể sử dụng Emit để thêm IL vào ứng dụng và chạy nó. Nhưng tôi không nghĩ rằng đó là một cách tốt để tôi muốn hoàn thiện điều này mà tôi viết câu trả lời của mình.

Emit có thể xem tài liệu chính thứcHướng dẫn

Bạn nên học một số IL để đọc mã. Tôi sẽ viết mã có thể sao chép tài sản trong lớp.

public static class Clone
{        
    // ReSharper disable once InconsistentNaming
    public static void CloneObjectWithIL<T>(T source, T los)
    {
        //see http://lindexi.oschina.io/lindexi/post/C-%E4%BD%BF%E7%94%A8Emit%E6%B7%B1%E5%85%8B%E9%9A%86/
        if (CachedIl.ContainsKey(typeof(T)))
        {
            ((Action<T, T>) CachedIl[typeof(T)])(source, los);
            return;
        }
        var dynamicMethod = new DynamicMethod("Clone", null, new[] { typeof(T), typeof(T) });
        ILGenerator generator = dynamicMethod.GetILGenerator();

        foreach (var temp in typeof(T).GetProperties().Where(temp => temp.CanRead && temp.CanWrite))
        {
            //do not copy static that will except
            if (temp.GetAccessors(true)[0].IsStatic)
            {
                continue;
            }

            generator.Emit(OpCodes.Ldarg_1);// los
            generator.Emit(OpCodes.Ldarg_0);// s
            generator.Emit(OpCodes.Callvirt, temp.GetMethod);
            generator.Emit(OpCodes.Callvirt, temp.SetMethod);
        }
        generator.Emit(OpCodes.Ret);
        var clone = (Action<T, T>) dynamicMethod.CreateDelegate(typeof(Action<T, T>));
        CachedIl[typeof(T)] = clone;
        clone(source, los);
    }

    private static Dictionary<Type, Delegate> CachedIl { set; get; } = new Dictionary<Type, Delegate>();
}

Mã có thể được sao chép sâu nhưng nó có thể sao chép tài sản. Nếu bạn muốn làm cho nó thành bản sao sâu mà bạn có thể thay đổi nó cho IL thì quá khó mà tôi không thể làm được.

0
lindexi

Gói Nuget nhanh chóng, dễ dàng, hiệu quả để giải quyết Nhân bản

Sau khi đọc tất cả các câu trả lời tôi đã ngạc nhiên không ai đề cập đến gói tuyệt vời này:

https://github.com/force-net/DeepCloner

Xây dựng một chút về readme của nó, đây là lý do tại sao chúng tôi chọn nó tại nơi làm việc:

Tuyên bố miễn trừ trách nhiệm - yêu cầu:

  • . NET 4.0 trở lên hoặc .NET Standard 1.3 (.NET Core)
  • Yêu cầu bộ quyền tin cậy đầy đủ hoặc quyền phản chiếu (MemberAccess)
  • Nó có thể sao chép sâu hoặc nông
  • Trong nhân bản sâu tất cả các đồ thị đối tượng được duy trì.
  • Sử dụng tạo mã trong thời gian chạy, vì kết quả nhân bản rất nhanh
  • Các đối tượng được sao chép bởi cấu trúc bên trong, không có phương thức hoặc các hàm được gọi
  • Bạn không cần phải đánh dấu các lớp bằng cách nào đó (như thuộc tính Nối tiếp hoặc thực hiện giao diện)
  • Không có yêu cầu chỉ định loại đối tượng để nhân bản. Đối tượng có thể được truyền tới giao diện hoặc dưới dạng đối tượng trừu tượng (ví dụ: bạn có thể sao chép mảng ints dưới dạng Array trừu tượng hoặc IEnumerable; thậm chí null có thể được sao chép mà không có bất kỳ lỗi nào)
  • Đối tượng nhân bản không có bất kỳ khả năng nào để xác định rằng anh ta là bản sao (ngoại trừ với các phương thức rất cụ thể)

Cách sử dụng rất dễ dàng:

  var deepClone = new { Id = 1, Name = "222" }.DeepClone();
  var shallowClone = new { Id = 1, Name = "222" }.ShallowClone();
0
alexlomba87

làm thế nào về việc chỉ đọc lại bên trong một phương thức nên gọi về cơ bản là một hàm tạo sao chép tự động

T t = new T();
T t2 = (T)t;  //eh something like that

        List<myclass> cloneum;
        public void SomeFuncB(ref List<myclass> _mylist)
        {
            cloneum = new List<myclass>();
            cloneum = (List < myclass >) _mylist;
            cloneum.Add(new myclass(3));
            _mylist = new List<myclass>();
        }

dường như làm việc với tôi

0
will_m

Điều này sẽ sao chép tất cả các thuộc tính có thể đọc và ghi của đối tượng sang đối tượng khác.

 public class PropertyCopy<TSource, TTarget> 
                        where TSource: class, new()
                        where TTarget: class, new()
        {
            public static TTarget Copy(TSource src, TTarget trg, params string[] properties)
            {
                if (src==null) return trg;
                if (trg == null) trg = new TTarget();
                var fulllist = src.GetType().GetProperties().Where(c => c.CanWrite && c.CanRead).ToList();
                if (properties != null && properties.Count() > 0)
                    fulllist = fulllist.Where(c => properties.Contains(c.Name)).ToList();
                if (fulllist == null || fulllist.Count() == 0) return trg;

                fulllist.ForEach(c =>
                    {
                        c.SetValue(trg, c.GetValue(src));
                    });

                return trg;
            }
        }

và đây là cách bạn sử dụng nó:

 var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave,
                                                            "Creation",
                                                            "Description",
                                                            "IdTicketStatus",
                                                            "IdUserCreated",
                                                            "IdUserInCharge",
                                                            "IdUserRequested",
                                                            "IsUniqueTicketGenerated",
                                                            "LastEdit",
                                                            "Subject",
                                                            "UniqeTicketRequestId",
                                                            "Visibility");

hoặc sao chép mọi thứ:

var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave);
0
Ylli Prifti