Erro: "Não é possível modificar o valor de retorno" c #

155

Estou usando propriedades implementadas automaticamente. Eu acho que a maneira mais rápida de corrigir o seguinte é declarar minha própria variável de backup?

public Point Origin { get; set; }

Origin.X = 10; // fails with CS1612

Mensagem de erro: Não é possível modificar o valor de retorno de 'expressão' porque não é uma variável

Foi feita uma tentativa de modificar um tipo de valor que era o resultado de uma expressão intermediária. Como o valor não é persistente, o valor não será alterado.

Para resolver esse erro, armazene o resultado da expressão em um valor intermediário ou use um tipo de referência para a expressão intermediária.

Paulo
fonte
13
Esta é mais uma ilustração de por que tipos de valores mutáveis ​​são uma má idéia. Se você pode evitar a mutação de um tipo de valor, faça-o.
Eric Lippert
Pegue o código a seguir (dos meus esforços em uma implementação do AStar blogada por um determinado EL :-), que não pôde evitar a alteração de um tipo de valor: class Path <T>: IEnumerable <T> em que T: INode, new () {. ..} public HexNode (int x, int y): this (novo ponto (x, y)) {} Caminho <T> caminho = novo caminho <T> (novo T (x, y)); // Erro // Correção feia Path <T> path = new Path <T> (new T ()); path.LastStep.Centre = novo ponto (x, y);
Tom Wilson #

Respostas:

198

Isso ocorre porque Pointé um tipo de valor ( struct).

Por esse motivo, ao acessar a Originpropriedade, você está acessando uma cópia do valor mantido pela classe, e não o valor em si, como faria com um tipo de referência ( class); portanto, se você definir a Xpropriedade nela, estará configurando a propriedade na cópia e, em seguida, descartá-la, mantendo o valor original inalterado. Provavelmente não é isso que você pretendia, e é por isso que o compilador está avisando sobre isso.

Se você deseja alterar apenas o Xvalor, precisa fazer algo assim:

Origin = new Point(10, Origin.Y);
Greg Beech
fonte
2
@Paul: Você tem a capacidade de alterar a estrutura para uma classe?
Doug
1
Este é o tipo de vadio, porque a propriedade intelectual im atribuição tem um efeito colateral (a estrutura age como uma vista em um tipo de referência suporte)
Alexander - Restabeleça Monica
Outra solução é simplesmente transformar sua estrutura em uma classe. Diferente do C ++, onde uma classe e uma estrutura diferem apenas pelo acesso de membro padrão (privado e público, respectivamente), estruturas e classes em C # têm mais algumas diferenças. Aqui está mais algumas informações: docs.microsoft.com/en-us/dotnet/csharp/programming-guide/...
Artorias2718
9

Usar uma variável de apoio não ajudará. O Pointtipo é um tipo de valor.

Você precisa atribuir todo o valor do ponto à propriedade Origin:

Origin = new Point(10, Origin.Y);

O problema é que, quando você acessa a propriedade Origin, o que é retornado geté uma cópia da estrutura Point no campo criado automaticamente pelas propriedades Origin. Portanto, sua modificação do campo X nesta cópia não afetaria o campo subjacente. O compilador detecta isso e gera um erro, pois essa operação é totalmente inútil.

Mesmo se você usou sua própria variável de apoio, sua getaparência seria: -

get { return myOrigin; }

Você ainda retornaria uma cópia da estrutura Point e obteria o mesmo erro.

Hmm ... depois de ler sua pergunta com mais cuidado, talvez você queira modificar a variável de apoio diretamente de sua classe: -

myOrigin.X = 10;

Sim, isso seria o que você precisaria.

AnthonyWJones
fonte
6

Até agora você já sabe qual é a fonte do erro. Caso um construtor não exista com uma sobrecarga para ocupar sua propriedade (nesse caso X), você pode usar o inicializador de objetos (que fará toda a mágica nos bastidores). Não que você não precise imutar suas estruturas , mas apenas fornecendo informações adicionais:

struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

class MyClass
{
    public Point Origin { get; set; }
}

MyClass c = new MyClass();
c.Origin.X = 23; //fails.

//but you could do:
c.Origin = new Point { X = 23, Y = c.Origin.Y }; //though you are invoking default constructor

