Ошибка: «Невозможно изменить возвращаемое значение» c #

155

Я использую автоматически реализованные свойства. Я думаю, что самый быстрый способ исправить следующее - объявить собственную переменную поддержки?

public Point Origin { get; set; }

Origin.X = 10; // fails with CS1612

Сообщение об ошибке: невозможно изменить возвращаемое значение выражения, поскольку оно не является переменной

Была предпринята попытка изменить тип значения, являющийся результатом промежуточного выражения. Поскольку значение не сохраняется, оно не изменится.

Чтобы устранить эту ошибку, сохраните результат выражения в промежуточном значении или используйте ссылочный тип для промежуточного выражения.

Павел
источник
13
Это еще одна иллюстрация того, почему изменяемые типы значений являются плохой идеей. Если вы можете избежать изменения типа значения, сделайте это.
Эрик Липперт
Возьмите следующий код (из моих усилий по реализации AStar, опубликованных в блоге определенного EL :-), который не мог избежать изменения типа значения: 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: у вас есть возможность изменить структуру на класс?
Дуг
1
Это немного обидно, потому что присваивающий им установщик свойств имеет побочный эффект (структура действует как представление в
Александр - Восстановить Монику
Другое решение - просто превратить вашу структуру в класс. В отличие от C ++, где класс и структура отличаются только доступом к элементу по умолчанию (соответственно private и public), структуры и классы в C # имеют еще несколько отличий. Вот еще немного информации: docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
Artorias2718
9

Использование резервной переменной не поможет. PointТип представляет собой тип значения.

Вам необходимо присвоить целое значение Point свойству Origin:

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

Проблема заключается в том, что при доступе к свойству Origin то, что возвращается, getявляется копией структуры Point в автоматически создаваемом поле свойств Origin. Следовательно, ваша модификация поля X этой копии не повлияет на базовое поле. Компилятор обнаруживает это и выдает ошибку, поскольку эта операция совершенно бесполезна.

Даже если бы вы использовали свою собственную переменную поддержки, вы getбы выглядели так:

get { return myOrigin; }

Вы все равно будете возвращать копию структуры Point, и вы получите ту же ошибку.

Хм ... прочитав ваш вопрос более внимательно, возможно, вы действительно хотите изменить вспомогательную переменную прямо из вашего класса:

myOrigin.X = 10;

Да, это было бы то, что вам нужно.

AnthonyWJones
источник
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, это первый раз, когда я думаю о твоей точке зрения, имеет смысл! У вас есть альтернативный шаблон / дизайнерская идея для решения этой проблемы? Было бы проще, если бы C # не предоставил конструктор по умолчанию для структуры по умолчанию (в этом случае я строго должен передать оба Xи Yконкретному конструктору). Теперь он теряет момент, когда можно сделать Point p = new Point(). Я знаю, почему это действительно требуется для структуры, поэтому нет смысла думать об этом. Но у вас есть крутая идея обновить только одно свойство, как X?
nawfal
Для структур, которые инкапсулируют коллекцию независимых, но связанных переменных (таких как координаты точки), я предпочитаю, чтобы структура просто выставляла все свои элементы как открытые поля; чтобы изменить один член свойства структуры, просто прочитайте его, измените член и запишите его обратно. Было бы неплохо, если бы C # предоставил «простое объявление Plain-Old-Data-Struct», которое автоматически определило бы конструктор, список параметров которого соответствовал бы списку полей, но люди, ответственные за C #, презирали изменяемые структуры.
суперкат
@supercat Я понял. Непоследовательное поведение структур и классов сбивает с толку.
nawfal
Путаница проистекает из ИМХО бесполезной веры в то, что все должно вести себя как объект класса. Хотя полезно иметь средство передачи значений типа значения вещам, которые ожидают ссылки на объекты кучи, бесполезно делать вид, что переменные типа значения содержат вещи, которые являются производными Object. Они не Каждое определение типа значения фактически определяет два вида вещей: тип хранилища (используется для переменных, слотов массивов и т. Д.) И тип объекта кучи, иногда называемый «коробочным» типом (используется, когда значение типа значения хранится в местоположении ссылочного типа).
суперкат
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
Mitselplik
источник
0

Проблема в том, что вы указываете на значение, находящееся в стеке, и оно не будет возвращено обратно в свойство orignal, поэтому C # не позволяет вам возвращать ссылку на тип значения. Я думаю, что вы можете решить эту проблему, удалив свойство Origin и вместо этого воспользовавшись открытым полем, да, я знаю, что это не очень хорошее решение. Другое решение - не использовать Point, а вместо этого создать собственный тип Point в качестве объекта.

Фредрик Нормен
источник
Если Pointэто член ссылочного типа, он не будет в стеке, он будет в куче в памяти содержащего объекта.
Грег Бич
0

Я предполагаю, что выгода заключается в том, что вы пытаетесь назначить под-значения объекта в выражении, а не сам объект. В этом случае вам необходимо назначить весь объект Point, поскольку типом свойства является Point.

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

Надеюсь, у меня есть смысл

MSIL
источник
2
Важным моментом является то, что Point является структурой (тип значения). Если бы это был класс (объект), то оригинальный код сработал бы.
Ганс Кеинг
@HansKesting: Если бы Pointэто был изменяемый тип класса, исходный код установил бы поле или свойство Xв объекте, возвращаемом свойством Origin. Я не вижу оснований полагать, что это оказало бы желаемый эффект на объект, содержащий Originсвойство. Некоторые классы Framework имеют свойства, которые копируют их состояние в новые изменяемые экземпляры классов и возвращают их. Такое проектирование имеет то преимущество, что позволяет коду, например, thing1.Origin = thing2.Origin;устанавливать состояние источника объекта, совпадающему с другим, но он не может предупреждать о подобном коде thing1.Origin.X += 4;.
суперкат
0

Просто удалите свойство «get set», как показано ниже, и тогда все работает как всегда.

В случае примитивных типов 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

Я думаю, что многие люди запутываются здесь, эта конкретная проблема связана с пониманием того, что свойства типа значения возвращают копию типа значения (как с методами и индексаторами), и к полям типа значения обращаются напрямую . Следующий код делает именно то, что вы пытаетесь достичь, напрямую обращаясь к полю поддержки свойства (примечание: выражение свойства в его подробной форме с помощью поля поддержки является эквивалентом свойства auto, но имеет то преимущество, что в нашем коде мы можем получить доступ к вспомогательному полю напрямую):

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
    }
}
Matt
источник