Pergunta

Eu li muitas vezes que structs deve ser imutável -? Não são, por definição

Você considera int ser imutável?

int i = 0;
i = i + 123;

Parece tudo bem - nós começamos um novo int e atribuí-lo de volta para i. Que tal isso?

i++;

Ok, podemos pensar nisso como um atalho.

i = i + 1;

E o struct Point?

Point p = new Point(1, 2);
p.Offset(3, 4);

Será que isso realmente transformar o (1, 2) ponto? não devemos pensar nisso como um atalho para o seguinte com Point.Offset() retornando um novo ponto?

p = p.Offset(3, 4);

O fundo deste pensamento é este - como pode um tipo de valor sem identidade ser mutável? Você tem que olhar para ele pelo menos duas vezes para determinar se ele mudou. Mas como você pode fazer isso sem uma identidade?

Eu não quero raciocínio complicar sobre este considerando parâmetros ref e boxe. Também estou ciente que expressa p = p.Offset(3, 4); imutabilidade muito melhor do que p.Offset(3, 4); faz. Mas a questão permanece - não são tipos de valores imutáveis ??por definição

?

Atualizar

Eu acho que há pelo menos dois conceitos envolvidos -. A mutabilidade de uma variável ou campo ea mutabilidade do valor de uma variável

public class Foo
{
    private Point point;
    private readonly Point readOnlyPoint;

    public Foo()
    {
        this.point = new Point(1, 2);
        this.readOnlyPoint = new Point(1, 2);
    }

    public void Bar()
    {
        this.point = new Point(1, 2);
        this.readOnlyPoint = new Point(1, 2); // Does not compile.

        this.point.Offset(3, 4); // Is now (4, 6).
        this.readOnlyPoint.Offset(3, 4); // Is still (1, 2).
    }
}

No exemplo temos a campos - um mutável e uma imutável. Porque um campo de tipo de valor contém o valor inteiro, um tipo de valor armazenado em um campo imutável deve ser imutável, também. Ainda estou bastante surpresos com o resultado -. Eu não exspect campo somente leitura para permanecer inalterado

Variáveis ??(além de constantes) são sempre mutável, portanto, eles não implicam qualquer restrição à mutabilidade de tipos de valor.


A resposta parece não ser que a frente por isso vou reformular a pergunta.

Dada a seguir.

public struct Foo
{
    public void DoStuff(whatEverArgumentsYouLike)
    {
        // Do what ever you like to do.
    }

    // Put in everything you like - fields, constants, methods, properties ...
}

Você pode dar uma versão completa do Foo e um exemplo de uso - que podem incluir parâmetros ref e boxe - de modo que não é possível reescrever todas as ocorrências de

foo.DoStuff(whatEverArgumentsYouLike);

com

foo = foo.DoStuff(whatEverArgumentsYouLike);
Foi útil?

Solução

Um objeto é imutável se seu estado não muda uma vez que o objeto tem foi criado.

Resposta curta: Não, tipos de valor não são imutáveis ??por definição. Ambas as estruturas e classes podem ser mutáveis ??ou imutáveis. Todas as quatro combinações são possíveis. Se um struct ou classe tem somente leitura não-campos públicos, propriedades públicas com setters, ou métodos que estabelecem campos privados, é mutável, porque você pode mudar seu estado sem criar uma nova instância desse tipo.


Long resposta: Em primeiro lugar, a questão da imutabilidade só se aplica a estruturas ou classes com campos ou propriedades. Maioria tipos básicos (números, cordas, e nulos) são inerentemente imutável porque não há nada (campo / propriedade) para alterar sobre eles. A 5 é um 5 é uma 5. Qualquer operação no 5 só retorna outro valor imutável.

Você pode criar estruturas mutáveis ??como System.Drawing.Point. Ambos X e Y tem setters que modificam os campos da struct:

Point p = new Point(0, 0);
p.X = 5;
// we modify the struct through property setter X
// still the same Point instance, but its state has changed
// it's property X is now 5

Algumas pessoas parecem confundir immutablity com o fato de que os tipos de valor são passados ??por valor (daí seu nome) e não por referência.

void Main()
{
    Point p1 = new Point(0, 0);
    SetX(p1, 5);
    Console.WriteLine(p1.ToString());
}

void SetX(Point p2, int value)
{
    p2.X = value;
}

Neste caso Console.WriteLine() escreve "{X=0,Y=0}". Aqui p1 não foi modificado porque SetX() modificado p2 que é uma cópia de p1. Isso acontece porque p1 é uma tipo de valor , não porque é imutável (não é).

