Existe uma razão específica para a inexistência de um genérico ICloneable<T>
?
Seria muito mais confortável, se eu não precisasse transmiti-lo toda vez que clonasse algo.
Existe uma razão específica para a inexistência de um genérico ICloneable<T>
?
Seria muito mais confortável, se eu não precisasse transmiti-lo toda vez que clonasse algo.
Respostas:
O ICloneable é considerado uma API incorreta agora, pois não especifica se o resultado é uma cópia profunda ou superficial. Eu acho que é por isso que eles não melhoram essa interface.
Você provavelmente pode fazer um método de extensão de clonagem digitado, mas acho que exigiria um nome diferente, já que os métodos de extensão têm menos prioridade que os originais.
List<T>
tivesse um método clone, esperaria que ele produzisse um List<T>
cujos itens tenham as mesmas identidades que os da lista original, mas esperaria que quaisquer estruturas de dados internas fossem duplicadas conforme necessário para garantir que nada feito em uma lista afete as identidades dos itens armazenados no outro. Onde está a ambiguidade? Um problema maior com a clonagem vem com uma variação do "problema diamante": se CloneableFoo
herda de [não cloneable publicamente] Foo
, deve CloneableDerivedFoo
derivar de ...
identity
lista em si, por exemplo (no caso de lista de listas)? No entanto, ignorando isso, sua expectativa não é a única ideia possível que as pessoas possam ter ao ligar ou implementar Clone
. E se os autores da biblioteca que implementam alguma outra lista não atenderem às suas expectativas? A API deve ser trivialmente inequívoca, não indiscutivelmente inequívoca.
Clone
todas as suas partes, não funcionará previsivelmente - dependendo se essa parte foi implementada por você ou por essa pessoa quem gosta de clonagem profunda. seu ponto de vista sobre os padrões é válido, mas ter o IMHO na API não é claro o suficiente - ele deve ser chamado ShallowCopy
para enfatizar o ponto ou não ser fornecido.
Além de resposta de Andrey (que eu concordo com, +1) - quando ICloneable
é feito, você pode também escolher implementação explícita para fazer o público Clone()
retornar um objeto digitado:
public Foo Clone() { /* your code */ }
object ICloneable.Clone() {return Clone();}
Claro que há um segundo problema com uma ICloneable<T>
herança genérica .
Se eu tiver:
public class Foo {}
public class Bar : Foo {}
E eu implementei ICloneable<T>
, então eu implemento ICloneable<Foo>
? ICloneable<Bar>
? Você começa rapidamente a implementar muitas interfaces idênticas ... Compare com um elenco ... e é realmente tão ruim assim?
Eu preciso perguntar, o que exatamente você faria com a interface além de implementá-la? As interfaces geralmente são úteis apenas quando você faz conversão para ela (por exemplo, essa classe suporta 'IBar') ou possui parâmetros ou configuradores que a utilizam (por exemplo, eu uso um 'IBar'). Com o ICloneable - percorremos todo o Framework e falhamos em encontrar um único uso em qualquer lugar que não fosse uma implementação dele. Também não conseguimos encontrar nenhum uso no 'mundo real' que faça algo além de implementá-lo (nos ~ 60.000 aplicativos aos quais temos acesso).
Agora, se você gostaria de impor um padrão que deseja que seus objetos 'clonáveis' implementem, esse é um uso muito bom - e vá em frente. Você também pode decidir exatamente o que "clonagem" significa para você (por exemplo, profundo ou superficial). No entanto, nesse caso, não há necessidade de nós (o BCL) defini-lo. Só definimos abstrações na BCL quando há necessidade de trocar instâncias digitadas como essa abstração entre bibliotecas não relacionadas.
David Kean (Equipe BCL)
ICloneable<out T>
pode ser bastante útil se herdado de ISelf<out T>
, com um único método Self
de tipo T
. Muitas vezes, não é necessário "algo que seja clonável", mas pode-se muito bem precisar de um T
que seja clonável. Se um objeto clonável é implementado ISelf<itsOwnType>
, uma rotina que precisa de um T
clonável pode aceitar um parâmetro do tipo ICloneable<T>
, mesmo que nem todos os derivados clonáveis de T
compartilhem um ancestral comum.
ICloneable<T>
poderia ser útil para isso, embora uma estrutura mais ampla para manter classes paralelas mutáveis e imutáveis possa ser mais útil. Em outras palavras, o código que precisa ver o que algum tipo de Foo
contém, mas não é nem vai sofrer mutação que nem esperar que ele não vai mudar nunca poderia usar um IReadableFoo
, enquanto ...
Foo
poderia usar um ImmutableFoo
código while que, para querer manipulá-lo, poderia usar a MutableFoo
. O código fornecido por qualquer tipo IReadableFoo
deve ser capaz de obter uma versão mutável ou imutável. Essa estrutura seria legal, mas infelizmente não consigo encontrar nenhuma maneira legal de configurar as coisas de maneira genérica. Se houvesse uma maneira consistente de criar um wrapper somente leitura para uma classe, tal coisa poderia ser usada em combinação com ICloneable<T>
para fazer uma cópia imutável de uma classe que contém T
'.
List<T>
, de modo que a clonada List<T>
seja uma nova coleção que contém ponteiros para todos os mesmos objetos da coleção original, há duas maneiras fáceis de fazer isso sem ICloneable<T>
. O primeiro é o Enumerable.ToList()
método de extensão: List<foo> clone = original.ToList();
o segundo é o List<T>
construtor que recebe um IEnumerable<T>
: List<foo> clone = new List<foo>(original);
Eu suspeito que o método de extensão provavelmente esteja apenas chamando o construtor, mas ambos farão o que você está solicitando. ;)
Eu acho que a pergunta "por que" é desnecessária. Há muitas interfaces / classes / etc ... que são muito úteis, mas não fazem parte da biblioteca base do .NET Frameworku.
Mas, principalmente, você pode fazer isso sozinho.
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(); }
}
É muito fácil escrever a interface você mesmo, se precisar:
public interface ICloneable<T> : ICloneable
where T : ICloneable<T>
{
new T Clone();
}
Tendo lido recentemente o artigo Por que copiar um objeto é uma coisa terrível a se fazer? , Acho que essa pergunta precisa de clafiricação adicional. Outras respostas aqui fornecem bons conselhos, mas a resposta ainda não está completa - por que não ICloneable<T>
?
Uso
Então, você tem uma classe que a implementa. Enquanto anteriormente você tinha um método que queria ICloneable
, agora ele precisa ser genérico para aceitar ICloneable<T>
. Você precisaria editá-lo.
Então, você poderia ter um método que verifica se há um objeto is ICloneable
. E agora? Você não pode fazer is ICloneable<>
e, como não conhece o tipo de objeto no tipo de compilação, não pode tornar o método genérico. Primeiro problema real.
Então você precisa ter ambos ICloneable<T>
e ICloneable
, o primeiro implementando o último. Assim, um implementador precisaria implementar os dois métodos - object Clone()
e T Clone()
. Não, obrigado, já nos divertimos o suficiente IEnumerable
.
Como já apontado, há também a complexidade da herança. Embora a covariância pareça resolver esse problema, um tipo derivado precisa implementar ICloneable<T>
seu próprio tipo, mas já existe um método com a mesma assinatura (= parâmetros, basicamente) - a Clone()
da classe base. Tornar explícita sua nova interface de método de clone é inútil, você perderá a vantagem que procurava ao criar ICloneable<T>
. Então adicione a new
palavra - chave. Mas não esqueça que você também precisaria substituir a classe base ' Clone()
(a implementação deve permanecer uniforme para todas as classes derivadas, ou seja, para retornar o mesmo objeto de cada método clone, portanto, o método clone base deve ser virtual
)! Mas, infelizmente, você não pode tanto override
enew
métodos com a mesma assinatura. Ao escolher a primeira palavra-chave, você perderia a meta que queria ter ao adicionar ICloneable<T>
. Ao escolher o segundo, você quebraria a própria interface, criando métodos que deveriam fazer o mesmo retornar objetos diferentes.
Ponto
Você deseja ICloneable<T>
conforto, mas o conforto não é para o que as interfaces são projetadas, seu significado é (em geral OOP) para unificar o comportamento dos objetos (embora em C # seja limitado à unificação do comportamento externo, por exemplo, métodos e propriedades, não seu funcionamento).
Se o primeiro motivo ainda não o convenceu, você pode objetar que ICloneable<T>
também funcione de forma restritiva, para limitar o tipo retornado pelo método clone. No entanto, o programador desagradável pode implementar ICloneable<T>
onde T não é o tipo que está implementando. Portanto, para alcançar sua restrição, você pode adicionar uma boa restrição ao parâmetro genérico:
public interface ICloneable<T> : ICloneable where T : ICloneable<T>
certamente mais restritivo que o outro where
, ainda não é possível restringir que T é o tipo que está implementando a interface (você pode derivar ICloneable<T>
de outro tipo que o implementa).
Veja bem, mesmo esse propósito não pode ser alcançado (o original ICloneable
também falha nisso, nenhuma interface pode realmente limitar o comportamento da classe de implementação).
Como você pode ver, isso prova que a interface genérica é difícil de implementar completamente e também é realmente desnecessária e inútil.
Mas voltando à questão, o que você realmente procura é ter conforto ao clonar um objeto. Existem duas maneiras de fazer isso:
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();
}
}
Essa solução fornece conforto e comportamento pretendido aos usuários, mas também é muito longa para ser implementada. Se não queremos que o método "confortável" retorne o tipo atual, é muito mais fácil ter apenaspublic virtual object Clone()
.
Então, vamos ver a solução "definitiva" - o que em C # realmente pretende nos dar conforto?
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;
}
É chamado Copiar para não colidir com os métodos Clone atuais (o compilador prefere os métodos declarados do tipo em vez dos de extensão). oclass
restrição existe para a velocidade (não requer verificação nula etc.).
Espero que isso esclareça a razão pela qual não fazer ICloneable<T>
. No entanto, é recomendável não implementar ICloneable
.
ICloneable
é para tipos de valor, onde ele pode contornar o boxe do método Clone e implica que você tem o valor fora da caixa. E como as estruturas podem ser clonadas (superficialmente) automaticamente, não há necessidade de implementá-las (a menos que você especifique que isso significa cópia em profundidade).
Embora a pergunta seja muito antiga (cinco anos depois de escrever essas respostas :) e já tenha sido respondida, mas achei que este artigo responde muito bem à pergunta, verifique aqui
EDITAR:
Aqui está a citação do artigo que responde à pergunta (leia o artigo completo, inclui outras coisas interessantes):
Existem muitas referências na Internet que apontam para uma publicação de Brad Abrams em 2003 - na época empregada na Microsoft - na qual são discutidas algumas reflexões sobre o ICloneable. A entrada do blog pode ser encontrada neste endereço: Implementando o ICloneable . Apesar do título enganoso, esta entrada do blog pede para não implementar o ICloneable, principalmente por causa de confusão superficial / profunda. O artigo termina com uma sugestão direta: se você precisar de um mecanismo de clonagem, defina sua própria metodologia Clone ou Copiar e garanta que você documente claramente se é uma cópia profunda ou superficial. Um padrão apropriado é:
public <type> Copy();
Um grande problema é que eles não podiam restringir T à mesma classe. Por exemplo, o que impediria você de fazer isso:
interface IClonable<T>
{
T Clone();
}
class Dog : IClonable<JackRabbit>
{
//not what you would expect, but possible
JackRabbit Clone()
{
return new JackRabbit();
}
}
Eles precisam de uma restrição de parâmetro como:
interfact IClonable<T> where T : implementing_type
class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
ICloneable<T>
pudesse restringir T
a correspondência com seu próprio tipo, isso não forçaria uma implementação Clone()
a retornar algo remotamente semelhante ao objeto no qual foi clonado. Além disso, gostaria de sugerir que, se alguém está usando interface de covariância, pode ser melhor ter classes que implementam ICloneable
ser selado, ter a interface ICloneable<out T>
incluem uma Self
propriedade que é esperado para si voltar, e ...
ICloneable<BaseType>
ou ICloneable<ICloneable<BaseType>>
. O BaseType
em questão deve ter um protected
método de clonagem, que seria chamado pelo tipo que implementa ICloneable
. Esse design permitiria a possibilidade de alguém ter a Container
, a CloneableContainer
, a FancyContainer
e a CloneableFancyContainer
, sendo este último utilizável em código que requer uma derivada clonável Container
ou que requer uma FancyContainer
(mas não se importa se é clonável).
FancyList
tipo que pode ser clonado de maneira sensata, mas um derivado pode persistir automaticamente em um arquivo de disco (especificado no construtor). O tipo derivado não pôde ser clonado, porque seu estado seria anexado ao de um singleton mutável (o arquivo), mas isso não deve impedir o uso do tipo derivado em locais que precisam da maioria dos recursos de um, FancyList
mas não precisam. cloná-lo.
É uma pergunta muito boa ... Você pode fazer o seu, no entanto:
interface ICloneable<T> : ICloneable
{
new T Clone ( );
}
Andrey diz que é considerada uma API ruim, mas não ouvi nada sobre essa interface ficar obsoleta. E isso quebraria toneladas de interfaces ... O método Clone deve executar uma cópia superficial. Se o objeto também fornecer cópia em profundidade, um Clone sobrecarregado (profundidade em bool) pode ser usado.
Edição: Padrão que eu uso para "clonar" um objeto, está passando um protótipo no construtor.
class C
{
public C ( C prototype )
{
...
}
}
Isso remove qualquer possível situação de implementação de código redundante. BTW, falando sobre as limitações do ICloneable, não cabe realmente ao objeto decidir se um clone raso ou profundo, ou mesmo um clone parcialmente raso / parcialmente profundo, deve ser executado? Deveríamos realmente nos importar, desde que o objeto funcione como pretendido? Em algumas ocasiões, uma boa implementação de Clone pode muito bem incluir clonagem superficial e profunda.