//instead of
c.Origin = new Point(23, c.Origin.Y); //in case there is no constructor like this.

Isso é possível porque nos bastidores isso acontece:

Point tmp = new Point();
tmp.X = 23;
tmp.Y = Origin.Y;
c.Origin = tmp;

Parece uma coisa muito estranha de se fazer, nem um pouco recomendada. Apenas listando uma maneira alternativa. A melhor maneira de fazer é tornar o struct imutável e fornecer um construtor adequado.

nawfal
fonte
2
Isso não descartaria o valor de Origin.Y? Dada uma propriedade do tipo Point, eu pensaria que a maneira idiomática de mudar Xseria var temp=thing.Origin; temp.X = 23; thing.Origin = temp;. A abordagem idiomática tem a vantagem de não precisar mencionar os membros que não deseja modificar, um recurso que só é possível porque Pointé mutável. Estou intrigado com a filosofia que diz que, porque o compilador não pode permitir que Origin.X = 23;se deva projetar uma estrutura para exigir código como Origin.X = new Point(23, Origin.Y);. O último parece realmente nojento para mim.
supercat
@ supercat esta é a primeira vez que penso no seu ponto, faz muito sentido! Você tem uma idéia alternativa de design / padrão para resolver isso? Ele teria sido se mais fácil C # não, desde que o construtor padrão para um struct por padrão (nesse caso eu estritamente tem que passar tanto Xe Yao construtor específico). Agora, perde o ponto quando se pode fazer Point p = new Point(). Eu sei por que é realmente necessário para uma estrutura, então não faz sentido pensar nisso. Mas você tem uma ideia interessante de atualizar apenas uma propriedade como X?
Nawfal
Para estruturas que encapsualam uma coleção de variáveis ​​independentes, mas relacionadas (como coordenadas de um ponto), minha preferência é simplesmente fazer com que a estrutura exponha todos os seus membros como campos públicos; para modificar um membro de uma propriedade struct, simplesmente leia, modifique o membro e escreva-o novamente. Seria bom se o C # tivesse fornecido uma declaração "Simples e antiga de estrutura de dados antiga" que definiria automaticamente um construtor cuja lista de parâmetros correspondesse à lista de campos, mas as pessoas responsáveis ​​pelo C # desprezam estruturas mutáveis.
supercat
@ supercat eu entendi. O comportamento inconsistente de estruturas e classes é confuso.
Nawfal # 25/13
A confusão resulta da crença inútil do IMHO de que tudo deve se comportar como um objeto de classe. Embora seja útil ter meios de passar valores de tipo de valor para coisas que esperam referências a objetos de pilha, não é útil fingir que variáveis ​​de tipo de valor retêm coisas das quais derivam Object. Eles não. Toda definição de tipo de valor realmente define dois tipos de coisas: um tipo de local de armazenamento (usado para variáveis, slots de matriz etc.) e um tipo de objeto de heap, às vezes chamado de tipo "em caixa" (usado quando um valor de tipo de valor é armazenado em um local do tipo de referência).
Supercat
2

Além de debater os prós e os contras de estruturas versus classes, costumo olhar para o objetivo e abordar o problema a partir dessa perspectiva.

Dito isto, se você não precisar escrever código por trás dos métodos get e set da propriedade (como no seu exemplo), não seria mais fácil simplesmente declarar o Origincampo como um campo da classe do que uma propriedade? Eu acho que isso permitiria que você atingisse seu objetivo.

struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

class MyClass
{
    public Point Origin;
}

MyClass c = new MyClass();
c.Origin.X = 23;   // No error.  Sets X just fine
Mitselplik
fonte
0

O problema é que você aponta para um valor localizado na pilha e o valor não será reconfigurado de volta para a propriedade orignal, portanto, o C # não permite retornar uma referência a um tipo de valor. Eu acho que você pode resolver isso removendo a propriedade Origin e, em vez disso, use um arquivo público, sim, eu sei que não é uma solução agradável. A outra solução é não usar o ponto e, em vez disso, criar seu próprio tipo de ponto como um objeto.

Fredrik Normén
fonte
Se Pointfor um membro de um tipo de referência, ele não estará na pilha, estará na pilha na memória do objeto que a contém.
Greg Beech
0