Por deve tipos de valor ser imutável? Muitas razões ... Consulte esta questão . Na maior parte é porque os tipos de valores mutáveis ??levar a todos os tipos de erros não tão óbvias. No exemplo acima, o programador pode ter p1 esperado para ser (5, 0) depois de chamar SetX(). Ou imagine a classificação por um valor que mais tarde pode mudar. Então sua coleção ordenada deixarão de ser classificadas como esperado. O mesmo vale para dicionários e hashes. A Fabuloso Eric Lippert ( blogue ) escreveu um série sobre imutabilidade e por que ele acredita que é o futuro do C #. Aqui está um de seus exemplos que permite você "modificar" uma variável só de leitura.


UPDATE: o seu exemplo, com:

this.readOnlyPoint.Offset(3, 4); // Is still (1, 2).

é exatamente o que Lippert referido no seu posto sobre como modificar variáveis ??somente leitura. Offset(3,4) realmente modificou um Point, mas foi um cópia de readOnlyPoint, e nunca foi atribuído a qualquer coisa, por isso é perdido.

e que é por isso que tipos de valores mutáveis ??são maus: Eles permitem que você pensar você está modificando alguma coisa, quando, por vezes, na verdade você está modificando uma cópia, o que leva a erros inesperados . Se Point era imutável, Offset() teria que voltar uma nova Point, e você não teria sido capaz de atribuí-la a readOnlyPoint. E então você vai "Ah, certo, é só de leitura por uma razão. Por que eu estava tentando mudar isso? Ainda bem que o compilador me parado agora."


UPDATE: Sobre o seu pedido reformulada ... Eu acho que sei o que você quer chegar. De certa forma, você pode "pensar" de estruturas como sendo internamente imutável, que a modificação de um struct é que mesmo que substituí-lo por uma cópia modificada. Pode até ser que o CLR faz internamente na memória, pelo que sei. (Que é como obras de memória flash. Você não pode editar apenas alguns bytes, você precisa ler um wbloco buraco de kilobytes na memória, modificar os poucos que quiser, e escrever todo o bloco de volta.) No entanto, mesmo se fossem "internamente imutável", que é um detalhe de implementação e para nós desenvolvedores como usuários de estruturas (de sua interface ou API , se você), eles pode ser alterado. Não podemos ignorar esse fato e "pensar neles como imutável".

Em um comentário que disse "você não pode ter uma referência para o valor do campo ou variável". Você está assumindo que cada variável struct tem uma cópia diferente, de modo que a modificação de uma cópia não afeta os outros. Isso não é inteiramente verdade. As linhas marcadas abaixo não são substituíveis se ...

interface IFoo { DoStuff(); }
struct Foo : IFoo { /* ... */ }

IFoo otherFoo = new Foo();
IFoo foo = otherFoo;
foo.DoStuff(whatEverArgumentsYouLike); // line #1
foo = foo.DoStuff(whatEverArgumentsYouLike); // line #2

Linhas # 1 e # 2 não tem os mesmos resultados ... Por quê? Porque foo e otherFoo referem-se ao mesma instância boxed de Foo. Tudo o que é mudado em foo na linha # 1 reflete em otherFoo. Linha # 2 substitui foo com um novo valor e não faz nada para otherFoo (assumindo que DoStuff() retorna uma nova instância IFoo e não modifica-se foo).

Foo foo1 = new Foo(); // creates first instance
Foo foo2 = foo1; // create a copy (2nd instance)
IFoo foo3 = foo2; // no copy here! foo2 and foo3 refer to same instance

Modificando foo1 não afetará foo2 ou foo3. Modificando foo2 refletirá em foo3, mas não em foo1. Modificando foo3 refletirá em foo2 mas não em foo1.

confuso? Vara para tipos de valores imutáveis ??e você elimina o desejo de modificar qualquer um deles.


UPDATE: Corrigido um erro no primeiro exemplo de código

Outras dicas

mutabilidade e valor tipos são duas coisas separadas.

A definição de um tipo como um tipo de valor, indica que o tempo de execução irá copiar os valores em vez de uma referência para o tempo de execução. Mutabilidade, por outro lado, depende da implementação, e cada classe pode implementar-lo como ele quer.

Você pode escrever estruturas que são mutáveis, mas é a melhor prática para fazer tipos de valores imutáveis.

Por exemplo DateTime sempre cria novas instâncias ao fazer qualquer operação. Ponto é mutável e pode ser alterado.

