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.
c#
.net
icloneable
Bluenuance
fonte
fonte
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.
fonte
List<T>
tivesse um método clone, esperaria que ele produzisse umList<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": seCloneableFoo
herda de [não cloneable publicamente]Foo
, deveCloneableDerivedFoo
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 implementarClone
. 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 chamadoShallowCopy
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úblicoClone()
retornar um objeto digitado:Claro que há um segundo problema com uma
ICloneable<T>
herança genérica .Se eu tiver:
E eu implementei
ICloneable<T>
, então eu implementoICloneable<Foo>
?ICloneable<Bar>
? Você começa rapidamente a implementar muitas interfaces idênticas ... Compare com um elenco ... e é realmente tão ruim assim?fonte
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)
fonte
ICloneable<out T>
pode ser bastante útil se herdado deISelf<out T>
, com um único métodoSelf
de tipoT
. Muitas vezes, não é necessário "algo que seja clonável", mas pode-se muito bem precisar de umT
que seja clonável. Se um objeto clonável é implementadoISelf<itsOwnType>
, uma rotina que precisa de umT
clonável pode aceitar um parâmetro do tipoICloneable<T>
, mesmo que nem todos os derivados clonáveis deT
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 deFoo
contém, mas não é nem vai sofrer mutação que nem esperar que ele não vai mudar nunca poderia usar umIReadableFoo
, enquanto ...Foo
poderia usar umImmutableFoo
código while que, para querer manipulá-lo, poderia usar aMutableFoo
. O código fornecido por qualquer tipoIReadableFoo
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 comICloneable<T>
para fazer uma cópia imutável de uma classe que contémT
'.List<T>
, de modo que a clonadaList<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 semICloneable<T>
. O primeiro é oEnumerable.ToList()
método de extensão:List<foo> clone = original.ToList();
o segundo é oList<T>
construtor que recebe umIEnumerable<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.
fonte
É muito fácil escrever a interface você mesmo, se precisar:
fonte
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 aceitarICloneable<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 fazeris 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>
eICloneable
, o primeiro implementando o último. Assim, um implementador precisaria implementar os dois métodos -object Clone()
eT Clone()
. Não, obrigado, já nos divertimos o suficienteIEnumerable
.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) - aClone()
da classe base. Tornar explícita sua nova interface de método de clone é inútil, você perderá a vantagem que procurava ao criarICloneable<T>
. Então adicione anew
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 servirtual
)! Mas, infelizmente, você não pode tantooverride
enew
métodos com a mesma assinatura. Ao escolher a primeira palavra-chave, você perderia a meta que queria ter ao adicionarICloneable<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 implementarICloneable<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 derivarICloneable<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:
Métodos adicionais
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 apenas
public virtual object Clone()
.Então, vamos ver a solução "definitiva" - o que em C # realmente pretende nos dar conforto?
Métodos de extensão!
É 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). o
class
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 implementarICloneable
.fonte
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):
fonte
Um grande problema é que eles não podiam restringir T à mesma classe. Por exemplo, o que impediria você de fazer isso:
Eles precisam de uma restrição de parâmetro como:
fonte
class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
ICloneable<T>
pudesse restringirT
a correspondência com seu próprio tipo, isso não forçaria uma implementaçãoClone()
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 implementamICloneable
ser selado, ter a interfaceICloneable<out T>
incluem umaSelf
propriedade que é esperado para si voltar, e ...ICloneable<BaseType>
ouICloneable<ICloneable<BaseType>>
. OBaseType
em questão deve ter umprotected
método de clonagem, que seria chamado pelo tipo que implementaICloneable
. Esse design permitiria a possibilidade de alguém ter aContainer
, aCloneableContainer
, aFancyContainer
e aCloneableFancyContainer
, sendo este último utilizável em código que requer uma derivada clonávelContainer
ou que requer umaFancyContainer
(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:
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.
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.
fonte