Невозможно изменить возвращаемое значение ошибки C#

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

public Point Origin { get; set; }

Origin.X = 10; // fails with CS1612

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

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

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

6 ответов

Решение

Это потому что Point тип значения (struct).

Из-за этого, когда вы получаете доступ к Origin свойство, к которому вы обращаетесь, копия значения, содержащегося в классе, а не само значение, как если бы вы использовали ссылочный тип (class), так что если вы установите X Затем вы устанавливаете свойство для копии, а затем отбрасываете его, оставляя исходное значение без изменений. Это, вероятно, не то, что вы хотели, поэтому компилятор предупреждает вас об этом.

Если вы хотите изменить только X значение, вам нужно сделать что-то вроде этого:

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

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

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

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

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

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

get { return myOrigin; }

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

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

myOrigin.X = 10;

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

К настоящему времени вы уже знаете, что является источником ошибки. В случае, если конструктор не существует с перегрузкой, чтобы взять ваше свойство (в этом случае 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;

Это выглядит очень странно, совсем не рекомендуется. Просто перечисление альтернативного пути. Лучший способ сделать это - сделать структуру неизменной и предоставить правильный конструктор.

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

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

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

При этом, если вам не нужно писать код за свойством 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

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

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

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

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

Просто удалите свойство "установить", как показано ниже, и все будет работать как всегда.

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

    }
}      
Другие вопросы по тегам