Para responder à sua pergunta: Não, eles não são imutáveis ??por definição, isso depende do caso se eles devem ser mutáveis ??ou não. Por exemplo, se eles devem servir como dicionário chaves, eles devem ser imutáveis.

Se você tomar a sua lógica longe o suficiente, então todas tipos são imutáveis. Quando você modifica um tipo de referência, você poderia argumentar que você está realmente escrevendo um novo objeto para o mesmo endereço, em vez de modificar nada.

Ou você poderia argumentar que tudo é mutável, em qualquer idioma, porque ocasionalmente memória que anteriormente tinha sido usado para uma coisa, será substituído por outro.

Com abstrações suficiente, e ignorando recursos de linguagem suficientes, você pode chegar a qualquer conclusão que você gosta.

E que perde o ponto. De acordo com .NET spec, tipos de valor são mutáveis. Você pode modificá-lo.

int i = 0;
Console.WriteLine(i); // will print 0, so here, i is 0
++i;
Console.WriteLine(i); // will print 1, so here, i is 1

mas ainda é o mesmo i. O i variável só é declarada uma vez. Tudo o que acontece com ele após esta declaração é uma modificação.

Em algo como uma linguagem funcional com variáveis ??imutáveis, isso não seria legal. A ++ i não seria possível. Uma vez que uma variável tenha sido declarada, ele tem um valor fixo.

Em .NET, que não é o caso, não há nada para me impedir de modificar o i depois de ter sido declarado.

Depois de pensar um pouco mais, aqui está outro exemplo que poderia ser melhor:

struct S {
  public S(int i) { this.i = i == 43 ? 0 : i; }
  private int i;
  public void set(int i) { 
    Console.WriteLine("Hello World");
    this.i = i;
  }
}

void Foo {
  var s = new S(42); // Create an instance of S, internally storing the value 42
  s.set(43); // What happens here?
}

Na última linha, de acordo com a sua lógica, poderíamos dizer que nós realmente construir um novo objeto, e substituir o antigo com esse valor. Mas isso não é possível! Para construir um novo objeto, o compilador tem que definir a variável i a 42. Mas é particular! Ela só é acessível através de um construtor definido pelo utilizador, o que não permite explicitamente o valor 43 (definindo-o para 0 em vez), e, em seguida, através do nosso método set, o que tem um efeito secundário desagradável. O compilador não tem como apenas a criação de um novo objeto com os valores que ela gosta. A única maneira em que s.i pode ser ajustado para 43 é de modificar o objeto atual chamando set(). O compilador não pode simplesmente fazer isso, porque isso mudaria o comportamento do programa (que iria imprimir para o console)

Assim, para todas as estruturas para ser imutável, o compilador teria que enganar e quebrar as regras da linguagem. E, claro, se estamos dispostos a quebrar as regras, podemos provar nada. Eu poderia provar que todos os inteiros são iguais também, ou que definir uma nova classe fará com que seu computador para pegar fogo. Enquanto nós ficar dentro das regras da língua, estruturas são mutáveis.

Eu não quero complicar o raciocínio sobre isso, considerando ref parâmetros e boxe. Também estou ciente que expressa p = p.Offset(3, 4); imutabilidade muito melhor do que p.Offset(3, 4); faz. Mas o questão permanece - não são tipos de valor imutáveis ??por definição?

Bem, então você não está realmente operando no mundo real, não é? Na prática, a propensão dos tipos de valor para fazer cópias de si mesmos como eles se movem entre as funções de malhas bem com a imutabilidade, mas eles não são realmente imutáveis ??a menos que você torná-los imutáveis, uma vez que, como você apontou, você pode usar referências a eles apenas como qualquer outra coisa.

não são tipos de valores imutáveis ??por definição?

Não, eles não são:. Se você olhar para a estrutura System.Drawing.Point por exemplo, tem um setter, bem como um getter em sua propriedade X

No entanto, pode ser verdade dizer que todos os tipos de valor deve ser definida com APIs imutáveis.

Eu acho que a confusão é que se você tem um tipo de referência que deve agir como um tipo de valor é uma boa idéia para torná-lo imutável. Uma das principais diferenças entre tipos de valor e tipos de referência é que uma alteração feita através de um nome em um tipo ref pode aparecer em outro nome. Isso não acontece com tipos de valor:

public class foo
{
    public int x;
}

public struct bar
{
    public int x;
}


