Hata: “Dönüş değeri değiştirilemiyor” c #


155

Otomatik uygulanan özellikleri kullanıyorum. Sanırım aşağıdaki düzeltmek için en hızlı yolu kendi destek değişken ilan etmektir?

public Point Origin { get; set; }

Origin.X = 10; // fails with CS1612

Hata İletisi: Değişken olmadığı için 'ifade'nin dönüş değeri değiştirilemiyor

Bir ara ifadenin sonucu olan bir değer türünü değiştirme girişiminde bulunuldu. Değer kalıcı olmadığından değer değiştirilmez.

Bu hatayı gidermek için, ifadenin sonucunu bir ara değerde saklayın veya ara ifade için bir referans türü kullanın.


13
Bu, değişebilir değer türlerinin neden kötü bir fikir olduğunu gösteren başka bir örnektir. Bir değer türünü değiştirmekten kaçınabiliyorsanız, bunu yapın.
Eric Lippert

Aşağıdaki kodu alın (bir değer türünü değiştirmekten kaçınamayan belirli bir EL :-) tarafından bloglanan bir AStar uygulamasındaki çabalarımdan: class Path <T>: IEnumerable <T> burada T: INode, new () {. ..} genel HexNode (int x, int y): this (yeni Nokta (x, y)) {} Yol <T> yolu = yeni Yol <T> (yeni T (x, y)); // Hata // Çirkin düzeltme Yol <T> yolu = yeni Yol <T> (yeni T ()); path.LastStep.Centre = yeni Nokta (x, y);
Tom Wilson

Yanıtlar:


198

Bunun nedeni Pointbir değer türü ( struct) olmasıdır.

Bu nedenle, Originözelliğe eriştiğinizde , değerin kendisini referans türü ( ) ile yaptığınız gibi sınıf tarafından tutulan değerin bir kopyasına erişirsiniz class, bu nedenle Xözelliği ayarladığınızda, kopyadaki özelliği ve ardından atarak orijinal değeri değiştirmeden bırakır. Muhtemelen amaçladığınız şey bu değil, bu yüzden derleyici sizi uyarıyor.

Sadece Xdeğeri değiştirmek istiyorsanız , böyle bir şey yapmanız gerekir:

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

2
@Paul: Yapıyı bir sınıfa değiştirme yeteneğiniz var mı?
Doug

1
Bu tür bir bummer olan im tayin etmek ayarlayıcı özelliği bir yan etkisi vardır, çünkü (yapı bir destek başvuru türü bir görünüm olarak işlev görür)
Alexander - Eski Monica

Başka bir çözüm ise yapınızı bir sınıfa dönüştürmektir. Bir sınıfın ve bir yapının yalnızca varsayılan üye erişimine (sırasıyla özel ve genel) göre değiştiği C ++ 'dan farklı olarak, C #' daki yapıların ve sınıfların birkaç farkı vardır. İşte biraz daha bilgi: docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
Artorias2718 10:07

9

Bir destek değişkeni kullanmak yardımcı olmaz. PointTip bir değer türüdür.

Origin özelliğine tüm Point değerini atamanız gerekir: -

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

Sorun, Origin özelliğine eriştiğinizde, tarafından döndürülen getşey, Origin özellikleri otomatik oluşturulan alanında Point yapısının bir kopyasıdır. Bu nedenle, X alanında yaptığınız değişiklik, bu kopya temel alanı etkilemez. Derleyici bunu algılar ve bu işlem tamamen işe yaramaz olduğundan size bir hata verir.

Kendi destek değişkeninizi kullansanız bile getşöyle görünür: -

get { return myOrigin; }

Hala Nokta yapısının bir kopyasını döndürüyorsunuz ve aynı hatayı alıyorsunuz.

Hmm ... sorunuzu daha dikkatli okuduğunuzda, aslında destek değişkenini doğrudan sınıfınızın içinden değiştirmek isteyebilirsiniz:

myOrigin.X = 10;

