エラー:「戻り値を変更できません」c#


155

自動実装プロパティを使用しています。以下を修正する最も速い方法は、自分のバッキング変数を宣言することだと思いますか?

public Point Origin { get; set; }

Origin.X = 10; // fails with CS1612

エラーメッセージ:変数ではないため、 '式'の戻り値を変更できません

中間式の結果である値タイプを変更しようとしました。値は永続化されないため、値は変更されません。

このエラーを解決するには、式の結果を中間値に格納するか、中間式の参照型を使用します。


13
これは、可変値型が悪い考えである理由のもう1つの例です。値型の変更を回避できる場合は、変更してください。
Eric Lippert、

次のコード(特定のELによってブログに記録されたAStar実装での私の取り組みから)を取り上げます。これは、値の型の変更を回避できませんでした:class Path <T>:IEnumerable <T> where 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 = new Point(x、y);
トムウィルソン

回答:


198

これは、Pointが値タイプ(struct)であるためです。

このため、Originプロパティにアクセスすると、参照型()のように値自体ではなく、クラスが保持する値のコピーにアクセスするclassので、Xプロパティを設定すると、コピーのプロパティを破棄し、元の値を変更せずに破棄します。これはおそらく意図したものではないため、コンパイラが警告を出します。

X値だけを変更したい場合は、次のようにする必要があります。

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

2
@Paul:構造体をクラスに変更する機能はありますか?
Doug

1
プロパティセッターのim割り当てには副作用があるため、これはちょっと厄介です(構造体はバッキング参照タイプへのビューとして機能します)
Alexander-Reinstate Monica

別の解決策は、単純に構造体をクラスにすることです。クラスと構造体がデフォルトのメンバーアクセス(それぞれプライベートとパブリック)だけが異なるC ++とは異なり、C#の構造体とクラスにはさらにいくつかの違いがあります。ここではいくつかのより多くの情報があります:docs.microsoft.com/en-us/dotnet/csharp/programming-guide/...
Artorias2718

9

バッキング変数を使用しても効果はありません。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);。後者は私には本当に気味悪いようです。
スーパーキャット2013

@supercatこれは私があなたのポイントを考えているのは初めてですこれに取り組むための代替パターン/デザインのアイデアはありますか?C#が構造体のデフォルトコンストラクターをデフォルトで提供していなかった場合はもっと簡単でした(その場合、厳密に両方XY特定のコンストラクターに渡す必要があります)。今、それは人ができるときにポイントを失いますPoint p = new Point()。構造体に本当に必要な理由を知っているので、それを考えても意味がありません。しかし、あなたは1つのプロパティだけを更新するという素晴らしいアイデアがありますXか?
nawfal 2013

独立しているが関連する変数のコレクション(点の座標など)をカプセル化する構造体の場合、私の構造は、構造体にすべてのメンバーをパブリックフィールドとして公開させることです。構造体プロパティの1つのメンバーを変更するには、単にそれを読み取り、メンバーを変更して、それを書き戻します。C#が、パラメーターリストがフィールドリストと一致するコンストラクターを自動的に定義する "単純なPlain-Old-Data-Struct"宣言を提供していれば良かったのですが、C#の責任者は可変構造を軽視しています。
スーパーキャット2013

@supercatわかりました。構造体とクラスの一貫性のない動作は混乱を招きます。
nawfal 2013

混乱は、すべてがクラスオブジェクトのように動作する必要があるというIMHOの役に立たない信念から生じます。値型の値をヒープオブジェクトの参照を期待するものに渡す手段があると便利ですが、値型の変数がから派生するものを保持しているふりをすることは役に立ちませんObject。彼らはしません。すべての値タイプの定義では、実際には2種類のものが定義されます。格納場所のタイプ(変数、配列スロットなどに使用)とヒープオブジェクトタイプで、「ボックス化」タイプ(値タイプの値参照型の場所に保存されます)。
スーパーキャット2013

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

問題は、スタックにある値をポイントし、その値が元のプロパティに反映されないため、C#では値の型への参照を返すことができないことです。Originプロパティを削除して、代わりに公開フィールドを使用することでこれを解決できると思います。そうですね、それは良い解決策ではないことはわかっています。もう1つの解決策は、ポイントを使用せず、代わりに独自のポイントタイプをオブジェクトとして作成することです。


Point参照型のメンバーである場合、それはスタック上にはなく、包含オブジェクトのメモリ内のヒープ上にあります。
グレッグビーチ、

0

ここでの問題点は、オブジェクト自体を割り当てるのではなく、ステートメントでオブジェクトのサブ値を割り当てようとしていることです。この場合、プロパティタイプはPointであるため、Pointオブジェクト全体を割り当てる必要があります。

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

私はそこに意味があったと思います


2
重要な点は、Pointが構造体(valuetype)であることです。クラス(オブジェクト)の場合、元のコードは機能します。
Hans Ke st ing

@HansKesting:Point変更可能なクラス型の場合、元のコードはproperty Xによって返されたオブジェクトにフィールドまたはプロパティを設定していましたOrigin。それがOriginプロパティを含むオブジェクトに望ましい効果をもたらすと信じる理由はないと思います。一部のフレームワーククラスには、状態を新しい可変クラスインスタンスにコピーし、それらを返すプロパティがあります。このような設計には、thing1.Origin = thing2.Origin;オブジェクトのオリジンの状態を別のオリジンの状態と一致させるようにコードを設定できるという利点がありますが、などのコードについて警告することはできませんthing1.Origin.X += 4;
スーパーキャット2013

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の値を変更できますが、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
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.