public class MyClass
{
    public static void Main()
    {
        foo a = new foo();
        bar b = new bar();

        a.x = 1;
        b.x = 1;

        foo a2 = a;
        bar b2 = b;

        a.x = 2;
        b.x = 2;

        Console.WriteLine( "a2.x == {0}", a2.x);
        Console.WriteLine( "b2.x == {0}", b2.x);
    }
}

Produz:

a2.x == 2
b2.x == 1

Agora, se você tem um tipo que você gostaria de ter semântica de valor, mas não querem realmente torná-lo um tipo de valor - talvez porque o armazenamento que requer é muito ou qualquer outra coisa, você deve considerar que a imutabilidade faz parte do design. Com um tipo de ref imutável, qualquer alteração feita uma referência existente produz um novo objeto em vez de mudança do existente, de modo a obter o comportamento do tipo de valor que o valor que você está segurando não pode ser alterado através de algum outro nome.

É claro que a classe System.String é um excelente exemplo de tal comportamento.

No ano passado eu escrevi um post sobre os problemas que você pode correr para por não fazer estruturas imutável.

O post completo pode ser lido aqui

Este é um exemplo de como as coisas podem dar errado:

//Struct declaration:

struct MyStruct
{
  public int Value = 0;

  public void Update(int i) { Value = i; }
}

Exemplo de código:

MyStruct[] list = new MyStruct[5];

for (int i=0;i<5;i++)
  Console.Write(list[i].Value + " ");
Console.WriteLine();

for (int i=0;i<5;i++)
  list[i].Update(i+1);

for (int i=0;i<5;i++)
  Console.Write(list[i].Value + " ");
Console.WriteLine();

A saída desse código é:

0 0 0 0 0
1 2 3 4 5

Agora vamos fazer o mesmo, mas substituir a matriz para uma List<> genérico:

List<MyStruct> list = new List<MyStruct>(new MyStruct[5]); 

for (int i=0;i<5;i++)
  Console.Write(list[i].Value + " ");
Console.WriteLine();

for (int i=0;i<5;i++)
  list[i].Update(i+1);

for (int i=0;i<5;i++)
  Console.Write(list[i].Value + " ");
Console.WriteLine();

A saída é:

0 0 0 0 0
0 0 0 0 0

A explicação é muito simples. Não, não é boxe / unboxing ...

Ao acessar elementos de uma matriz, o tempo de execução terá os elementos da matriz diretamente, portanto, o método Update () trabalha no próprio item da matriz. Isto significa que as próprias estruturas na matriz são atualizados.

No segundo exemplo, foi utilizado um List<> genérico. O que acontece quando acessar um elemento específico? Bem, a propriedade do indexador é chamado, que é um método. Tipos de valor são sempre copiados quando retornado por um método, por isso é exatamente o que acontece: método indexador da lista recupera a estrutura de uma matriz interna e retorna para o chamador. Porque se trata de um tipo de valor, uma cópia será feita, eo método Update () será chamado na cópia, o que obviamente não tem efeito sobre itens originais da lista.

Em outras palavras, certifique-se sempre as suas estruturas são imutáveis, porque você nunca tem certeza quando uma cópia será feita. Na maioria das vezes, é óbvio, mas em alguns casos pode realmente surpreendê-lo ...

Não, eles não são. Exemplo:

Point p = new Point (3,4);
Point p2 = p;
p.moveTo (5,7);

Neste exemplo moveTo() é uma in-place operação. Ele altera a estrutura que se esconde atrás do p referência. Você pode ver que por olhada p2: É posição também terá mudado. Com estruturas imutáveis, moveTo() teria que voltar uma nova estrutura:

p = p.moveTo (5,7);

Agora, Point é imutável e quando você cria uma referência a ele em qualquer lugar no seu código, você não terá nenhuma surpresa. Vamos olhar i:

int i = 5;
int j = i;
i = 1;

Este é diferente. i não é imutável, 5 é. E a segunda tarefa não copia uma referência para a estrutura que contém i mas ele copia o conteúdo de i. Então, nos bastidores, algo completamente diferente acontece:. Você recebe uma cópia completa da variável em vez de apenas uma cópia do endereço na memória (a referência)

Um equivalente com objetos seria o construtor de cópia:

Point p = new Point (3,4);
Point p2 = new Point (p);

Aqui, a estrutura interna do p é copiado para um novo objeto / estrutura e p2 conterá a referência a ele. Mas esta é uma operação muito cara (ao contrário da atribuição de número inteiro acima) que é por isso que a maioria das linguagens de programação fazer a distinção.