Evet, ihtiyacınız olan şey bu olurdu.


6

Şimdiye kadar hatanın kaynağının ne olduğunu zaten biliyorsunuz. Bir yapıcı, mülkünüzü almak için aşırı yük ile mevcut değilse (bu durumda X), nesne başlatıcıyı kullanabilirsiniz (bu, sahnelerin arkasındaki tüm sihri yapacak). Yapılarınızı değiştirilemez hale getirmek değil , sadece ek bilgi vermek zorunda değilsiniz :

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.

Bu mümkündür çünkü perde arkasında bu olur:

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

Bu yapılması çok garip bir şey gibi görünüyor, hiç tavsiye edilmez. Sadece alternatif bir yol listelemek. Yapmanın en iyi yolu, yapıyı değişmez yapmak ve uygun bir kurucu sağlamaktır.


2
Bu, değerini düşürmez mi Origin.Y? Türünde bir özellik göz önüne alındığında Point, sadece değiştirmek için deyimsel yolu düşünürdüm Xolacaktır var temp=thing.Origin; temp.X = 23; thing.Origin = temp;. Deyimsel yaklaşım, değiştirmek istemediği üyelerden bahsetmek zorunda olmama avantajına sahiptir, bu sadece değiştirilebilir olan bir özelliktir Point. Derleyici izin veremez Origin.X = 23;gibi kod gerektirecek bir yapı tasarlamalıdır çünkü diyor felsefe şaşkın Origin.X = new Point(23, Origin.Y);. İkincisi benim için gerçekten çok yapışkan görünüyor.
supercat

@supercat ilk kez senin fikrini düşünüyorum, çok mantıklı! Bununla başa çıkmak için alternatif bir desen / tasarım fikriniz var mı? C # varsayılan olarak bir yapı için varsayılan kurucu sağlamamış olsaydı daha kolay olurdu (bu durumda kesinlikle her ikisini de Xve Ybelirli kurucuya geçmek zorundayım ). Şimdi kişinin yapabileceği noktayı kaybediyor Point p = new Point(). Neden bir yapı için gerçekten gerekli olduğunu biliyorum, bu yüzden abt düşünmenin bir anlamı yok. Ancak, yalnızca bir mülkü güncellemek için harika bir fikriniz var Xmı?
nawfal

Bağımsız fakat ilgili değişkenlerin (bir noktanın koordinatları gibi) bir koleksiyonunu kapsayan yapılar için tercihim, yapının tüm üyelerini kamusal alanlar olarak göstermesini sağlamaktır; struct özelliğinin bir üyesini değiştirmek için basitçe okuyun, üyeyi değiştirin ve tekrar yazın. C #, parametre listesi alan listesiyle eşleşen bir yapıcıyı otomatik olarak tanımlayacak bir "basit Düz-Eski-Veri-Yapı" bildirimi sağlamış olsaydı güzel olurdu, ancak C # sorumlu değişken yapılardan sorumlu kişiler.
supercat

@supercat Anlıyorum. Yapıların ve sınıfların tutarsız davranışı kafa karıştırıcıdır.
nawfal

Karışıklık, IMHO'nun her şeyin bir sınıf nesnesi gibi davranması gerektiği yararsız inancından kaynaklanıyor. Öbek nesnesi başvuruları bekleyen şeylere değer türü değerleri iletmek için bir araç kullanmak yararlı olsa da, değer türü değişkenlerin türetilmiş şeylere sahip olduğunu iddia etmek yararlı değildir Object. Yapmazlar. Her değer türü tanımı aslında iki tür şey tanımlar: depolama konumu türü (değişkenler, dizi yuvaları vb. İçin kullanılır) ve bazen "kutulu" tür olarak adlandırılan bir yığın nesne türü (değer türü değer olduğunda kullanılır) referans türü bir konuma kaydedilir).
supercat

2

Yapıların artılarını ve eksilerini sınıflara karşı tartışmanın yanı sıra, hedefe bakma ve soruna bu perspektiften yaklaşma eğilimindeyim.

