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




constructor warnings (14)

चेतावनी के कारण पहले ही वर्णित हैं, लेकिन आप चेतावनी को कैसे ठीक करेंगे? आपको कक्षा या आभासी सदस्य को सील करना होगा।

  class B
  {
    protected virtual void Foo() { }
  }

  class A : B
  {
    public A()
    {
      Foo(); // warning here
    }
  }

आप कक्षा ए को सील कर सकते हैं:

  sealed class A : B
  {
    public A()
    {
      Foo(); // no warning
    }
  }

या आप विधि फू सील कर सकते हैं:

  class A : B
  {
    public A()
    {
      Foo(); // no warning
    }

    protected sealed override void Foo()
    {
      base.Foo();
    }
  }

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

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


ऊपर दिए गए लिखित उत्तर हैं कि आप ऐसा क्यों नहीं करना चाहते हैं। यहां एक काउंटर उदाहरण है जहां शायद आप ऐसा करना चाहते हैं ( रूबी में प्रैक्टिकल ऑब्जेक्ट-ओरिएंटेड डिज़ाइन से सैंडी मेटज़, पृष्ठ 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 { }

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

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


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


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

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

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


एक महत्वपूर्ण लापता बिट यह है कि इस मुद्दे को हल करने का सही तरीका क्या है?

जैसा कि ग्रेग ने समझाया , यहां मूल समस्या यह है कि बेस क्लास कन्स्ट्रक्टर व्युत्पन्न वर्ग का निर्माण करने से पहले आभासी सदस्य का आह्वान करेगा।

एमएसडीएन के कन्स्ट्रक्टर डिज़ाइन दिशानिर्देशों से लिया गया निम्नलिखित कोड, इस मुद्दे को प्रदर्शित करता है।

public class BadBaseClass
{
    protected string state;

    public BadBaseClass()
    {
        this.state = "BadBaseClass";
        this.DisplayState();
    }

    public virtual void DisplayState()
    {
    }
}

public class DerivedFromBad : BadBaseClass
{
    public DerivedFromBad()
    {
        this.state = "DerivedFromBad";
    }

    public override void DisplayState()
    {   
        Console.WriteLine(this.state);
    }
}

जब DerivedFromBad का एक नया उदाहरण बनाया गया है, तो बेस क्लास कन्स्ट्रक्टर BadBaseClass को कॉल करता है और BadBaseClass दिखाता है क्योंकि फ़ील्ड अभी तक व्युत्पन्न कन्स्ट्रक्टर द्वारा अपडेट नहीं किया गया है।

public class Tester
{
    public static void Main()
    {
        var bad = new DerivedFromBad();
    }
}

एक बेहतर कार्यान्वयन बेस क्लास कन्स्ट्रक्टर से आभासी विधि को हटा देता है, और एक Initialize विधि का उपयोग करता है। DerivedFromBetter का एक नया उदाहरण बनाना अपेक्षित "DerivedFromBetter" प्रदर्शित करता है

public class BetterBaseClass
{
    protected string state;

    public BetterBaseClass()
    {
        this.state = "BetterBaseClass";
        this.Initialize();
    }

    public void Initialize()
    {
        this.DisplayState();
    }

    public virtual void DisplayState()
    {
    }
}

public class DerivedFromBetter : BetterBaseClass
{
    public DerivedFromBetter()
    {
        this.state = "DerivedFromBetter";
    }

    public override void DisplayState()
    {
        Console.WriteLine(this.state);
    }
}

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

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


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


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

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

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

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

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

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


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


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

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


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

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

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

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

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

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





virtual-functions