Tại sao không có IClonizable <T>?


222

Có một lý do cụ thể tại sao một cái chung ICloneable<T>không tồn tại?

Sẽ thoải mái hơn nhiều, nếu tôi không cần phải sử dụng nó mỗi khi tôi sao chép thứ gì đó.


6
Không! với tất cả sự tôn trọng với 'lý do', tôi đồng ý với bạn, họ nên thực hiện nó!
Shimmy Weitzhandler

Nó sẽ là một điều tốt đẹp cho Microsoft đã xác định (vấn đề với các giao diện riêng của bạn là các giao diện trong hai cụm khác nhau sẽ không tương thích, ngay cả khi chúng giống hệt nhau về mặt ngữ nghĩa). Nếu tôi thiết kế giao diện, nó sẽ có ba thành viên là Clone, Self và Clone IfMutable, tất cả sẽ trả về T (thành viên cuối cùng sẽ trả về Clone hoặc Self, nếu phù hợp). Thành viên Self sẽ có thể chấp nhận IClonizable (của Foo) làm tham số và sau đó sử dụng nó làm Foo mà không cần phải đánh máy.
supercat

Điều đó sẽ cho phép một hệ thống phân cấp lớp nhân bản thích hợp, trong đó các lớp kế thừa phơi bày một phương thức "nhân bản" được bảo vệ và có các dẫn xuất niêm phong phơi bày ra một công khai. Ví dụ: người ta có thể có Pile, ClonizablePile: Pile, EnhifiedPile: Pile và ClonizableEnhifiedPile: EnhizedPile, không ai trong số đó sẽ bị phá vỡ nếu được nhân bản (mặc dù không phải tất cả đều phơi bày một phương thức nhân bản công khai), và MoreEnhifiedPile: nếu được nhân bản, nhưng không phơi bày bất kỳ phương pháp nhân bản nào). Một thói quen chấp nhận IClonizable (của Pile) có thể chấp nhận ClonizablePile hoặc ClonizableEnhifiedPile ...
supercat

... mặc dù ClonizableEnhifiedPile không kế thừa từ ClonizablePile. Lưu ý rằng nếu EnhifiedPile được kế thừa từ ClonizablePile, thì MoreEnhifiedPile sẽ phải đưa ra một phương thức nhân bản công khai và có thể được chuyển sang mã dự kiến ​​sẽ sao chép nó, vi phạm Nguyên tắc thay thế Liskov. Vì ClonizableEnhifiedPile sẽ triển khai IClonizable (Of EnhifiedPile) và bằng hàm ý IClonizable (Of Pile), nên nó có thể được chuyển sang một thói quen mong đợi một dẫn xuất có thể nhân bản của Pile.
supercat

Câu trả lời:


111

IClonizable hiện được coi là một API xấu, vì nó không xác định kết quả là bản sao sâu hay nông. Tôi nghĩ đây là lý do tại sao họ không cải thiện giao diện này.

Bạn có thể có thể thực hiện một phương thức mở rộng nhân bản được gõ, nhưng tôi nghĩ rằng nó sẽ yêu cầu một tên khác vì các phương thức mở rộng có ít ưu tiên hơn so với phương thức ban đầu.


8
Tôi không đồng ý, xấu hay không xấu, đôi khi nó rất hữu ích cho việc sao chép SHALLOW, và trong những trường hợp đó thực sự cần Clone <T>, để cứu một quyền anh không cần thiết và bỏ hộp.
Shimmy Weitzhandler

2
@AndreyShchekin: Điều gì không rõ ràng về nhân bản sâu và nông? Nếu List<T>có một phương thức nhân bản, tôi sẽ hy vọng nó mang lại một List<T>mục có danh tính giống như trong danh sách gốc, nhưng tôi hy vọng rằng mọi cấu trúc dữ liệu nội bộ sẽ được sao chép khi cần để đảm bảo rằng không có gì được thực hiện cho một danh sách sẽ ảnh hưởng các sắc các mặt hàng lưu trữ trong người kia. Sự mơ hồ ở đâu? Một vấn đề lớn hơn với nhân bản đi kèm với một biến thể của "vấn đề kim cương": nếu CloneableFoothừa hưởng từ [không thể nhân bản công khai] Foo, nên CloneableDerivedFooxuất phát từ ...
supercat