Eu acho que o problema aqui é que você está tentando atribuir subvalores do objeto na declaração, em vez de atribuir o próprio objeto. Você precisa atribuir o objeto Point inteiro nesse caso, pois o tipo de propriedade é Point.

Point newOrigin = new Point(10, 10);
Origin = newOrigin;

Espero ter feito sentido lá

MSIL
fonte
2
O ponto importante é que Point é uma estrutura (tipo de valor). Se fosse uma classe (objeto), o código original teria funcionado.
Hans Keing
@HansKesting: se Pointfosse um tipo de classe mutável, o código original teria definido o campo ou a propriedade Xno objeto retornado pela propriedade Origin. Não vejo razão para acreditar que isso teria o efeito desejado sobre o objeto que contém a Originpropriedade. Algumas classes do Framework têm propriedades que copiam seu estado para novas instâncias de classe mutável e as retornam. Esse design tem a vantagem de permitir que o código como thing1.Origin = thing2.Origin;defina o estado de origem do objeto para corresponder ao de outro, mas não pode alertar sobre o código como thing1.Origin.X += 4;.
Supercat
0

Apenas remova a propriedade "get set" da seguinte maneira e tudo funcionará como sempre.

No caso de tipos primitivos, instread use o get; set; ...

using Microsoft.Xna.Framework;
using System;

namespace DL
{
    [Serializable()]
    public class CameraProperty
    {
        #region [READONLY PROPERTIES]
        public static readonly string CameraPropertyVersion = "v1.00";
        #endregion [READONLY PROPERTIES]


        /// <summary>
        /// CONSTRUCTOR
        /// </summary>
        public CameraProperty() {
            // INIT
            Scrolling               = 0f;
            CameraPos               = new Vector2(0f, 0f);
        }
        #region [PROPERTIES]   

        /// <summary>
        /// Scrolling
        /// </summary>
        public float Scrolling { get; set; }

        /// <summary>
        /// Position of the camera
        /// </summary>
        public Vector2 CameraPos;
        // instead of: public Vector2 CameraPos { get; set; }

        #endregion [PROPERTIES]

    }
}      
Roberto Mutti
fonte
0

Acho que muitas pessoas estão ficando confusas aqui, esse problema específico está relacionado ao entendimento de que as propriedades do tipo de valor retornam uma cópia do tipo de valor (como nos métodos e indexadores), e os campos do tipo de valor são acessados ​​diretamente . O código a seguir faz exatamente o que você está tentando alcançar acessando diretamente o campo de apoio da propriedade (note: expressar uma propriedade em sua forma detalhada com um campo de apoio é o equivalente a uma propriedade automática, mas tem a vantagem de que em nosso código podemos acesse diretamente o campo de apoio):

class Program
{
    static void Main(string[] args)
    {
        var myClass = new MyClass();
        myClass.SetOrigin();
        Debug.Assert(myClass.Origin.X == 10); //succeeds
    }
}

class MyClass
{
    private Point _origin;
    public Point Origin
    { 
        get => _origin; 
        set => _origin = value; 
    }

    public void SetOrigin()
    {
        _origin.X = 10; //this works
        //Origin.X = 10; // fails with CS1612;
    }
}

O erro que você está recebendo é uma consequência indireta de não entender que uma propriedade retorna uma cópia de um tipo de valor. Se você receber uma cópia de um tipo de valor e não a atribuir a uma variável local, as alterações feitas nessa cópia nunca poderão ser lidas e, portanto, o compilador gera isso como um erro, pois isso não pode ser intencional. Se atribuirmos a cópia a uma variável local, podemos alterar o valor de X, mas ele será alterado apenas na cópia local, que corrige o erro do tempo de compilação, mas não terá o efeito desejado de modificar a propriedade Origin. O código a seguir ilustra isso, pois o erro de compilação desapareceu, mas a declaração de depuração falhará:

class Program
{
    static void Main(string[] args)
    {
        var myClass = new MyClass();
        myClass.SetOrigin();
        Debug.Assert(myClass.Origin.X == 10); //throws error
    }
}

class MyClass
{
    private Point _origin;
    public Point Origin
    { 
        get => _origin; 
        set => _origin = value; 
    }

    public void SetOrigin()
    {
        var origin = Origin;
        origin.X = 10; //this is only changing the value of the local copy
    }
}
Matt
fonte