MutableSlab और ImmutableSlab कार्यान्वयन के बीच एकमात्र अंतर readonly handle फ़ील्ड पर लागू किया गया संशोधक है:

using System;
using System.Runtime.InteropServices;

public class Program
{
    class MutableSlab : IDisposable
    {
        private GCHandle handle;

        public MutableSlab()
        {
            this.handle = GCHandle.Alloc(new byte[256], GCHandleType.Pinned);
        }

        public bool IsAllocated => this.handle.IsAllocated;

        public void Dispose()
        {
            this.handle.Free();
        }
    }

    class ImmutableSlab : IDisposable
    {
        private readonly GCHandle handle;

        public ImmutableSlab()
        {
            this.handle = GCHandle.Alloc(new byte[256], GCHandleType.Pinned);
        }

        public bool IsAllocated => this.handle.IsAllocated;

        public void Dispose()
        {
            this.handle.Free();
        }
    }

    public static void Main()
    {
        var mutableSlab = new MutableSlab();
        var immutableSlab = new ImmutableSlab();

        mutableSlab.Dispose();
        immutableSlab.Dispose();

        Console.WriteLine($"{nameof(mutableSlab)}.handle.IsAllocated = {mutableSlab.IsAllocated}");
        Console.WriteLine($"{nameof(immutableSlab)}.handle.IsAllocated = {immutableSlab.IsAllocated}");
    }
}

लेकिन वे अलग परिणाम देते हैं:

mutableSlab.handle.IsAllocated = False
immutableSlab.handle.IsAllocated = True

GCHandle एक परिवर्तनशील संरचना है और जब आप इसे कॉपी करते हैं तो यह बिल्कुल वैसा ही व्यवहार करता है जैसा कि immutableSlab के साथ होता है।

क्या readonly संशोधक किसी फ़ील्ड की छिपी हुई प्रतिलिपि बनाता है? क्या इसका मतलब यह है कि यह केवल संकलन-समय की जांच नहीं है? मुझे इस व्यवहार के बारे में कुछ भी नहीं मिला यहां. क्या यह व्यवहार प्रलेखित है?

32
user6440521 1 जुलाई 2019, 10:26

1 उत्तर

सबसे बढ़िया उत्तर

क्या readonly संशोधक किसी फ़ील्ड की छिपी हुई प्रतिलिपि बनाता है?

नियमित संरचना प्रकार (कन्स्ट्रक्टर या स्थिर कन्स्ट्रक्टर के बाहर) के केवल-पढ़ने वाले फ़ील्ड पर किसी विधि या संपत्ति को कॉल करना पहले फ़ील्ड की प्रतिलिपि बनाता है, हां। ऐसा इसलिए है क्योंकि संकलक यह नहीं जानता है कि संपत्ति या विधि का उपयोग उस मूल्य को संशोधित करेगा जिसे आप इसे कहते हैं।

C# 5 ECMA विनिर्देश से:

अनुभाग १२.७.५.१ (सदस्य पहुंच, सामान्य)

यह सदस्य पहुंच को वर्गीकृत करता है, जिसमें शामिल हैं:

  • यदि मैं एक स्थिर क्षेत्र की पहचान करता हूं: <उल>
  • यदि फ़ील्ड केवल पढ़ने के लिए है और संदर्भ उस वर्ग या संरचना के स्थिर निर्माता के बाहर होता है जिसमें फ़ील्ड घोषित किया गया है, तो परिणाम एक मान है, अर्थात् स्थिर फ़ील्ड I का मान E.
  • अन्यथा, परिणाम एक चर है, अर्थात् स्थिर क्षेत्र I में E.

और:

  • यदि टी एक संरचना-प्रकार है और मैं उस संरचना-प्रकार के एक उदाहरण फ़ील्ड की पहचान करता हूं: <उल>
  • यदि E एक मान है, या यदि फ़ील्ड केवल पढ़ने के लिए है और संदर्भ उस संरचना के इंस्टेंस कंस्ट्रक्टर के बाहर होता है जिसमें फ़ील्ड घोषित किया गया है, तो परिणाम एक मान है, अर्थात् संरचना में फ़ील्ड I का मान उदाहरण ई.
  • . द्वारा दिया गया
  • अन्यथा, परिणाम एक चर है, अर्थात् ई द्वारा दिए गए स्ट्रक्चर इंस्टेंस में फ़ील्ड I।

