Помилка: "Неможливо змінити повернене значення" c #


155

Я використовую властивості, реалізовані автоматично. Я думаю, найшвидший спосіб виправити наступне - оголосити власну змінну резервного копіювання?

public Point Origin { get; set; }

Origin.X = 10; // fails with CS1612

Повідомлення про помилку: Неможливо змінити повернене значення 'вираження', оскільки воно не є змінною

Була зроблена спроба змінити тип значення, який був результатом проміжного вираження. Оскільки значення не зберігається, значення буде незмінним.

Щоб усунути цю помилку, збережіть результат вираження в проміжному значенні або використовуйте тип посилання для проміжного виразу.


13
Це ще одна ілюстрація того, чому типи змінних значень є поганою ідеєю. Якщо ви не можете мутувати тип значення, зробіть це.
Ерік Ліпперт

Візьміть наступний код (з моїх зусиль у впровадженні програми AStar, що блогується певним EL :-), який не міг уникнути зміни типу значення: class Path <T>: IEnumerable <T> where T: INode, new () {. ..} public HexNode (int x, int y): це (нова точка (x, y)) {} Шлях <T> шлях = новий Шлях <T> (новий T (x, y)); // Помилка // Ugly fix 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: Чи є у вас можливість змінити структуру на клас?
Дуг

1
Це своєрідний об’єм, тому що налаштування властивостей при призначенні має побічний ефект (структура виступає як перегляд типового посилання)
Олександр - Поновіть Моніку

Ще одне рішення - просто перетворити структуру в клас. На відміну від C ++, де клас і структура відрізняються лише тимчасовим доступом до членів (приватний та загальнодоступний відповідно), структури та класи в C # мають ще кілька відмінностей. Ось ще інформація: docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
Artorias2718

9

Використання резервної змінної не допоможе. PointТип являє собою тип значення.

Вам потрібно призначити властивість Origin ціле значення Point: -

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

Проблема полягає в тому, що коли ви отримуєте доступ до властивості Origin, те, що повертається, getє копією структури Point у полі створеного властивостями Origin. Отже, ваша зміна поля 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 # не надав конструктору за замовчуванням структуру за замовчуванням (в такому випадку я строго повинен передавати Xі Yконкретний конструктор). Тепер він втрачає момент, коли можна це зробити Point p = new Point(). Я знаю, чому його дійсно потрібно для структури, так що немає сенсу думати про це. Але у вас крута ідея оновити лише одну властивість, як-от X?
nawfal

Що стосується структур, які містять сукупність незалежних, але пов'язаних між собою змінних (таких як координати точки), я віддаю перевагу просто структурі виставити всіх її членів як публічні поля; щоб змінити один член властивості структури, просто прочитайте його, модифікуйте член і запишіть його назад. Було б добре, якби C # надавав декларацію "звичайна звичайна структура даних", яка автоматично визначала б конструктор, список параметрів якого відповідає списку полів, але люди, відповідальні за C #, зневажають змінні структури.
supercat

@supercat Я розумію. Послідовність непорушної поведінки структур та класів є заплутаною.
nawfal

Плутанина випливає з недоброзичливої ​​віри ІМХО, що все має вести себе як об'єкт класу. Хоча корисно мати засоби передачі значень типу значень речам, на які очікуються посилання на об'єкти купи, не корисно робити вигляд, що змінні типу значень містять речі, які походять від цього Object. Вони ні. Кожне визначення типу значення фактично визначає два види речей: тип місця зберігання (використовується для змінних, слотів масиву тощо) та тип об'єкта купи, який іноді називають типом "в коробці" (використовується, коли значення типу значення зберігається до місця розташування опорного типу).
supercat

2

Крім обговорення плюсів і мінусів структур проти класів, я схильний розглядати мету і підходити до проблеми з цієї точки зору.

Якщо говорити, якщо вам не потрібно писати код за властивістю get і встановлювати методи (як у вашому прикладі), то чи не буде простіше просто оголосити 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

Проблема полягає в тому, що ви вказуєте на значення, розташоване в стеку, і значення не буде повернено до початкового властивості, тому C # не дозволяє повернути посилання на тип значення. Я думаю, що ви можете вирішити це, видаливши властивість Origin і замість цього скориставшись загальнодоступною подачею, так, я знаю, що це не приємне рішення. Іншим рішенням є не використовувати точку, а замість цього створити власний тип точки як об’єкт.


Якщо Pointелемент є еталонним типом, він не буде знаходитися в стеці, він буде знаходитися в купі пам'яті, що містить об'єкт.
Грег Бук

0

Я здогадуюсь тут, що ви намагаєтеся призначити суб-значення об'єкта в операторі, а не призначати сам об'єкт. У цьому випадку потрібно призначити весь об’єкт Point, оскільки типом властивості є Point.

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

Сподіваюся, я мав сенс там


2
Важливим моментом є те, що Point є структурою (вартієтип). Якби це був клас (об’єкт), то початковий код працював би.
Ганс Ке Ґінґ

@HansKesting: Якби Pointтип мутаційного класу мінявся , оригінальний код мав би встановити поле або властивість Xв об'єкті, повернутий властивістю Origin. Я не бачу причин вважати, що це матиме бажаний ефект на об'єкт, що містить Originвластивість. Деякі класи Framework мають властивості, які копіюють їх стан у нові екземпляри змінних класів та повертають їх. Така конструкція має перевагу в тому, що дозволяє коду як thing1.Origin = thing2.Origin;би встановити стан походження об'єкта відповідно до іншого, але він не може попереджати про подібний код thing1.Origin.X += 4;.
supercat

0

Просто видаліть властивість "встановити" як слід, і тоді все працює як завжди.

У випадку примітивних типів instread використовуйте 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, але воно буде змінено лише на локальній копії, яка виправить помилку часу компіляції, але не матиме бажаного ефекту змінити властивість Origin. Наступний код ілюструє це, оскільки помилка компіляції відсутня, але твердження про налагодження не вдасться:

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
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.