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ì đó.
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ì đó.
Câu trả lời:
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.
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 CloneableFoo
thừa hưởng từ [không thể nhân bản công khai] Foo
, nên CloneableDerivedFoo
xuất phát từ ...
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.
Clone
tấ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.
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?
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)
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 Self
loạ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ứ T
có 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 T
sao 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 T
chung một tổ tiên.
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 Foo
chứ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 ...
Foo
có thể sử dụng ImmutableFoo
mã 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 IReadableFoo
sẽ 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
'.
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. ;)
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(); }
}
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();
}
Đã đọ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>
?
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>
và 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()
và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 new
từ 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 override
vànew
ICloneable<T>
Đ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 ICloneable
cũ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 đó:
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?
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 class
hạ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 ICloneable
chút nào.
ICloneable
là 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).
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();
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
class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
ICloneable<T>
có thể buộc T
phả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 Self
tính dự kiến sẽ tự trả về và ...
ICloneable<BaseType>
hoặc ICloneable<ICloneable<BaseType>>
. Câu BaseType
hỏi nên có một protected
phươ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 FancyContainer
và 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 Container
hoặc yêu cầu FancyContainer
(nhưng không quan tâm nếu nó có thể sao chép được).
FancyList
loạ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 FancyList
nhưng không cần để nhân bản nó.
Đó 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.