Bununla birlikte, özellik get ve set yöntemlerini (örneğin örneğiniz gibi) arkasına kod yazmanız gerekmiyorsa, o zaman Originbir özellik yerine sınıfın bir alanı olarak ilan etmek daha kolay olmaz mıydı ? Bunun hedefinize ulaşmanıza izin vereceğini düşünmeliyim.

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

Sorun, yığın üzerinde bulunan bir değere işaret etmeniz ve C # değer türüne başvuru döndürmenize izin vermemesi için değerin orignal özelliğine geri dönüştürülmemesidir. Bunu Origin özelliğini kaldırarak çözebilir ve bunun yerine bir kamuya açık dosya kullanabilirsiniz, evet bunun güzel bir çözüm olmadığını biliyorum. Diğer çözüm, Noktayı kullanmamak ve bunun yerine nesne olarak kendi Nokta türünüzü oluşturmaktır.


Eğer Pointreferans türünün üyesi sonra yığın olmayacak, bu ihtiva eden nesnenin belleğinde yığın olacaktır.
Greg Beech

0

Sanırım burada yakalamak, nesnenin kendisini atamak yerine deyimde nesnenin alt değerlerini atamaya çalışıyorsunuz. Özellik türü Nokta olduğundan, bu durumda tüm Point nesnesini atamanız gerekir.

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

Umarım orada mantıklıydım


2
Önemli olan nokta, bir yapı (değer türü) olmasıdır. Bir sınıf (nesne) olsaydı, orijinal kod işe yarardı.
Hans Ke sting

@HansKesting: Değiştirilebilir Pointbir sınıf türü olsaydı, orijinal kod özelliği Xtarafından döndürülen nesnede alan veya özellik ayarlamış olurdu Origin. Ben Originözelliği içeren nesne üzerinde istenen etkisi olacağını inanmak için hiçbir neden görmüyorum . Bazı Framework sınıfları, durumlarını yeni değiştirilebilir sınıf örneklerine kopyalayan ve bunları döndüren özelliklere sahiptir. Böyle bir tasarım, kodun thing1.Origin = thing2.Origin;, nesnenin kökeninin durumunu başka biriyle eşleşecek şekilde ayarlamak gibi bir avantaja sahiptir , ancak kod hakkında uyaramaz thing1.Origin.X += 4;.
supercat

0

Sadece aşağıdaki gibi "set" özelliğini kaldırın ve sonra her şey her zamanki gibi çalışır.

İlkel türlerde instread kullanın; 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

Bence birçok insan burada kafasını karıştırıyor, bu özel konu değer türü özelliklerinin (yöntem ve dizinleyicileri gibi) değer türünün bir kopyasını döndürdüğünü ve değer türü alanlarına doğrudan erişildiğini anlamakla ilgilidir . Aşağıdaki kod, doğrudan mülkün destek alanına erişerek tam olarak elde etmeye çalıştığınız şeyi yapar (not: bir özelliği, destek alanıyla ayrıntılı formda ifade etmek, bir otomatik özelliğin eşdeğeridir, ancak kodumuzda doğrudan destek alanına erişin):

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

Aldığınız hata, bir özelliğin değer türünün bir kopyasını döndürdüğünü anlamamanın dolaylı bir sonucudur. Değer türünün bir kopyası döndürülürse ve yerel bir değişkene atamazsanız, bu kopyada yaptığınız hiçbir değişiklik asla okunamaz ve bu nedenle derleyici bunu kasıtlı olamayacağı için bir hata olarak yükseltir. Kopyayı yerel bir değişkene atarsak, X değerini değiştirebiliriz, ancak yalnızca derleme zamanı hatasını gideren ancak Origin özelliğini değiştirmek için istenen etkiye sahip olmayacak olan yerel kopyada değiştirilecektir. Derleme hatası gittiğinden aşağıdaki kod bunu göstermektedir, ancak hata ayıklama iddiası başarısız olacaktır:

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
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.