Eu tenho a pergunta "melhores práticas" sobre POO em C # (mas isso se aplica a todos os idiomas).
Considere ter uma classe de biblioteca com um objeto a ser exposto ao público, por exemplo, via acessador de propriedade, mas não queremos que o público (pessoas que usam essa classe de biblioteca) o altere.
class A
{
// Note: List is just example, I am interested in objects in general.
private List<string> _items = new List<string>() { "hello" }
public List<string> Items
{
get
{
// Option A (not read-only), can be modified from outside:
return _items;
// Option B (sort-of read-only):
return new List<string>( _items );
// Option C, must change return type to ReadOnlyCollection<string>
return new ReadOnlyCollection<string>( _items );
}
}
}
Obviamente, a melhor abordagem é a "opção C", mas muito poucos objetos possuem a variante ReadOnly (e certamente nenhuma classe definida pelo usuário a possui).
Se você fosse usuário da classe A, esperaria mudanças no
List<string> someList = ( new A() ).Items;
propagar para o objeto original (A)? Ou está certo devolver um clone desde que tenha sido escrito em comentários / documentação? Eu acho que essa abordagem de clone pode levar a erros bastante difíceis de rastrear.
Lembro que em C ++ poderíamos retornar objetos const e você só poderia chamar métodos marcados como const nele. Eu acho que não existe esse recurso / padrão no c #? Se não, por que eles não o incluiriam? Eu acredito que é chamado const correção.
Mas, novamente, minha pergunta principal é sobre "o que você esperaria" ou como lidar com a Opção A vs B.
fonte
Respostas:
Além da resposta de @ Jesse, que é a melhor solução para coleções: a idéia geral é projetar sua interface pública de A de uma maneira que retorne apenas
tipos de valor (como int, double, struct)
objetos imutáveis (como "string" ou classes definidas pelo usuário que oferecem apenas métodos somente leitura, assim como sua classe A)
(somente se você precisar): cópias de objetos, como sua "Opção B", demonstram
IEnumerable<immutable_type_here>
é imutável por si só, então esse é apenas um caso especial do que foi dito acima.fonte
Observe também que você deve programar para interfaces e, em geral, o que você deseja retornar dessa propriedade é um
IEnumerable<string>
. AList<string>
é um pouco de não-não, porque geralmente é mutável e vou até agora dizer que,ReadOnlyCollection<string>
como um implementador de,ICollection<string>
parece que viola o Princípio de Substituição de Liskov.fonte
IEnumerable<string>
é exatamente o que se deseja aqui.IReadOnlyList<T>
.IList<T>
)Eu encontrei um problema semelhante há algum tempo atrás e abaixo foi a minha solução, mas realmente só funciona se você controlar a biblioteca também:
Comece com sua turma
A
Em seguida, derive uma interface disso apenas com a parte get das propriedades (e quaisquer métodos de leitura) e faça com que A implemente
Então é claro que quando você deseja usar a versão somente leitura, uma conversão simples é suficiente. É exatamente isso que você solucione C, realmente, mas pensei em oferecer o método com o qual o alcancei
fonte
Para quem encontra esta pergunta e ainda está interessado na resposta - agora há outra opção que está expondo
ImmutableArray<T>
. Estes são alguns pontos pelos quais eu acho que é melhorIEnumerable<T>
ouReadOnlyCollection<T>
:IEnumerable<T>
fazIEnumerable
ouIReadOnlyCollect
não pode assumir que nunca muda (em outras palavras, não há garantia de que os itens de uma coleção não tenham sido alterados enquanto a referência a essa coleção permanece inalterada)Além disso, se a APi precisar notificar o cliente sobre alterações na coleção, eu exporia um evento como um membro separado.
Note que não estou dizendo que isso
IEnumerable<T>
seja ruim em geral. Eu ainda o usaria ao passar a coleção como argumento ou ao retornar uma coleção inicializada preguiçosamente (nesse caso em particular, faz sentido ocultar a contagem de itens porque ainda não é conhecido).fonte