c# - एक कन्स्ट्रक्टर में आभासी सदस्य कॉल




constructor warnings (12)

मुझे अपने ऑब्जेक्ट कन्स्ट्रक्टर से वर्चुअल सदस्य को कॉल के बारे में ReSharper से चेतावनी मिल रही है।

ऐसा कुछ क्यों नहीं होगा?


Resharper की सलाह के बाद अंधेरे से सावधान रहें और कक्षा को सील कर दें! यदि यह ईएफ कोड में पहला मॉडल है तो यह वर्चुअल कीवर्ड को हटा देगा और इससे इसके रिश्तों की आलसी लोडिंग अक्षम हो जाएगी।

    public **virtual** User User{ get; set; }

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

हालांकि, यदि आपका डिज़ाइन लिस्कोव प्रतिस्थापन सिद्धांत को पूरा करता है, तो कोई नुकसान नहीं होगा। शायद यही कारण है कि यह सहन किया जाता है - एक चेतावनी, कोई त्रुटि नहीं।


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


ऊपर दिए गए लिखित उत्तर हैं कि आप ऐसा क्यों नहीं करना चाहते हैं। यहां एक काउंटर उदाहरण है जहां शायद आप ऐसा करना चाहते हैं ( रूबी में प्रैक्टिकल ऑब्जेक्ट-ओरिएंटेड डिज़ाइन से सैंडी मेटज़, पृष्ठ 126) में सी # में अनुवाद किया गया है।

ध्यान दें कि GetDependency() किसी भी आवृत्ति चर को छू नहीं रहा है। यदि स्थैतिक विधियां आभासी हो सकती हैं तो यह स्थिर होगा।

(निष्पक्ष होने के लिए, निर्भरता इंजेक्शन कंटेनर या ऑब्जेक्ट प्रारंभकर्ताओं के माध्यम से ऐसा करने के शायद बेहतर तरीके हैं ...)

public class MyClass
{
    private IDependency _myDependency;

    public MyClass(IDependency someValue = null)
    {
        _myDependency = someValue ?? GetDependency();
    }

    // If this were static, it could not be overridden
    // as static methods cannot be virtual in C#.
    protected virtual IDependency GetDependency() 
    {
        return new SomeDependency();
    }
}

public class MySubClass : MyClass
{
    protected override IDependency GetDependency()
    {
        return new SomeOtherDependency();
    }
}

public interface IDependency  { }
public class SomeDependency : IDependency { }
public class SomeOtherDependency : IDependency { }

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


चेतावनी एक अनुस्मारक है कि वर्चुअल सदस्यों को व्युत्पन्न कक्षा पर ओवरराइड होने की संभावना है। उस स्थिति में जो भी मूल वर्ग वर्चुअल सदस्य को करता है उसे बाल वर्ग को ओवरराइड करके पूर्ववत या बदला जाएगा। स्पष्टता के लिए छोटे उदाहरण को देखो

नीचे दिया गया मूल वर्ग अपने कन्स्ट्रक्टर पर वर्चुअल सदस्य को मान सेट करने का प्रयास करता है। और यह फिर से तेज चेतावनी ट्रिगर करेगा, कोड पर देखें:

public class Parent
{
    public virtual object Obj{get;set;}
    public Parent()
    {
        // Re-sharper warning: this is open to change from 
        // inheriting class overriding virtual member
        this.Obj = new Object();
    }
}

यहां बाल वर्ग माता-पिता की संपत्ति को ओवरराइड करता है। यदि इस संपत्ति को आभासी चिह्नित नहीं किया गया था तो संकलक चेतावनी देगा कि संपत्ति मूल वर्ग पर संपत्ति छिपाती है और सुझाव देती है कि यदि आप जानबूझकर हैं तो आप 'नया' कीवर्ड जोड़ते हैं।

public class Child: Parent
{
    public Child():base()
    {
        this.Obj = "Something";
    }
    public override object Obj{get;set;}
}

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

public class Program
{
    public static void Main()
    {
        var child = new Child();
        // anything that is done on parent virtual member is destroyed
        Console.WriteLine(child.Obj);
        // Output: "Something"
    }
} 

जब सी # में लिखा गया कोई ऑब्जेक्ट बनाया जाता है, तो क्या होता है कि शुरुआती वर्ग सबसे व्युत्पन्न कक्षा से बेस क्लास तक क्रम में चलते हैं, और उसके बाद बेस क्लास से सबसे व्युत्पन्न वर्ग में कन्स्ट्रक्टर चलाते हैं ( विवरण के लिए एरिक लिपर्ट का ब्लॉग देखें यह क्यों है )।

इसके अलावा .NET ऑब्जेक्ट्स में टाइप किए गए प्रकार को नहीं बदला जाता है, लेकिन अधिकांश व्युत्पन्न प्रकार के लिए विधि तालिका के साथ सबसे व्युत्पन्न प्रकार के रूप में शुरू होता है। इसका मतलब है कि आभासी विधि कॉल हमेशा सबसे व्युत्पन्न प्रकार पर चलती हैं।

