오류 :“반환 값을 수정할 수 없습니다”c #


155

자동 구현 속성을 사용하고 있습니다. 다음을 수정하는 가장 빠른 방법은 내 백업 변수를 선언하는 것입니다.

public Point Origin { get; set; }

Origin.X = 10; // fails with CS1612

오류 메시지 : 'expression'의 반환 값은 변수가 아니므로 수정할 수 없습니다.

중간 표현식의 결과 인 값 유형을 수정하려고했습니다. 값이 유지되지 않으므로 값이 변경되지 않습니다.

이 오류를 해결하려면 표현식 결과를 중간 값으로 저장하거나 중간 표현식에 참조 유형을 사용하십시오.


13
이것은 왜 가변 값 유형이 나쁜 생각인지에 대한 또 다른 예입니다. 값 유형을 변경하지 않으려면 그렇게하십시오.
Eric Lippert

값 유형 변경을 피할 수없는 다음 코드 (특정 EL :-에 의해 블로그 된 AStar 구현에서 나의 노력에서)를 가져옵니다. class Path <T> : IEnumerable <T> 여기서 T : INode, new () {. ..} public HexNode (int x, int y) : this (new Point (x, y)) {} Path <T> path = new Path <T> (new T (x, y)); // 오류 // 못생긴 경로 수정 Path <T> path = new Path <T> (new T ()); path.LastStep.Centre = 새로운 점 (x, y);
톰 윌슨

답변:


198

Point값 유형 ( struct) 이기 때문 입니다 .

이로 인해 Origin속성에 액세스 하면 참조 유형 ( )에서 와 같이 값 자체가 아닌 클래스가 보유한 값 의 사본 에 액세스 class하므로 X속성을 설정하면 설정됩니다. 복사본의 속성을 삭제 한 다음 버리고 원래 값은 변경하지 않습니다. 이것은 아마도 의도 한 것이 아니기 때문에 컴파일러가 경고합니다.

X값만 변경하려면 다음과 같이해야합니다.

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

2
@Paul : 구조체를 클래스로 변경할 수 있습니까?
Doug

1
이것은 일종의 귀찮은이다 IM 할당 세터 재산권 부작용을 갖기 때문에 (구조체는 백킹 참조 형식으로도 역할)
알렉산더 - 분석 재개 모니카

또 다른 해결책은 단순히 구조체를 클래스로 만드는 것입니다. 클래스와 구조체가 기본 멤버 액세스 (개인 및 공개)에 의해서만 다른 C ++과 달리 C #의 구조체와 클래스에는 몇 가지 차이점이 더 있습니다. 여기에 더 많은 정보가 있습니다 : docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
Artorias2718

9

지지 변수를 사용하면 도움이되지 않습니다. Point유형은 값 형식입니다.

전체 Point 값을 Origin 속성에 할당해야합니다.

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

문제는 Origin 속성에 액세스 할 때 반환되는 get것은 Origin 속성 자동 생성 필드에 Point 구조의 복사본이라는 것입니다. 따라서 X 필드를 수정하면이 사본은 기본 필드에 영향을 미치지 않습니다. 컴파일러는이를 감지하여이 작업이 완전히 쓸모 없기 때문에 오류를 발생시킵니다.

자신의 백업 변수를 사용하더라도 get다음과 같습니다.

get { return myOrigin; }

여전히 Point 구조의 복사본을 반환하고 동일한 오류가 발생합니다.

흠 ... 당신의 질문을 더 신중하게 읽으면 실제로 클래스 내에서 직접 백업 변수를 수정하는 것을 의미 할 것입니다.

myOrigin.X = 10;

네, 그게 필요할 것입니다.


6

이제 오류의 원인이 무엇인지 이미 알고 있습니다. 오버로드가있는 생성자가 존재하지 않아 속성을 가져 오는 경우 (이 경우 X) 객체 이니셜 라이저를 사용하면 장면 뒤의 모든 마술을 수행 할 수 있습니다. 구조체를 불변으로 만들 필요는 없지만 추가 정보를 제공하면됩니다.

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.

이것은 배경 뒤에서 발생하기 때문에 가능합니다.

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

이것은 전혀 권장되지 않는 매우 이상한 일처럼 보입니다 . 다른 방법으로 만 나열하십시오. 더 좋은 방법은 구조체를 불변으로 만들고 적절한 생성자를 제공하는 것입니다.


2
그 가치를 버리지 Origin.Y않습니까? 유형의 속성을 감안할 때 Point, 나는 변화하는 관용적 방법은 그저 생각할 X것입니다 var temp=thing.Origin; temp.X = 23; thing.Origin = temp;. 관용적 접근 방식은 수정하고 싶지 않은 멤버를 언급 할 필요가 없다는 장점이 있으며, 이는 Point변경 가능하기 때문에 가능한 기능입니다 . 나는 컴파일러 Origin.X = 23;가 코드를 요구하는 구조체를 설계 할 수 없기 때문에 철학에 의아해 한다 Origin.X = new Point(23, Origin.Y);. 후자는 나에게 정말 불안해 보인다.
supercat

@ supercat 이것은 내가 당신의 요점을 처음 생각하는 것 입니다. 이를 해결하기위한 대체 패턴 / 디자인 아이디어가 있습니까? 쉽게 (이 경우에 나는 엄격하게 모두 통과해야 C 번호는 기본적으로 구조체의 기본 생성자를 제공하지 있었다되었을 것입니다 XY특정 생성자를). 이제는 할 수있는 시점을 잃습니다 Point p = new Point(). 구조체에 실제로 필요한 이유를 알고 있으므로 그것을 생각할 필요는 없습니다. 그러나 하나의 속성 만 업데이트하는 멋진 아이디어가 X있습니까?
nawfal

