Como faço para criar uma classe imutável?


113

Estou trabalhando na criação de uma classe imutável.
Marquei todas as propriedades como somente leitura.

Eu tenho uma lista de itens da aula.
Embora se a propriedade for somente leitura, a lista pode ser modificada.

Expor o IEnumerable da lista o torna imutável.
Eu queria saber quais são as regras básicas que devem ser seguidas para tornar uma classe imutável.


2
Eu recomendo fortemente que você leia a série do blog de Eric Lippert sobre imutabilidade , em particular a entrada sobre "tipos de imutabilidade" . Seu comentário de que "Expor o IEnumerable da lista o torna imutável" parece um pouco estranho para mim. O que voce quer dizer com isso?
Jon Skeet

O que eu quis dizer foi, em vez de permitir o acesso à lista (se o objeto tiver uma lista de alguns outros objetos), permitir ao usuário acessar os membros com IEnumerable. Lista de discussão aqui como em um exemplo específico, mas pode ser qualquer estrutura de dados.
Biswanath

1
Os links de Jon Skeet para a série de blogs de Eric Lippert quebraram quando o MSDN arquivou esses blogs. Por favor, veja "tipos de imutabilidade" . O resto da série parece estar abaixo de dezembro de 2007 + janeiro de 2008 no painel esquerdo.
John Doe

Por favor, veja série blog de Eric Lippert sobre como as pessoas comumente confundir os termos atomicity, volatility, e immutability: Parte I , Parte II e Parte III . Estas são de seu blog pessoal e, acredito, mais amigável para novatos do que seus posts MSDN.
John Doe

Respostas:


121

Acho que você está no caminho certo -

  • todas as informações injetadas na classe devem ser fornecidas no construtor
  • todas as propriedades devem ser apenas getters
  • se uma coleção (ou Array) é passada para o construtor, ela deve ser copiada para evitar que o autor da chamada a modifique mais tarde
  • se você vai retornar sua coleção, retorne uma cópia ou uma versão somente leitura (por exemplo, usando ArrayList.ReadOnly ou similar - você pode combinar isso com o ponto anterior e armazenar uma cópia somente leitura para ser retornada quando chamadores acessam), retornam um enumerador ou usam algum outro método / propriedade que permite acesso somente leitura à coleção
  • tenha em mente que você ainda pode ter a aparência de uma classe mutável se algum de seus membros for mutável - se este for o caso, você deve copiar qualquer estado que deseja manter e evitar retornar objetos mutáveis ​​inteiros, a menos que você os copie antes de devolvê-los ao chamador - outra opção é retornar apenas "seções" imutáveis ​​do objeto mutável - obrigado a @Brian Rasmussen por me encorajar a expandir este ponto

Devo escrever uma propriedade de pesquisa de wrapper, que permite apenas a pesquisa? E obrigado pela resposta.
Biswanath

5
Qualquer tipo de referência mutável passado como um argumento para o construtor deve ser copiado. Caso contrário, o chamador ainda manterá uma referência ao estado.
Brian Rasmussen

@Brian Rasmussen, você está certo, mas mesmo que seja copiado, pode ser possível para qualquer chamador acessar o objeto mutável, dependendo dos acessos.sors fornecidos. No caso de ser passado um objeto mutável, é melhor a classe sempre retornar uma cópia diferente ou seções imutáveis ​​do objeto
Blair Conrad

@Blair - Se eu tiver um dicionário na aula que só quero usar para pesquisar. É uma propriedade somente leitura que faz uma pesquisa deve ser adequada?
Biswanath

@Biswanath - sim, com a ressalva de que se os valores no dicionário forem mutáveis, você precisará protegê-los antes de retornar, se não quiser que eles sofram mutação
Blair Conrad

18

Para serem imutáveis, todas as suas propriedades e campos devem ser somente leitura. E os itens em qualquer lista devem ser imutáveis.

Você pode criar uma propriedade de lista somente leitura da seguinte maneira:

public class MyClass
{
    public MyClass(..., IList<MyType> items)
    {
        ...
        _myReadOnlyList = new List<MyType>(items).AsReadOnly();
    }

    public IList<MyType> MyReadOnlyList
    {
        get { return _myReadOnlyList; }
    }
    private IList<MyType> _myReadOnlyList

}

1
Acho que ImmutableList seria melhor.
Kevin Wong

use ImmutableList, também considere selar a classe
kofifus

Eu não estou convencido de que ImmutableList é uma boa opção para este caso de uso: basic rules to make a class (that contains a list) immutable. A semântica de ImmutableList permite um uso de memória mais eficiente ao adicionar itens a uma cópia da lista, mas isso me parece um caso de uso mais específico.
Joe

11

Além disso, lembre-se de que:

public readonly object[] MyObjects;

não é imutável, mesmo se estiver marcado com a palavra-chave somente leitura. Você ainda pode alterar referências / valores de array individuais por acessador de índice.


5

Use a ReadOnlyCollectionclasse. Ele está situado no System.Collections.ObjectModelnamespace.

Em qualquer coisa que retorne sua lista (ou no construtor), defina a lista como uma coleção somente leitura.

using System.Collections.ObjectModel;

...

public MyClass(..., List<ListItemType> theList, ...)
{
    ...
    this.myListItemCollection= theList.AsReadOnly();
    ...
}

public ReadOnlyCollection<ListItemType> ListItems
{
     get { return this.myListItemCollection; }
}

1

Outra opção seria usar um padrão de visitante em vez de expor qualquer coleção interna.


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.