मुझे यकीन नहीं है कि इंस्टेंस फ़ील्ड भाग विशेष रूप से स्ट्रक्चर प्रकारों को क्यों संदर्भित करता है, लेकिन स्थिर फ़ील्ड भाग नहीं करता है। महत्वपूर्ण हिस्सा यह है कि क्या व्यंजक को एक चर या एक मान के रूप में वर्गीकृत किया गया है। फ़ंक्शन सदस्य आमंत्रण में यह तब महत्वपूर्ण है ...

धारा १२.६.६.१ (कार्य सदस्य आमंत्रण, सामान्य)

फ़ंक्शन सदस्य आमंत्रण के रन-टाइम प्रोसेसिंग में निम्नलिखित चरण होते हैं, जहां एम फ़ंक्शन सदस्य होता है और, यदि एम एक इंस्टेंस सदस्य है, तो ई इंस्टेंस अभिव्यक्ति है:

[...]

  • अन्यथा, यदि E का प्रकार एक मान-प्रकार V है, और M को V में घोषित या ओवरराइड किया गया है: <उल>
  • [...]
  • यदि E को एक चर के रूप में वर्गीकृत नहीं किया जाता है, तो E के प्रकार का एक अस्थायी स्थानीय चर बनाया जाता है और E का मान उस चर को सौंपा जाता है। ई को फिर उस अस्थायी स्थानीय चर के संदर्भ के रूप में पुनर्वर्गीकृत किया जाता है। अस्थायी चर इस रूप में एम के भीतर पहुंच योग्य है, लेकिन किसी अन्य तरीके से नहीं। इस प्रकार, केवल जब E एक वास्तविक चर है, क्या कॉल करने वाले के लिए M द्वारा किए गए परिवर्तनों का निरीक्षण करना संभव है।

यहाँ एक स्व-निहित उदाहरण है:

using System;
using System.Globalization;

struct Counter
{
    private int count;

    public int IncrementedCount => ++count;
}

class Test
{
    static readonly Counter readOnlyCounter;
    static Counter readWriteCounter;

    static void Main()
    {
        Console.WriteLine(readOnlyCounter.IncrementedCount);  // 1
        Console.WriteLine(readOnlyCounter.IncrementedCount);  // 1
        Console.WriteLine(readOnlyCounter.IncrementedCount);  // 1

        Console.WriteLine(readWriteCounter.IncrementedCount); // 1
        Console.WriteLine(readWriteCounter.IncrementedCount); // 2
        Console.WriteLine(readWriteCounter.IncrementedCount); // 3
    }
}

readOnlyCounter.IncrementedCount पर कॉल करने के लिए आईएल यह है:

ldsfld     valuetype Counter Test::readOnlyCounter
stloc.0
ldloca.s   V_0
call       instance int32 Counter::get_IncrementedCount()

यह फ़ील्ड मान को स्टैक पर कॉपी करता है, फिर संपत्ति को कॉल करता है ... इसलिए फ़ील्ड का मान बदलना समाप्त नहीं होता है; यह प्रतिलिपि के भीतर count बढ़ रहा है।

पढ़ने-लिखने के क्षेत्र के लिए आईएल के साथ इसकी तुलना करें:

ldsflda    valuetype Counter Test::readWriteCounter
call       instance int32 Counter::get_IncrementedCount()

यह सीधे फ़ील्ड पर कॉल करता है, इसलिए फ़ील्ड मान संपत्ति के भीतर बदल जाता है।

जब संरचना बड़ी होती है और सदस्य उसे नहीं बदलता है, तो प्रतिलिपि बनाना अक्षम हो सकता है। इसलिए C# 7.2 और इसके बाद के संस्करण में, readonly संशोधक को किसी स्ट्रक्चर पर लागू किया जा सकता है। यहाँ एक और उदाहरण है:

using System;
using System.Globalization;

readonly struct ReadOnlyStruct
{
    public void NoOp() {}
}

class Test
{
    static readonly ReadOnlyStruct field1;
    static ReadOnlyStruct field2;

    static void Main()
    {
        field1.NoOp();
        field2.NoOp();
    }
}

संरचना पर ही readonly संशोधक के साथ, field1.NoOp() कॉल एक प्रति नहीं बनाता है। यदि आप readonly संशोधक को हटाते हैं और पुन: संकलित करते हैं, तो आप देखेंगे कि यह एक प्रतिलिपि बनाता है जैसे उसने readOnlyCounter.IncrementedCount में किया था।

मेरे पास एक ब्लॉग पोस्ट है 2014 कि मैंने लिखा था कि readonly फ़ील्ड Noda Time में प्रदर्शन समस्याओं का कारण बन रहे थे। सौभाग्य से अब इसके बजाय structs पर readonly संशोधक का उपयोग करके तय किया गया है।

33
Peter Mortensen 2 जुलाई 2019, 02:01