जब आप इन दो तथ्यों को गठबंधन करते हैं तो आपको समस्या के साथ छोड़ दिया जाता है कि यदि आप एक कन्स्ट्रक्टर में वर्चुअल विधि कॉल करते हैं, और यह अपने उत्तराधिकारी पदानुक्रम में सबसे व्युत्पन्न प्रकार नहीं है, तो इसे उस वर्ग पर बुलाया जाएगा जिसका निर्माता नहीं है चलाएं, और इसलिए उस विधि को रखने के लिए उपयुक्त राज्य में नहीं हो सकता है।

यह समस्या निश्चित रूप से कम हो जाती है यदि आप अपनी कक्षा को मुहरबंद के रूप में चिह्नित करते हैं ताकि यह सुनिश्चित किया जा सके कि यह विरासत पदानुक्रम में सबसे व्युत्पन्न प्रकार है - इस मामले में यह वर्चुअल विधि को कॉल करने के लिए पूरी तरह से सुरक्षित है।


बस मेरे विचार जोड़ने के लिए। यदि आप इसे परिभाषित करते समय निजी क्षेत्र को हमेशा प्रारंभ करते हैं, तो इस समस्या से बचना चाहिए। कम से कम कोड एक आकर्षण की तरह काम करता है:

class Parent
{
    public Parent()
    {
        DoSomething();
    }
    protected virtual void DoSomething()
    {
    }
}

class Child : Parent
{
    private string foo = "HELLO";
    public Child() { /*Originally foo initialized here. Removed.*/ }
    protected override void DoSomething()
    {
        Console.WriteLine(foo.ToLower());
    }
}

मैं बस बेस क्लास में एक प्रारंभिक () विधि जोड़ूंगा और उसके बाद व्युत्पन्न कन्स्ट्रक्टर से कॉल करूंगा। उस विधि को सभी कन्स्ट्रक्टर निष्पादित किए जाने के बाद किसी वर्चुअल / अमूर्त विधियों / गुणों को कॉल किया जाएगा :)


सी # के नियम जावा और सी ++ से बहुत अलग हैं।

जब आप सी # में किसी ऑब्जेक्ट के लिए कन्स्ट्रक्टर में होते हैं, तो वह ऑब्जेक्ट पूरी तरह से व्युत्पन्न प्रकार के रूप में पूरी तरह प्रारंभिक (केवल "निर्मित") रूप में मौजूद नहीं होता है।

namespace Demo
{
    class A 
    {
      public A()
      {
        System.Console.WriteLine("This is a {0},", this.GetType());
      }
    }

    class B : A
    {      
    }

    // . . .

    B b = new B(); // Output: "This is a Demo.B"
}

इसका अर्थ यह है कि यदि आप ए के कन्स्ट्रक्टर से वर्चुअल फ़ंक्शन को कॉल करते हैं, तो यह बी में किसी भी ओवरराइड को हल करेगा, अगर कोई प्रदान किया जाता है।

यहां तक ​​कि यदि आप जानबूझकर ए और बी को इस तरह स्थापित करते हैं, तो सिस्टम के व्यवहार को पूरी तरह से समझते हैं, तो आप बाद में सदमे के लिए हो सकते हैं। मान लें कि आप बी के कन्स्ट्रक्टर में वर्चुअल फ़ंक्शंस कहलाते हैं, "जानना" उन्हें बी या ए द्वारा उचित रूप से संभाला जाएगा। फिर समय बीतता है, और कोई और फैसला करता है कि उन्हें सी को परिभाषित करने की आवश्यकता है, और वहां कुछ वर्चुअल फ़ंक्शन ओवरराइड करें। अचानक बी के कन्स्ट्रक्टर सी में कॉलिंग कोड समाप्त होता है, जिससे काफी आश्चर्यजनक व्यवहार हो सकता है।

कन्स्ट्रक्टर में आभासी कार्यों से बचने के लिए शायद यह एक अच्छा विचार है, क्योंकि नियम सी #, सी ++ और जावा के बीच बहुत अलग हैं । आपके प्रोग्रामर शायद नहीं जानते कि क्या उम्मीद करनी है!


हां, कन्स्ट्रक्टर में आभासी विधि को कॉल करना आम तौर पर खराब है।

इस बिंदु पर, objet अभी तक पूरी तरह से निर्मित नहीं हो सकता है, और विधियों द्वारा अपेक्षित आविष्कार अभी तक नहीं हो सकता है।


अपने प्रश्न का उत्तर देने के लिए, इस प्रश्न पर विचार करें: जब Child ऑब्जेक्ट को तत्काल किया जाता है तो निम्न कोड क्या प्रिंट करेगा?

class Parent
{
    public Parent()
    {
        DoSomething();
    }

    protected virtual void DoSomething() 
    {
    }
}

class Child : Parent
{
    private string foo;

    public Child() 
    { 
        foo = "HELLO"; 
    }

    protected override void DoSomething()
    {
        Console.WriteLine(foo.ToLower());
    }
}

जवाब यह है कि वास्तव में एक NullReferenceException फेंक दिया जाएगा, क्योंकि foo शून्य है। किसी ऑब्जेक्ट के बेस कन्स्ट्रक्टर को अपने स्वयं के कन्स्ट्रक्टर से पहले बुलाया जाता है । किसी ऑब्जेक्ट के कन्स्ट्रक्टर में virtual कॉल करके आप इस संभावना को पेश कर रहे हैं कि विरासत वस्तुएं पूर्णतः प्रारंभ होने से पहले कोड निष्पादित करेंगी।





virtual-functions