त्रुटि: "वापसी मान को संशोधित नहीं कर सकता"


155

मैं ऑटो-कार्यान्वित गुणों का उपयोग कर रहा हूं। मुझे लगता है कि अपने खुद के बैकिंग वेरिएबल को घोषित करने के लिए सबसे आसान तरीका है?

public Point Origin { get; set; }

Origin.X = 10; // fails with CS1612

त्रुटि संदेश: 'अभिव्यक्ति' के रिटर्न मान को संशोधित नहीं कर सकता क्योंकि यह एक चर नहीं है

एक मूल्य प्रकार को संशोधित करने का प्रयास किया गया था जो एक मध्यवर्ती अभिव्यक्ति का परिणाम था। क्योंकि मान कायम नहीं है, तो मान अपरिवर्तित रहेगा।

इस त्रुटि को हल करने के लिए, एक मध्यवर्ती मान में अभिव्यक्ति के परिणाम को संग्रहीत करें, या मध्यवर्ती अभिव्यक्ति के लिए एक संदर्भ प्रकार का उपयोग करें।


13
यह एक और दृष्टांत है कि क्यों परिवर्तनशील मूल्य प्रकार एक बुरा विचार है। यदि आप एक मूल्य प्रकार को बदलने से बच सकते हैं, तो ऐसा करें।
एरिक लिपर्ट

निम्न कोड लें (एक निश्चित ईएल :-) द्वारा ब्लॉग पर किए गए एक एएसटीआर कार्यान्वयन पर मेरे प्रयासों से, जो मूल्य प्रकार बदलने से बच नहीं सकता था: वर्ग पथ <टी>: IEnumerable <टी> जहां टी: इनोड, नया () {। ..} public HexNode (int x, int y): यह (नया पॉइंट (x, y)) {} Path <T> path = new Path <T> (new T (x, y)); // त्रुटि // बदसूरत तय पथ <टी> पथ = नया पथ <टी> (नया टी ()); path.LastStep.Centre = नया बिंदु (x, y);
टॉम विल्सन

जवाबों:


198

ऐसा इसलिए है क्योंकि Pointएक मूल्य प्रकार ( struct) है।

इस वजह से, जब आप उस Originसंपत्ति का उपयोग करते हैं जिसे आप वर्ग द्वारा रखे गए मूल्य की एक प्रति तक पहुँचा रहे हैं , न कि स्वयं मान के साथ जैसा कि आप एक संदर्भ प्रकार ( class) के साथ करेंगे , इसलिए यदि आप उस Xपर संपत्ति सेट करते हैं तो आप सेटिंग कर रहे हैं प्रतिलिपि पर संपत्ति और फिर उसे छोड़ना, मूल मूल्य को अपरिवर्तित छोड़ देना। यह संभवतः वह नहीं है जो आपने इरादा किया था, यही वजह है कि संकलक आपको इसके बारे में चेतावनी दे रहा है।

यदि आप सिर्फ Xमूल्य बदलना चाहते हैं , तो आपको कुछ ऐसा करने की आवश्यकता है:

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

2
@Paul: क्या आपके पास संरचना को कक्षा में बदलने की क्षमता है?
डग

1
यह एक प्रकार का बमर है, क्योंकि प्रॉपर्टी सेटर इम असाइनमेंट का साइड इफेक्ट होता है (संरचना एक बैकिंग संदर्भ प्रकार में एक दृश्य के रूप में कार्य करता है)
अलेक्जेंडर - मोनिका

एक अन्य उपाय यह है कि आप अपनी संरचना को एक कक्षा में रखें। सी ++ के विपरीत, जहां एक वर्ग और एक संरचना केवल डिफ़ॉल्ट सदस्य पहुंच (निजी और सार्वजनिक, क्रमशः) द्वारा भिन्न होती है, सी # में संरचना और कक्षाएं कुछ और अंतर हैं। यहां कुछ और जानकारी दी गई है: docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
Artorias2718

9

बैकिंग वैरिएबल का उपयोग करने से मदद नहीं मिलेगी। Pointप्रकार एक मूल्य प्रकार है।

आपको ओरिजिनल प्रॉपर्टी को पूरे पॉइंट वैल्यू असाइन करने की आवश्यकता है: -

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