1
@supercat: Tôi không thể nói rằng tôi hiểu đầy đủ về kỳ vọng của bạn identity, ví dụ như bạn sẽ xem xét một danh sách nào trong danh sách (trong trường hợp danh sách các danh sách)? Tuy nhiên, bỏ qua điều đó, kỳ vọng của bạn không phải là ý tưởng khả thi duy nhất mọi người có thể có khi gọi điện hoặc thực hiện Clone. Điều gì xảy ra nếu các tác giả thư viện thực hiện một số danh sách khác không tuân theo mong đợi của bạn? API nên rõ ràng không rõ ràng, không thể tranh cãi rõ ràng.
Andrey Shchekin

2
@supercat Vậy bạn đang nói về bản sao nông. Tuy nhiên, về cơ bản không có gì sai với một bản sao sâu, ví dụ nếu bạn muốn thực hiện một giao dịch đảo ngược trên các đối tượng trong bộ nhớ. Kỳ vọng của bạn dựa trên trường hợp sử dụng của bạn, những người khác có thể có những kỳ vọng khác nhau. Nếu họ đặt đối tượng của bạn vào đối tượng của họ (hoặc ngược lại), kết quả sẽ bất ngờ.
Andrey Shchekin

5
@supercat bạn không tính đến nó là một phần của .NET framework, không phải là một phần của ứng dụng của bạn. Vì vậy, nếu bạn tạo một lớp không nhân bản sâu, thì người khác tạo một lớp thực hiện nhân bản sâu và gọi Clonetất cả các phần của nó, nó sẽ không hoạt động có thể dự đoán được - tùy thuộc vào việc phần đó được thực hiện bởi bạn hay người đó ai thích nhân bản sâu. quan điểm của bạn về các mẫu là hợp lệ nhưng có IMHO trong API là không đủ rõ ràng - nên được gọi ShallowCopyđể nhấn mạnh điểm hoặc hoàn toàn không được cung cấp.
Andrey Shchekin

141

Ngoài trả lời của Andrey (mà tôi đồng ý với, +1) - khi ICloneable được thực hiện, bạn cũng có thể chọn thực hiện rõ ràng để làm cho công chúng Clone()trở lại một đối tượng đánh máy:

public Foo Clone() { /* your code */ }
object ICloneable.Clone() {return Clone();}

Tất nhiên có một vấn đề thứ hai với sự ICloneable<T>thừa kế chung .

Nếu tôi có:

public class Foo {}
public class Bar : Foo {}

Và tôi thực hiện ICloneable<T>, sau đó tôi thực hiện ICloneable<Foo>? ICloneable<Bar>? Bạn nhanh chóng bắt đầu thực hiện rất nhiều giao diện giống hệt nhau ... So sánh với một diễn viên ... và nó có thực sự tệ đến vậy không?


10
+1 Tôi luôn nghĩ rằng vấn đề hiệp phương sai là lý do thực sự
Tamas Czinege

Có một vấn đề với điều này: việc triển khai giao diện rõ ràng phải ở chế độ riêng tư , điều này sẽ gây ra vấn đề nếu một lúc nào đó Foo cần phải chuyển sang IClonizable ... Tôi có thiếu điều gì ở đây không?
Joel ở Gô-lô