독립적이지만 관련이있는 변수 (예 : 점 좌표)의 모음을 캡슐화하는 구조체의 경우, 구조체가 모든 멤버를 공개 필드로 표시하도록하는 것이 좋습니다. 구조체 속성의 멤버 하나를 수정하려면 간단히 읽고, 멤버를 수정 한 후 다시 쓰십시오. C #에서 매개 변수 목록이 필드 목록과 일치하는 생성자를 자동으로 정의하는 "단순 Plain-Old-Data-Struct"선언을 제공했다면 C #을 무시할 수있는 구조체를 무시합니다.
supercat

@ supercat 나는 그것을 얻는다. 구조체와 클래스의 일관되지 않은 동작은 혼란 스럽습니다.
nawfal

혼란은 모든 것이 클래스 객체처럼 동작해야한다는 IMHO의 도움이되지 않는 믿음으로 인해 발생합니다. 힙 개체 참조가 필요한 항목에 값 형식 값을 전달하는 방법을 사용하는 것이 유용하지만 값 형식 변수에서 파생되는 항목을 보유하는 척은 유용하지 않습니다 Object. 그들은하지 않습니다. 모든 값 유형 정의는 실제로 저장 위치 유형 (변수, 배열 슬롯 등에 사용됨) 및 힙 객체 유형 (때때로 "상자 형"유형이라고도 함) (값 유형 값일 때 사용됨)의 두 가지 유형을 정의합니다. 참조 유형 위치에 저장됩니다).
supercat

2

구조체 대 클래스의 장단점을 토론하는 것 외에도 목표를보고 그 관점에서 문제에 접근하는 경향이 있습니다.

즉, 속성 get 및 set 메소드 뒤에 코드를 작성할 필요가 없다면 (예에서와 같이) Origin속성이 아니라 클래스의 필드로 간단히 선언하는 것이 쉽지 않을까요? 나는 이것이 당신이 당신의 목표를 달성 할 수 있다고 생각해야합니다.

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

0

문제는 스택에있는 값을 가리키고 값이 orignal 속성으로 다시 영향을 미치지 않으므로 C #에서는 값 형식에 대한 참조를 반환 할 수 없다는 것입니다. Origin 속성을 제거 하여이 문제를 해결할 수 있다고 생각하고 대신 공용 파일을 사용하십시오. 예, 좋은 해결책이 아니라는 것을 알고 있습니다. 다른 해결책은 Point를 사용하지 않고 대신 자신 만의 Point 유형을 객체로 생성하는 것입니다.


(가) 경우 Point, 기준 형의 부재 후 스택에 없을 것, 그것이 포함하는 객체의 메모리의 힙 것이다.
Greg Beech

0

여기서 잡는 것은 객체 자체를 할당하는 것이 아니라 명령문에서 객체의 하위 값을 할당하려고한다는 것입니다. 이 경우 속성 유형이 Point이므로 전체 Point 객체를 할당해야합니다.

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

내가 이해가 되길 바래


2
중요한 점은 Point가 구조체 (값 유형)라는 것입니다. 그것이 클래스 (객체)라면 원래 코드가 작동했을 것입니다.
Hans Ke st ing

@HansKesting : Point변경 가능한 클래스 유형 인 경우 원래 코드는 property X가 반환 한 객체에서 필드 또는 속성 을 설정했을 것 Origin입니다. 나는 그것이 Origin속성을 포함하는 객체에 원하는 효과를 줄 것이라고 믿을 이유가 없다 . 일부 Framework 클래스에는 상태를 새로운 가변 클래스 인스턴스에 복사하여 반환하는 속성이 있습니다. 이러한 디자인은 코드 thing1.Origin = thing2.Origin;가 객체의 원산지 상태를 다른 원산지의 상태 와 동일 하게 설정하는 것과 같은 이점을 가지지 만, 코드와 같은 경고는 할 수 없습니다 thing1.Origin.X += 4;.
supercat

0

다음과 같이 "get set"속성을 제거하면 모든 것이 항상 작동합니다.

기본 유형이 겹친 경우 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]

    }
}      

0

많은 사람들이 여기에서 혼란스러워하고 있다고 생각합니다. 이 특별한 문제는 값 유형 속성 이 값 유형 의 복사본을 반환하고 메서드 및 인덱서와 마찬가지로 값 유형 필드 에 직접 액세스 한다는 것을 이해하는 것과 관련 이 있습니다 . 다음 코드는 속성의 백업 필드에 직접 액세스하여 달성하려는 작업을 정확하게 수행합니다 (참고 : 백업 필드를 사용하여 자세한 형식으로 속성을 표현하는 것은 자동 속성과 동일하지만 코드에서 우리가 할 수있는 이점이 있습니다) 지원 필드에 직접 액세스) :

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;
    }
}

오류가 발생하면 속성이 값 형식의 복사본을 반환한다는 것을 이해하지 못해 간접적으로 발생합니다. 값 유형의 사본이 리턴되고이를 로컬 변수에 지정하지 않으면 해당 사본에 대한 변경 사항을 읽을 수 없으므로 컴파일러는이를 의도하지 않으므로 오류로 발생시킵니다. 복사본을 로컬 변수에 할당하면 X 값을 변경할 수 있지만 로컬 복사본에서만 변경되어 컴파일 시간 오류를 수정하지만 원점 속성을 수정하는 원하는 효과는 없습니다. 다음 코드는 컴파일 오류가 발생했지만 디버그 어설 션이 실패하기 때문에이를 보여줍니다.

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
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.