Como os computadores se tornam mais poderosos e obter mais memória, essa distinção vai para ir embora porque provoca uma enorme quantidade de bugs e problemas. Na geração seguinte, haverá apenas objetos imutáveis, qualquer operação será protegido por uma transação e até mesmo um int será um objeto desenvolvido. Assim como coleta de lixo, ele vai ser um grande passo em frente na estabilidade do programa, causar muita dor nos primeiros anos, mas permitirá a escrever software confiável. Hoje, os computadores simplesmente não são rápidos o suficiente para isso.

Não, tipos de valor são não imutáveis ??por definição.

Em primeiro lugar, devo melhor ter feito a pergunta "tipos de valor se comportam como tipos imutáveis?" em vez de perguntar se eles são imutáveis ??- Suponho que isso causou muita confusão

.
struct MutableStruct
{
    private int state;

    public MutableStruct(int state) { this.state = state; }

    public void ChangeState() { this.state++; }
}

struct ImmutableStruct
{
    private readonly int state;

    public MutableStruct(int state) { this.state = state; }

    public ImmutableStruct ChangeState()
    {
        return new ImmutableStruct(this.state + 1);
    }
}

[Continua ...]

Para definir se um tipo é mutável ou imutável, é preciso definir o que esse "tipo" está se referindo. Quando um local de armazenamento do tipo de referência é declarada, a declaração apenas aloca espaço para armazenar uma referência a um objeto armazenado em outro lugar; a declaração não cria o objeto real em questão. No entanto, na maioria dos contextos em que se fala sobre determinados tipos de referência, um não será falar de um local de armazenamento que contém uma referência , mas o objeto identificado pelo que a referência . O fato de que um pode escrever para um local de armazenamento mantendo uma referência a um objeto implica de modo algum que o objeto em si é mutável.

Por outro lado, quando um local de armazenamento do tipo de valor é declarado, o sistema irá alocar dentro desse local de armazenamento locais de armazenamento aninhados para cada público ou privado campo realizada por esse tipo de valor. Tudo sobre o tipo de valor é realizada nesse local de armazenamento. Se se define uma variável do tipo foo Point e seus dois campos, X e Y, espera 3 e 6, respectivamente. Se alguém define a "instância" de Point em foo como sendo o par de campos , essa instância será mutável se e somente se foo é mutável. Se se define um exemplo de Point como sendo o valores realizada nesses campos (por exemplo, "3,6"), em seguida, um tal exemplo é por definição imutável, desde alterar um desses campos causaria a Point realizar uma instância diferente.

Eu acho que é mais útil pensar em um tipo de valor "instância" como sendo os campos, em vez dos valores que possuem. Por essa definição, qualquer tipo de valor armazenado em um local de armazenamento mutável, e para a qual exista qualquer valor não-padrão, irá sempre ser mutável, independentemente de como ela é declarada. A MyPoint = new Point(5,8) declaração constrói uma nova instância do Point, com campos X=5 e Y=8, e depois se transforma MyPoint substituindo os valores em seus campos com os da Point recém-criado. Mesmo que um struct fornece nenhuma maneira de modificar qualquer um dos seus campos fora do seu construtor, não há nenhuma maneira um tipo struct pode proteger uma instância de ter todos os seus campos substituídos pelo conteúdo de outra instância.

Aliás, um exemplo simples onde uma estrutura mutável pode atingir semântica não realizáveis ??através de outros meios: Assumindo myPoints[] é uma matriz de elemento único que é acessível a múltiplos fios, ter vinte fios simultaneamente executar o código:

Threading.Interlocked.Increment(myPoints[0].X);

Se myPoints[0].X começa igual a zero e vinte tópicos executar o código acima, seja simultaneamente ou não, myPoints[0].X será igual a vinte. Se fosse para tentar imitar o código acima com:

myPoints[0] = new Point(myPoints[0].X + 1, myPoints[0].Y);

, em seguida, se qualquer tópico leia myPoints[0].X entre o momento em outro segmento lê-lo e escreveu de volta o valor revisto, os resultados do incremento seria perdido (com a consequência de que myPoints[0].X poderia arbitrariamente acabar com qualquer valor entre 1 e 20.

Objetos / Structs são imutáveis ??quando eles são passados ??para uma função de tal forma que os dados não podem ser alterados, e a estrutura retornado é um struct new. O exemplo clássico é

String s = "abc";

s.toLower();

Se a função toLower é escrito para que uma nova string é devolvida que substitui "s", que é imutável, mas se a função vai letra por letra substituindo a carta dentro "s" e nunca declarando uma "nova String", ele é mutável.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top