समस्या यह है कि जब आप ओरिजिन प्रॉपर्टी का उपयोग करते हैं, तो जो लौटाया जाता getहै वह ओरिजिन प्रॉपर्टीज़ ऑटो-निर्मित फ़ील्ड में पॉइंट स्ट्रक्चर की एक प्रति है। इसलिए एक्स फ़ील्ड का आपका संशोधन इस प्रति अंतर्निहित क्षेत्र को प्रभावित नहीं करेगा। कंपाइलर इसका पता लगाता है और आपको एक त्रुटि देता है क्योंकि यह ऑपरेशन पूरी तरह से बेकार है।

यहां तक ​​कि अगर आप अपने स्वयं के बैकिंग वेरिएबल का उपयोग करते हैं, तो getऐसा लगेगा: -

get { return myOrigin; }

आप अभी भी पॉइंट संरचना की एक प्रति लौटा रहे हैं और आपको वही त्रुटि मिलेगी।

हम्म ... अपने प्रश्न को अधिक ध्यान से पढ़ने के बाद शायद आपको वास्तव में अपनी कक्षा के भीतर से सीधे बैकिंग चर को संशोधित करने का मतलब है: -

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);। उत्तरार्द्ध वास्तव में मेरे लिए icky लगता है।
सुपरकैट

@ सुपरकैट यह पहली बार है जब मैं आपकी बात पर विचार कर रहा हूं, बहुत समझ में आता है! क्या आपके पास इससे निपटने के लिए एक वैकल्पिक पैटर्न / डिजाइन विचार है? यह आसान होता कि C # डिफ़ॉल्ट रूप से किसी संरचना के लिए डिफ़ॉल्ट कंस्ट्रक्टर उपलब्ध नहीं कराता (उस स्थिति में मुझे कड़ाई से Xऔर Yविशिष्ट कंस्ट्रक्टर दोनों को पास करना होता है)। अब यह उस बिंदु को खो देता है जब कोई कर सकता है Point p = new Point()। मुझे पता है कि इसकी संरचना के लिए वास्तव में क्यों आवश्यक है, इसलिए यह सोचने का कोई मतलब नहीं है। लेकिन क्या आपके पास सिर्फ एक संपत्ति की तरह अद्यतन करने के लिए एक अच्छा विचार है X?
नवफाल

उन संरचनाओं के लिए जो स्वतंत्र लेकिन संबंधित चर (जैसे कि एक बिंदु के निर्देशांक) के संग्रह का एनकैप्सुलेट करते हैं, मेरी प्राथमिकता बस यह है कि संरचना अपने सभी सदस्यों को सार्वजनिक क्षेत्रों के रूप में उजागर करे; एक संरचनात्मक संपत्ति के एक सदस्य को संशोधित करने के लिए, बस इसे पढ़ें, सदस्य को संशोधित करें, और इसे वापस लिखें। यह अच्छा होता अगर C # ने एक "सरल सादा-पुराना-डेटा-संरचना" घोषणा प्रदान की होती जो स्वचालित रूप से एक निर्माता को परिभाषित करता था जिसकी पैरामीटर सूची फ़ील्ड सूची से मेल खाती थी, लेकिन C # तिरस्कृत उत्परिवर्ती संरचनाओं के लिए जिम्मेदार लोग।
सुपरकैट

@ सुपरकैट मुझे मिलता है। संरचना और वर्गों का असंगत व्यवहार भ्रामक है।
नवफाल

IMHO के इस भ्रम से भ्रम पैदा होता है कि सब कुछ एक वर्ग वस्तु की तरह व्यवहार करना चाहिए। हालाँकि, यह उन चीज़ों के लिए मान-प्रकार के मानों को पास करने के लिए उपयोगी है जो ढेर ऑब्जेक्ट संदर्भों की अपेक्षा करते हैं, यह मूल्य-प्रकार के वैरिएबल का ढोंग करने के लिए उपयोगी नहीं है जो उन चीजों को पकड़ते हैं जो इससे प्राप्त होते हैं Object। वे नहीं करते। हर मूल्य-प्रकार की परिभाषा वास्तव में दो प्रकार की चीजों को परिभाषित करती है: एक भंडारण स्थान प्रकार (जिसका उपयोग चर, सरणी स्लॉट, आदि के लिए किया जाता है) और एक ढेर वस्तु प्रकार, कभी-कभी "बॉक्सिंग" प्रकार के रूप में संदर्भित किया जाता है (जब मूल्य-प्रकार का मूल्य होता है एक संदर्भ-प्रकार स्थान पर संग्रहीत है)।
सुपरकैट

