Como manejo setters em campos imutáveis?


25

Eu tenho uma classe com dois readonly intcampos. Eles são expostos como propriedades:

public class Thing
{
    private readonly int _foo, _bar;

    /// <summary> I AM IMMUTABLE. </summary>
    public Thing(int foo, int bar)
    {
        _foo = foo;
        _bar = bar;
    }

    public int Foo { get { return _foo; } set { } }

    public int Bar { get { return _bar; } set { } }
}

No entanto, isso significa que o seguinte é um código perfeitamente legal:

Thing iThoughtThisWasMutable = new Thing(1, 42);

iThoughtThisWasMutable.Foo = 99;  // <-- Poor, mistaken developer.
                                  //     He surely has bugs now. :-(

Os erros que advêm da suposição de que funcionariam certamente serão insidiosos. Claro, o desenvolvedor errado deve ter lido os documentos. Mas isso não muda o fato de que nenhum erro de compilação ou tempo de execução o alertou sobre o problema.

Como a Thingclasse deve ser alterada para que os desenvolvedores tenham menos probabilidade de cometer o erro acima?

Lançar uma exceção? Use um método getter em vez de uma propriedade?



1
Obrigado, @gnat. Esse post (tanto a questão ea resposta) parece estar falando sobre interfaces, como na Capital I Interfaces. Não tenho certeza se é isso que estou fazendo.
precisa saber é o seguinte

6
@gnat, esse é um conselho terrível. As interfaces oferecem uma ótima maneira de fornecer VOs / DTOs publicamente imutáveis ​​que ainda são fáceis de testar.
David Arno

4
@gnat, eu fiz e a pergunta não faz sentido, pois o OP parece pensar que as interfaces destruirão a imutabilidade, o que é um absurdo.
David Arno

1
@gnat, essa pergunta era sobre declarar interfaces para DTOs. A resposta mais votada foi que as interfaces não seriam prejudiciais, mas provavelmente desnecessárias.
Paul Draper

Respostas:


107

Por que tornar esse código legal?
Retire o set { }que não faz nada.
É assim que você define uma propriedade pública somente leitura:

public int Foo { get { return _foo; } }

3
Não achei legal por algum motivo, mas parece funcionar perfeitamente! Perfeito, obrigado.
Kdbanman

1
@kdbanman Provavelmente, tem algo a ver com algo que você viu o IDE fazendo em um ponto - provavelmente com conclusão automática.
Panzercrisis

2
@kdbanman; o que não é legal (até C # 5) é public int Foo { get; }(propriedade automática com apenas um getter) Mas public int Foo { get; private set; }é.
Laurent LA RIZZA

@LaurentLARIZZA, você mexeu com minha memória. Foi exatamente de onde veio minha confusão.
Kdbanman

45

Com o C # 5 e antes, nos deparamos com duas opções para campos imutáveis ​​expostos por meio de um getter:

  1. Crie uma variável de suporte somente leitura e retorne-a por meio de um getter manual. Essa opção é segura (é preciso remover explicitamente readonlyo arquivo para destruir a imutabilidade. No entanto, ele criou muitos códigos de placas de caldeira.
  2. Use uma propriedade automática, com um setter privado. Isso cria um código mais simples, mas é mais fácil quebrar acidentalmente a imutabilidade.

No entanto, com o C # 6 (disponível no VS2015, lançado ontem), agora obtemos o melhor dos dois mundos: propriedades automáticas somente leitura. Isso nos permite simplificar a classe do OP para:

public class Thing
{
    /// <summary> I AM IMMUTABLE. </summary>
    public Thing(int foo, int bar)
    {
        Foo = foo;
        Bar = bar;
    }

    public int Foo { get; }
    public int Bar { get; }
}

As linhas Foo = fooe Bar = barsão válidas apenas no construtor (que atinge o requisito robusto de somente leitura) e o campo de apoio está implícito, em vez de precisar ser explicitamente definido (o que alcança o código mais simples).


2
E os construtores primários podem simplificar ainda mais isso, eliminando a sobrecarga sintática do construtor.
Sebastian Redl


1
@ DanLyons Porra, eu estava ansioso para usar isso em toda a nossa base de código.
Sebastian Redl

12

Você pode simplesmente se livrar dos levantadores. Eles não fazem nada, confundem os usuários e levam a erros. No entanto, você pode torná-los privados e, assim, livrar-se das variáveis ​​de backup, simplificando seu código:

public class Thing
{
    public Thing(int foo, int bar)
    {
        Foo = foo;
        Bar = bar;
    }

    public int Foo { get; private set; }

    public int Bar { get; private set; }
}

1
" public int Foo { get; private set; }" Essa classe não é mais imutável porque pode mudar a si mesma. Obrigado, no entanto!
Kdbanman

4
A classe descrita é imutável, pois não se altera.
David Arno

1
Minha turma atual é um pouco mais complexa do que o MWE que eu dei. Estou atrás de uma imutabilidade real , completa com segurança de thread e garantias internas de classe.
precisa saber é o seguinte

4
@kdbanman, e seu ponto é? Se sua classe grava apenas nos setters particulares durante a construção, ela implementou "imutabilidade real". A readonlypalavra-chave é apenas um poka-yoke, ou seja, protege contra a classe que quebra a imutabilidade no futuro; não é necessário alcançar a imutabilidade.
David Arno

3
Termo interessante, eu tive que pesquisar no google - poka-yoke é um termo japonês que significa "à prova de erros". Você está certo! É exatamente o que estou fazendo.
Kdbanman

6

Duas soluções

Simples:

Não inclua setters, como observado por David, para objetos imutáveis ​​somente para leitura.

Alternativamente:

Permita que os setters retornem um novo objeto imutável, detalhado em comparação ao anterior, mas fornece estado ao longo do tempo para cada objeto inicializado. Esse design é uma ferramenta muito útil para a segurança e imutabilidade do Thread, que se estende por todas as linguagens imperativas de POO.

Pseudo-código

public class Thing
{

{readonly vars}

    public Thing(int foo, int bar)
    {
        _foo = foo;
        _bar = bar;
    }

    public Thing withFoo(int foo) {
        return new Thing(foo, getBar());
    }

    public Thing withBar(int bar) {
        return new Thing(getFoo(), bar)
    }

    etc...
}

fábrica estática pública

public class Thing
{

{readonly vars}

    private Thing(int foo, int bar)
    {
        _foo = foo;
        _bar = bar;
    }

    public static with(int foo, int bar) {
        return new Thing(foo, bar)
    }

    public Thing withFoo(int foo) {
        return new Thing(foo, getBar());
    }

    public Thing withBar(int bar) {
        return new Thing(getFoo(), bar)
    }

    etc...
}

2
setXYZ são nomes horríveis para métodos de fábrica, considere comXYZ ou similares.
Ben Voigt

Obrigado, atualizei minha resposta para refletir seu comentário útil. :-)
Matt Smithies

A pergunta estava perguntando especificamente sobre configuradores de propriedades , não métodos de configuradores.
user253751

É verdade, mas o OP está preocupado com o estado mutável de um possível desenvolvedor futuro. Variáveis ​​que usam palavras-chave privadas somente de leitura ou finais devem ser auto-documentadas, o que não é culpa dos desenvolvedores originais, se isso não for entendido. Compreender os diferentes métodos para alterar o estado é fundamental. Eu adicionei outra abordagem que é muito útil. Existem muitas coisas no mundo do código que causam paranóia excessiva; os vars privados somente de leitura não devem ser um deles.
Matt Smithies
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.