3
Sai lầm của tôi: hóa ra, mặc dù được định nghĩa là riêng tư, phương thức sẽ được gọi là Foo nên được chuyển thành IClonable (CLR qua C # 2nd Ed, tr.319). Có vẻ như một quyết định thiết kế kỳ lạ, nhưng nó có. Vì vậy, Foo.Clone () đưa ra phương thức đầu tiên và ((IClonizable) Foo) .Clone () đưa ra phương thức thứ hai.
Joel ở Gô-lô

Trên thực tế, việc triển khai giao diện rõ ràng là, một phần của API công khai. Trong đó họ có thể gọi công khai thông qua đúc.
Marc Gravell

8
Giải pháp đề xuất ban đầu có vẻ tuyệt vời ... cho đến khi bạn nhận ra rằng trong hệ thống phân cấp lớp, bạn muốn 'công khai Foo Clone ()' là ảo và được ghi đè trong các lớp dẫn xuất để chúng có thể trả về kiểu của riêng chúng (và sao chép các trường của riêng chúng khóa học). Đáng buồn thay, bạn không thể thay đổi loại trả về trong một ghi đè (vấn đề hiệp phương sai được đề cập). Vì vậy, bạn quay lại vòng tròn đầy đủ trở lại vấn đề ban đầu - việc triển khai rõ ràng không thực sự mua cho bạn nhiều (ngoài việc trả về loại cơ sở của hệ thống phân cấp của bạn thay vì 'đối tượng') và bạn quay lại truyền hầu hết Kết quả cuộc gọi Clone (). Kinh quá!
Ken Beckett

18

Tôi cần hỏi, chính xác bạn sẽ làm gì với giao diện ngoài việc thực hiện nó? Các giao diện thường chỉ hữu ích khi bạn sử dụng nó (tức là lớp này hỗ trợ 'IBar') hoặc có các tham số hoặc setters lấy nó (tức là tôi lấy 'IBar'). Với IClonizable - chúng tôi đã đi qua toàn bộ Khung và không tìm thấy một cách sử dụng nào ở bất kỳ nơi nào khác ngoài việc triển khai nó. Chúng tôi cũng không thể tìm thấy bất kỳ việc sử dụng nào trong 'thế giới thực', cũng thực hiện một số thứ khác ngoài việc thực hiện nó (trong ~ 60.000 ứng dụng mà chúng tôi có quyền truy cập).

Bây giờ nếu bạn muốn thực thi một mẫu mà bạn muốn các đối tượng 'nhân bản' của mình thực hiện, thì đó là một cách sử dụng hoàn toàn tốt - và tiếp tục. Bạn cũng có thể quyết định chính xác "nhân bản" nghĩa là gì đối với bạn (tức là sâu hay nông). Tuy nhiên, trong trường hợp đó, không cần chúng tôi (BCL) xác định nó. Chúng tôi chỉ định nghĩa các trừu tượng trong BCL khi có nhu cầu trao đổi các thể hiện được gõ là sự trừu tượng giữa các thư viện không liên quan.

David Kean (Đội BCL)


1
IClonizable không chung chung không hữu ích lắm, nhưng ICloneable<out T>có thể khá hữu ích nếu nó được kế thừa từ ISelf<out T>, với một phương thức Selfloại duy nhất T. Người ta thường không cần "thứ gì đó có thể nhân bản", nhưng người ta rất có thể cần một thứ Tcó thể nhân bản. Nếu một đối tượng có thể nhân bản thực hiện ISelf<itsOwnType>, một thói quen cần một bản Tsao có thể nhân bản có thể chấp nhận một tham số loại ICloneable<T>, ngay cả khi không phải tất cả các dẫn xuất có thể sao chép của Tchung một tổ tiên.
supercat

Cảm ơn. Điều đó tương tự như tình huống diễn viên tôi đã đề cập ở trên (ví dụ: 'lớp này có hỗ trợ' IBar ') không. Đáng buồn thay, chúng tôi chỉ đưa ra các kịch bản rất hạn chế và bị cô lập, nơi bạn thực sự sẽ sử dụng thực tế là T có thể nhân bản. Bạn có tình huống trong tâm trí?
David Kean

Một điều đôi khi khó xử trong .net là quản lý các bộ sưu tập có thể thay đổi khi nội dung của bộ sưu tập được coi là một giá trị (tức là tôi sẽ muốn biết sau đó là bộ các mục trong bộ sưu tập bây giờ). Một cái gì đó giống như ICloneable<T>có thể hữu ích cho điều đó, mặc dù một khung rộng hơn để duy trì các lớp có thể thay đổi và bất biến song song có thể hữu ích hơn. Nói cách khác, mã cần xem một số loại Foochứa nhưng sẽ không làm thay đổi nó và cũng không hy vọng rằng nó sẽ không bao giờ thay đổi có thể sử dụng một IReadableFoo, trong khi ...
supercat

... mã muốn giữ nội dung Foocó thể sử dụng ImmutableFoomã while muốn thao tác nó có thể sử dụng mã MutableFoo. Mã được cung cấp bất kỳ loại nào IReadableFoosẽ có thể có được một phiên bản có thể thay đổi hoặc bất biến. Một khung như vậy sẽ rất tốt, nhưng thật không may, tôi không thể tìm thấy bất kỳ cách hay nào để sắp đặt mọi thứ theo cách chung chung. Nếu có một cách nhất quán để tạo một trình bao bọc chỉ đọc cho một lớp, thì một thứ như vậy có thể được sử dụng kết hợp với ICloneable<T>để tạo một bản sao bất biến của một lớp chứa T'.
supercat

@supercat Nếu bạn muốn sao chép a List<T>, sao cho nhân bản List<T>là một bộ sưu tập mới giữ con trỏ cho tất cả các đối tượng giống nhau trong bộ sưu tập ban đầu, có hai cách dễ dàng để làm điều đó mà không cần ICloneable<T>. Đầu tiên là Enumerable.ToList()phương thức mở rộng: List<foo> clone = original.ToList();Thứ hai là hàm List<T>tạo IEnumerable<T>: List<foo> clone = new List<foo>(original);Tôi nghi ngờ phương thức mở rộng có thể chỉ gọi hàm tạo, nhưng cả hai sẽ làm những gì bạn yêu cầu. ;)
CptRulk

12

Tôi nghĩ rằng câu hỏi "tại sao" là không cần thiết. Có rất nhiều giao diện / lớp / vv ... rất hữu ích, nhưng không phải là một phần của thư viện cơ sở .NET Frameworku.

Nhưng, chủ yếu bạn có thể tự làm điều đó.

public interface ICloneable<T> : ICloneable {
    new T Clone();
}

public abstract class CloneableBase<T> : ICloneable<T> where T : CloneableBase<T> {
    public abstract T Clone();
    object ICloneable.Clone() { return this.Clone(); }
}

public abstract class CloneableExBase<T> : CloneableBase<T> where T : CloneableExBase<T> {
    protected abstract T CreateClone();
    protected abstract void FillClone( T clone );
    public override T Clone() {
        T clone = this.CreateClone();
        if ( object.ReferenceEquals( clone, null ) ) { throw new NullReferenceException( "Clone was not created." ); }
        return clone
    }
}

public abstract class PersonBase<T> : CloneableExBase<T> where T : PersonBase<T> {
    public string Name { get; set; }

    protected override void FillClone( T clone ) {
        clone.Name = this.Name;
    }
}

public sealed class Person : PersonBase<Person> {
    protected override Person CreateClone() { return new Person(); }
}

public abstract class EmployeeBase<T> : PersonBase<T> where T : EmployeeBase<T> {
    public string Department { get; set; }

    protected override void FillClone( T clone ) {
        base.FillClone( clone );
        clone.Department = this.Department;
    }
}

public sealed class Employee : EmployeeBase<Employee> {
    protected override Employee CreateClone() { return new Employee(); }
}

Bất kỳ phương thức nhân bản khả thi nào cho một lớp kế thừa đều phải sử dụng Object.MemberwiseClone làm điểm bắt đầu (hoặc nếu không sử dụng sự phản chiếu) vì nếu không thì không có gì đảm bảo rằng bản sao sẽ cùng loại với đối tượng ban đầu. Tôi có một mẫu khá đẹp nếu bạn quan tâm.
supercat

@supercat tại sao không làm cho nó một câu trả lời sau đó?
nawfal

"Có rất nhiều giao diện / lớp / vv ... rất hữu ích, nhưng không phải là một phần của thư viện cơ sở .NET Frameworku." Bạn có thể cho chúng tôi ví dụ?
Krythic

1
@Krythic tức là: các cơ sở dữ liệu nâng cao như cây b, cây đỏ đen, vòng tròn đệm. Trong '09 không có bộ dữ liệu, bộ sưu tập đồng thời yếu, tham chiếu yếu ...
TcKs

10

Thật dễ dàng để tự viết giao diện nếu bạn cần:

public interface ICloneable<T> : ICloneable
        where T : ICloneable<T>
{
    new T Clone();
}

Tôi đã bao gồm một đoạn trích vì danh dự của bản quyền, liên kết đã bị phá vỡ ...
Shimmy Weitzhandler

2
Và chính xác thì giao diện đó hoạt động như thế nào? Để kết quả của một hoạt động nhân bản có thể được chuyển sang loại T, đối tượng được nhân bản phải xuất phát từ T. Không có cách nào để thiết lập một ràng buộc kiểu như vậy. Nói một cách thực tế, điều duy nhất tôi có thể thấy kết quả của việc trả lại iClonizable là một loại iClonizable.
supercat

@supercat: vâng, đó chính xác là những gì Clone () trả về: một T thực hiện IClonizable <T>. MbUnit đã sử dụng giao diện này trong nhiều năm , vì vậy, nó hoạt động. Về phần triển khai, hãy xem qua nguồn của MbUnit.
Mauricio Scheffer

2
@Maurermo Scheffer: Tôi thấy những gì đang diễn ra. Không có loại nào thực sự thực hiện iClonizable (của T). Thay vào đó, mỗi loại thực hiện iClonizable (của chính nó). Điều đó sẽ làm việc, tôi đoán, mặc dù tất cả những gì nó làm là di chuyển một kiểu chữ. Thật tệ khi không có cách nào để tuyên bố một trường là có một ràng buộc kế thừa và một giao diện; Sẽ rất hữu ích khi có một phương thức nhân bản trả về một đối tượng của kiểu cơ sở bán vô tính (chỉ nhân bản được hiển thị dưới dạng phương thức được bảo vệ), nhưng với một ràng buộc kiểu có thể nhân bản. Điều đó sẽ cho phép một đối tượng chấp nhận các dẫn xuất có thể nhân bản của các dẫn xuất bán vô tính của một loại cơ sở.
supercat

@Maurermo Scheffer: BTW, tôi muốn đề xuất rằng IClonizable (của T) cũng hỗ trợ thuộc tính Self chỉ đọc (thuộc loại trả về T). Điều này sẽ cho phép một phương thức chấp nhận IClonizable (của Foo) và sử dụng nó (thông qua thuộc tính Self) làm Foo.
supercat

9

Đã đọc gần đây bài viết Tại sao Sao chép một đối tượng là một điều khủng khiếp để làm? , Tôi nghĩ rằng câu hỏi này cần thêm thông tin. Các câu trả lời khác ở đây cung cấp lời khuyên tốt, nhưng câu trả lời vẫn chưa hoàn thành - tại sao không ICloneable<T>?

  1. Sử dụng

    Vì vậy, bạn có một lớp thực hiện nó. Mặc dù trước đây bạn có một phương thức muốn ICloneable, nhưng bây giờ nó phải chung chung để chấp nhận ICloneable<T>. Bạn sẽ cần phải chỉnh sửa nó.

    Sau đó, bạn có thể có một phương thức kiểm tra nếu một đối tượng is ICloneable . Gì bây giờ? Bạn không thể làm is ICloneable<>và vì bạn không biết loại đối tượng ở kiểu biên dịch, bạn không thể tạo phương thức chung. Vấn đề thực sự đầu tiên.

    Vì vậy, bạn cần phải có cả hai ICloneable<T>ICloneable, cái trước thực hiện cái sau. Do đó, một người triển khai sẽ cần phải thực hiện cả hai phương thức - object Clone()T Clone() . Không, cảm ơn, chúng tôi đã có đủ niềm vui với IEnumerable.

    Như đã chỉ ra, đó cũng là sự phức tạp của thừa kế. Mặc dù hiệp phương sai dường như có thể giải quyết vấn đề này, một kiểu dẫn xuất cần thực hiện ICloneable<T>kiểu riêng của nó, nhưng đã có một phương thức có cùng chữ ký (= tham số, về cơ bản) - Clone()của lớp cơ sở. Làm cho giao diện phương thức nhân bản mới của bạn rõ ràng là vô nghĩa, bạn sẽ mất lợi thế bạn tìm kiếm khi tạo ICloneable<T>. Vì vậy, thêm newtừ khóa. Nhưng đừng quên rằng bạn cũng sẽ cần ghi đè các phương thức của lớp cơ sở với cùng chữ ký. Chọn từ khóa đầu tiên, bạn sẽ mất mục tiêu bạn muốn có khi thêm . Choss cái thứ hai, bạn sẽ tự phá vỡ giao diện, tạo ra các phương thức nên thực hiện cùng một trả về các đối tượng khác nhau.Clone() (việc triển khai phải duy trì thống nhất cho tất cả các lớp dẫn xuất, nghĩa là trả về cùng một đối tượng từ mọi phương thức nhân bản, vì vậy phương thức nhân bản cơ sở phải có virtual)! Nhưng, thật không may, bạn không thể cả hai overridenewICloneable<T>

  2. Điểm

    Bạn muốn ICloneable<T>thoải mái, nhưng sự thoải mái không phải là giao diện được thiết kế để làm gì, ý nghĩa của chúng là (nói chung là OOP) để thống nhất hành vi của các đối tượng (mặc dù trong C #, nó bị giới hạn trong việc thống nhất hành vi bên ngoài, ví dụ như các phương thức và thuộc tính, không phải hoạt động của họ).

    Nếu lý do đầu tiên chưa thuyết phục được bạn, bạn có thể phản đối ICloneable<T>cũng có thể hoạt động hạn chế, để hạn chế loại được trả về từ phương thức nhân bản. Tuy nhiên, lập trình viên khó chịu có thể thực hiện ICloneable<T>trong đó T không phải là loại đang thực hiện nó. Vì vậy, để đạt được hạn chế của bạn, bạn có thể thêm một ràng buộc đẹp cho tham số chung:
    public interface ICloneable<T> : ICloneable where T : ICloneable<T>
    Chắc chắn hạn chế hơn là không có where, bạn vẫn không thể hạn chế rằng T là loại đang thực hiện giao diện (bạn có thể xuất phát từ ICloneable<T>loại khác thực hiện nó).

    Bạn thấy đấy, thậm chí mục đích này không thể đạt được (bản gốc ICloneablecũng thất bại ở đây, không có giao diện nào thực sự có thể hạn chế hành vi của lớp thực hiện).

Như bạn có thể thấy, điều này chứng tỏ làm cho giao diện chung vừa khó thực hiện đầy đủ, vừa thực sự không cần thiết và vô dụng.

Nhưng trở lại câu hỏi, điều bạn thực sự tìm kiếm là có được sự thoải mái khi nhân bản một vật thể. Có hai cách để làm điều đó:

Phương pháp bổ sung

public class Base : ICloneable
{
    public Base Clone()
    {
        return this.CloneImpl() as Base;
    }

    object ICloneable.Clone()
    {
        return this.CloneImpl();
    }

    protected virtual object CloneImpl()
    {
        return new Base();
    }
}

public class Derived : Base
{
    public new Derived Clone()
    {
        return this.CloneImpl() as Derived;
    }

    protected override object CloneImpl()
    {
        return new Derived();
    }
}

Giải pháp này cung cấp cả sự thoải mái và hành vi dự định cho người dùng, nhưng nó cũng quá dài để thực hiện. Nếu chúng ta không muốn có phương thức "thoải mái" trả về loại hiện tại, thì sẽ dễ dàng hơn nhiều public virtual object Clone().

Vì vậy, hãy xem giải pháp "tối thượng" - những gì trong C # thực sự có ý định mang đến cho chúng ta sự thoải mái?

Phương pháp mở rộng!

public class Base : ICloneable
{
    public virtual object Clone()
    {
        return new Base();
    }
}

public class Derived : Base
{
    public override object Clone()
    {
        return new Derived();
    }
}

public static T Copy<T>(this T obj) where T : class, ICloneable
{
    return obj.Clone() as T;
}

Nó được đặt tên Sao chép không va chạm với các phương thức Clone hiện tại (trình biên dịch thích các phương thức khai báo của chính loại này hơn các phương thức mở rộng). Các classhạn chế là có cho tốc độ (không yêu cầu rỗng kiểm tra vv).

Tôi hy vọng điều này làm rõ lý do tại sao không thực hiện ICloneable<T>. Tuy nhiên, không nên thực hiện ICloneablechút nào.


Phụ lục # 1: Cách sử dụng chung duy nhất có thể ICloneablelà cho các loại giá trị, trong đó nó có thể phá vỡ quyền anh của phương pháp Clone và nó ngụ ý rằng bạn có giá trị không được mở hộp. Và vì các cấu trúc có thể được nhân bản (nông) một cách tự động, không cần phải thực hiện nó (trừ khi bạn làm cho nó cụ thể rằng nó có nghĩa là bản sao sâu).
IllidanS4 muốn Monica trở lại vào

2

Mặc dù câu hỏi đã rất cũ (5 năm kể từ khi viết câu trả lời này :) và đã được trả lời, nhưng tôi thấy bài viết này trả lời câu hỏi khá tốt, kiểm tra nó ở đây

BIÊN TẬP:

Dưới đây là trích dẫn từ bài viết trả lời câu hỏi (đảm bảo đọc toàn bộ bài viết, nó bao gồm những điều thú vị khác):

Có nhiều tài liệu tham khảo trên Internet chỉ ra một bài đăng trên blog năm 2003 của Brad Abrams - tại thời điểm làm việc tại Microsoft - trong đó một số suy nghĩ về IClonizable được thảo luận. Có thể tìm thấy mục blog tại địa chỉ này: Triển khai IClonizable . Mặc dù tiêu đề sai lệch, mục blog này kêu gọi không thực hiện IClonizable, chủ yếu là do sự nhầm lẫn nông / sâu. Bài viết kết thúc trong một gợi ý thẳng: Nếu bạn cần một cơ chế nhân bản, hãy xác định phương pháp sao chép của riêng bạn hoặc sao chép phương pháp và đảm bảo rằng bạn ghi lại rõ ràng liệu đó là bản sao sâu hay nông. Một mô hình thích hợp là:public <type> Copy();


1

Một vấn đề lớn là họ không thể hạn chế T là cùng một lớp. Ví dụ về những gì sẽ ngăn bạn làm điều này:

interface IClonable<T>
{
    T Clone();
}

class Dog : IClonable<JackRabbit>
{
    //not what you would expect, but possible
    JackRabbit Clone()
    {
        return new JackRabbit();
    }

}

Họ cần một hạn chế tham số như:

interfact IClonable<T> where T : implementing_type

8
Điều đó dường như không tệ như class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
Nikola Novak

Tôi không thực sự thấy vấn đề là gì. Không có giao diện có thể buộc thực hiện để hành xử hợp lý. Ngay cả khi ICloneable<T>có thể buộc Tphải khớp với loại của chính nó, điều đó sẽ không buộc thực thi Clone()phải trả lại bất cứ thứ gì từ xa giống với vật thể mà nó được nhân bản. Hơn nữa, tôi sẽ đề nghị rằng nếu một người đang sử dụng hiệp phương sai giao diện, tốt nhất nên có các lớp thực hiện ICloneableđược niêm phong, giao diện ICloneable<out T>bao gồm một thuộc Selftính dự kiến ​​sẽ tự trả về và ...
supercat

... có người tiêu dùng đúc hoặc ràng buộc ICloneable<BaseType>hoặc ICloneable<ICloneable<BaseType>>. Câu BaseTypehỏi nên có một protectedphương pháp để nhân bản, sẽ được gọi theo kiểu thực hiện ICloneable. Thiết kế này sẽ cho phép khả năng người ta có thể có một Container, a CloneableContainer, a FancyContainervà a CloneableFancyContainer, cái sau có thể sử dụng được trong mã yêu cầu một dẫn xuất có thể sao chép Containerhoặc yêu cầu FancyContainer(nhưng không quan tâm nếu nó có thể sao chép được).
supercat

Lý do tôi thích một thiết kế như vậy là vì vấn đề liệu một loại có thể được nhân bản một cách hợp lý có phần trực giao với các khía cạnh khác của nó hay không. Ví dụ, người ta có thể có một FancyListloại có thể được nhân bản một cách hợp lý, nhưng một công cụ phái sinh có thể tự động duy trì trạng thái của nó trong một tệp đĩa (được chỉ định trong hàm tạo). Loại dẫn xuất không thể được sao chép, vì trạng thái của nó sẽ được gắn với trạng thái của một đơn vị có thể thay đổi (tệp), nhưng không loại trừ việc sử dụng loại dẫn xuất ở những nơi cần hầu hết các tính năng của một FancyListnhưng không cần để nhân bản nó.
supercat

+1 - câu trả lời này là câu duy nhất trong số những người tôi đã thấy ở đây thực sự trả lời câu hỏi.
IllidanS4 muốn Monica trở lại vào

0

Đó là một câu hỏi rất hay ... Mặc dù vậy, bạn có thể tự làm:

interface ICloneable<T> : ICloneable
{
  new T Clone ( );
}

Andrey nói rằng nó được coi là một API tồi, nhưng tôi chưa nghe thấy gì về giao diện này bị phản đối. Và điều đó sẽ phá vỡ hàng tấn giao diện ... Phương pháp Clone sẽ thực hiện một bản sao nông. Nếu đối tượng cũng cung cấp bản sao sâu, có thể sử dụng bản sao quá tải (bool deep).

EDIT: Mẫu tôi sử dụng để "nhân bản" một đối tượng, đang truyền một nguyên mẫu trong hàm tạo.

class C
{
  public C ( C prototype )
  {
    ...
  }
}

Điều này loại bỏ bất kỳ tình huống triển khai mã dự phòng tiềm năng. BTW, nói về những hạn chế của IClonizable, không thực sự tùy thuộc vào đối tượng để quyết định liệu một bản sao nông hay bản sao sâu, hoặc thậm chí là một bản sao một phần nông / sâu một phần, nên được thực hiện? Chúng ta có nên thực sự quan tâm, miễn là đối tượng hoạt động như dự định? Trong một số trường hợp, việc thực hiện Clone tốt rất có thể bao gồm cả nhân bản nông và sâu.


"Xấu" không có nghĩa là không dùng nữa. Nó đơn giản có nghĩa là "tốt hơn hết là bạn không nên sử dụng nó". Điều đó không bị phản đối theo nghĩa là "chúng tôi sẽ loại bỏ giao diện IClonizable trong tương lai". Họ chỉ khuyến khích bạn không sử dụng nó và sẽ không cập nhật nó với thuốc generic hoặc các tính năng mới khác.
jalf

Dennis: Xem câu trả lời của Marc. Các vấn đề hiệp phương sai / kế thừa làm cho giao diện này không thể sử dụng được cho bất kỳ kịch bản nào ít nhất là phức tạp một chút.
Tamas Czinege

Phải, tôi có thể thấy những hạn chế của IClonizable, chắc chắn ... tôi hiếm khi cần sử dụng nhân bản.
baretta
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.