2

अलग-अलग वर्गों के नियमों और विपक्ष पर बहस करने के अलावा, मैं लक्ष्य को देखता हूं और उस दृष्टिकोण से समस्या का सामना करता हूं।

कहा जा रहा है, अगर आपको संपत्ति प्राप्त करने और सेट करने के तरीकों के अनुसार कोड लिखने की आवश्यकता नहीं है (जैसा कि आपके उदाहरण में), तो क्या 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 # आपको वैल्यू टाइप का संदर्भ वापस करने की अनुमति नहीं देता है। मुझे लगता है कि आप मूल संपत्ति को हटाकर इसे हल कर सकते हैं और इसके बजाय एक सार्वजनिक दायर का उपयोग कर सकते हैं, हां मुझे पता है कि यह एक अच्छा समाधान नहीं है। अन्य समाधान बिंदु का उपयोग नहीं करना है, और इसके बजाय ऑब्जेक्ट के रूप में अपना खुद का प्वाइंट टाइप बनाएं।


यदि वह Pointएक संदर्भ प्रकार का सदस्य है, तो यह स्टैक पर नहीं होगा, यह युक्त ऑब्जेक्ट की मेमोरी में ढेर पर होगा।
ग्रेग बीच

0

मुझे लगता है कि यहां पर पकड़ यह है कि आप ऑब्जेक्ट को असाइन करने के बजाय कथन में ऑब्जेक्ट के उप-मान निर्दिष्ट करने का प्रयास कर रहे हैं। आपको इस मामले में संपूर्ण बिंदु ऑब्जेक्ट असाइन करने की आवश्यकता है क्योंकि संपत्ति का प्रकार प्वाइंट है।

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

आशा है कि मैंने वहाँ समझ बनाई


2
महत्वपूर्ण बिंदु यह है कि प्वाइंट एक संरचना (वैल्यूएटाइप) है। यदि यह एक वर्ग (वस्तु) होता तो मूल कोड काम कर जाता।
हंस Ke

@HansKesting: यदि Pointएक उत्परिवर्ती वर्ग प्रकार होता, तो मूल कोड Xमें संपत्ति द्वारा लौटाए गए ऑब्जेक्ट में फ़ील्ड या प्रॉपर्टी होती Origin। मुझे विश्वास है कि युक्त वस्तु पर वांछित असर होगा कोई कारण नहीं देखते Originसंपत्ति। कुछ फ्रेमवर्क वर्गों में ऐसे गुण होते हैं जो उनके राज्य को नए उत्परिवर्तनीय वर्ग उदाहरणों की नकल करते हैं और उन्हें वापस करते हैं। इस तरह के डिजाइन में कोड की अनुमति देने का लाभ होता है जैसे thing1.Origin = thing2.Origin;ऑब्जेक्ट की उत्पत्ति की स्थिति को दूसरे से मिलान करने के लिए सेट करना, लेकिन यह कोड के बारे में चेतावनी नहीं दे सकता thing1.Origin.X += 4;
सुपरकाट

0

बस "प्राप्त करें" संपत्ति को अनुसरण के रूप में हटा दें, और फिर सब कुछ हमेशा की तरह काम करता है।

आदिम प्रकारों के मामले में, सेट का उपयोग करें;

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 का मान बदल सकते हैं, लेकिन इसे केवल स्थानीय प्रतिलिपि पर बदला जाएगा, जो संकलन समय त्रुटि को ठीक करता है, लेकिन मूल संपत्ति को संशोधित करने का वांछित प्रभाव नहीं होगा। निम्न कोड इसे दिखाता है, क्योंकि संकलन त्रुटि हो गई है, लेकिन डीबग अभिकथन विफल हो जाएगा:

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 Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.