سؤال

أنا على الحصول على تحذير من ReSharper عن دعوة الظاهري عضو من الكائنات منشئ.

لماذا يكون هذا شيء لا تفعل ؟

هل كانت مفيدة؟

المحلول

عندما كائن مكتوب في C# هي التي شيدت ، ما يحدث هو أن المهيآت تشغيل بالترتيب من الأكثر مشتقة من فئة إلى فئة القاعدة ، ومن ثم تشغيل المنشئات في أمر من قاعدة الطبقة الأكثر فئة مشتقة (يرى إريك ليبرت بلوق لمزيد من التفاصيل لماذا هذا).

أيضا في .صافي كائنات لا تغيير نوع كما أنها هي التي شيدت ، ولكن كما تبدأ معظم مشتقة نوع ، مع طريقة الجدول كونها الأكثر نوع مشتق.وهذا يعني أن أسلوب الظاهري يدعو دائما تعمل على معظم نوع مشتق.

عند الجمع بين هاتين الحقيقتين أنت تركت مع المشكلة إذا قمت بإجراء الظاهري استدعاء الأسلوب في البناء ، وليس أكثر نوع مشتق في الميراث الهرمي ، أنه سوف يطلق على الطبقة التي منشئ لم يتم تشغيل ، وبالتالي قد لا تكون مناسبة الدولة أن يكون هذا الأسلوب يسمى.

هذه المشكلة هي ، بالطبع ، تخفيفها إذا كنت مارك صفك كما مختومة لضمان أكثر نوع مشتق في الميراث الهرمي - في هذه الحالة هو آمنة تماما استدعاء أسلوب الظاهري.

نصائح أخرى

من أجل الإجابة على السؤال الخاص بك, النظر في هذه المسألة:ماذا سيكون تحت كود طباعة عندما 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!?!
    }
}

الجواب هو أنه في الواقع NullReferenceException سيتم طرح, لأن foo هي null. كائن قاعدة ما يسمى منشئ قبل الخاصة منشئ.من خلال وجود virtual استدعاء كائن منشئ أنت إدخال إمكانية أن وراثة الكائنات تنفيذ التعليمات البرمجية قبل أن تتم تهيئة بشكل كامل.

قواعد C# هي مختلفة جدا عن تلك التي Java و C++.

عندما كنت في منشئ لبعض الكائن في C#, هذا الكائن موجود في تهيئة بشكل كامل (ليس فقط "شيدت") شكل ، تماما نوع مشتق.

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

هذا يعني أنه إذا كنت استدعاء دالة ظاهري من منشئ ، فإنه سيتم حل أي تجاوز في ب ، إذا قدمت واحدة.

حتى إذا كنت عن قصد إعداد أ و ب مثل هذا تماما فهم سلوك النظام ، يمكن أن يكون في صدمة في وقت لاحق.يقول اتصلت وظائف افتراضية في " ب " منشئ "معرفة" انهم سوف يكون التعامل معها من قبل ب أو عند الاقتضاء.ثم يمر الوقت و شخص آخر تقرر أنها تحتاج إلى تعريف C, وتجاوز بعض وظائف افتراضية هناك.فجأة ب منشئ ينتهي استدعاء التعليمات البرمجية في C ، والتي يمكن أن تؤدي إلى السلوك المستغرب جدا.

فمن المحتمل ان يكون فكرة جيدة لتجنب وظائف افتراضية في المنشئات على أي حال ، منذ القواعد هي مختلفة جدا بين C#, C++, Java.الخاص بك المبرمجين قد لا يعرفون ما يمكن توقعه!

أسباب التحذير التي سبق وصفها ، ولكن كيف يمكنك إصلاح التحذير ؟ عليك أن ختم إما فئة أو الظاهري الأعضاء.

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

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

يمكنك ختم فئة A:

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

أو يمكنك ختم طريقة فو:

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

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

في C#, قاعدة الطبقة' منشئ يعمل قبل فئة مشتقة' منشئ, لذلك أي سبيل المثال حقول فئة مشتقة قد تستخدم في ربما تجاوز الظاهري عضو لم يتم تهيئة حتى الآن.

هل لاحظ أن هذا هو مجرد تحذير لجعل كنت تدفع الانتباه والتأكد من أنها بخير.هناك الفعلية حالات الاستخدام لهذا السيناريو ، عليك أن وثيقة السلوك من الأعضاء الظاهرية أنه لا يمكن استخدام أي مثيل الحقول أعلن في فئة مشتقة أدناه حيث منشئ الدعوة هو.

هناك كتب الإجابات أعلاه لماذا لن تريد أن تفعل ذلك.هنا المثال والمثال حيث ربما أن تريد أن تفعل ذلك (مترجمة إلى C# من عملية تصميم وجوه المنحى في روبي بواسطة ساندي ميتز ، ص.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 { }

نعم, إنها عادة سيئة استدعاء أسلوب الظاهري في منشئ.

في هذه النقطة ، أوبجكت قد لا يكون كاملا بناؤها بعد ، الثوابت المتوقع طريق الأساليب قد لا تعقد حتى الآن.

لأنه حتى منشئ اكتمال تنفيذ الكائن ليس كاملا مثيل.أي الأعضاء المشار إليها من خلال وظيفة افتراضية قد لا يكون تم تفعيلهافي C++, عندما كنت في منشئ ، this يشير فقط إلى نوع ثابت من المنشئ ، وليس الفعلي ديناميكية نوع الكائن الذي يتم إنشاؤه.وهذا يعني أن وظيفة افتراضية الاتصال قد لا تذهب إلى حيث كنت أتوقع أن.

المنشئ الخاص بك قد (في وقت لاحق ، في امتداد البرنامج) أن يسمى من منشئ فئة فرعية أن يتجاوز أسلوب الظاهري.الآن ليس فرعية تنفيذ وظيفة ، ولكن تنفيذ قاعدة الطبقة وسوف يطلق.لذلك لا تجعل حقا معنى الكلمة دالة ظاهري هنا.

ومع ذلك ، إذا كان التصميم الخاص بك يفي Liskov مبدأ الإحلال لا يكون الضرر.ربما هذا هو السبب في أنه من التسامح - تحذير وليس خطأ.

أحد الجوانب الهامة من هذا السؤال الذي إجابات أخرى لم تعالج بعد هو أنه آمن على قاعدة الطبقة الاتصال الظاهري أعضاء من داخل منشئ إذا كان هذا هو ما الفئات المشتقة نتوقع أن تفعل.في مثل هذه الحالات المصمم من قبل كبار المصممين من فئة مشتقة هو المسؤول عن ضمان أن أي الطرق التي يتم تشغيلها قبل اكتمال البناء سوف تتصرف كما معقول كما يمكن في ظل هذه الظروف.على سبيل المثال ، في C++/CLI ، منشئات هي ملفوفة في التعليمات البرمجية التي سوف نداء Dispose على المشيدة جزئيا الكائن إذا كان البناء فشل.الدعوة Dispose في مثل هذه الحالات غالبا ما يكون ضروريا لمنع الموارد التسريبات ، ولكن Dispose الأساليب يجب أن تكون مستعدة لاحتمال أن الكائن الذي يتم تشغيل قد لا يكون كاملا شيدت.

التحذير هو تذكير بأن الظاهري أعضاء من المرجح أن يتم تجاوز على فئة مشتقة.في هذه الحالة مهما كانت الفئة الأصل فعل الظاهري عضو سيتم التراجع عنها أو تغييرها عن طريق تجاوز الطفل الطبقة.ننظر في مثال صغير ضربة الوضوح

الوالد فئة أدناه محاولات لتعيين قيمة افتراضية الأعضاء على منشئ.وهذا سوف يؤدي إلى إعادة أكثر وضوحا تحذير, دعونا نرى على كود:

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

حذار من عمياء التالية Resharper نصيحة وجعل الطبقة مختومة!إذا كان نموذج في EF الرمز الأول فإنه سيتم إزالة الكلمة الافتراضية و التي من شأنها تعطيل كسول التحميل من العلاقات.

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

واحد مهم مفقود بت هو: ما هو الطريق الصحيح لحل هذه القضية ؟

كما وأوضح جريج, جذر المشكلة هنا هو أن منشئ الفئة الأساسية من شأنه أن تحتج الظاهري عضو قبل فئة مشتقة تم بناؤها.

التعليمة البرمجية التالية مأخوذة من MSDN منشئ المبادئ التوجيهية تصميم, يوضح هذه المسألة.

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 إنشاء منشئ الفئة الأساسية المكالمات DisplayState ويظهر 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);
    }
}

هناك فرق بين C++ و C# في هذه الحالة المحددة.في C++ كائن لم يتم تهيئة وبالتالي فمن غير آمنة للاتصال virutal وظيفة داخل منشئ.في C# عند فئة كائن خلق كل أعضائها صفر تهيئة.فمن الممكن استدعاء دالة ظاهري في منشئ ولكن إذا كنت سوف ربما الحصول الأعضاء التي لا تزال صفر.إذا كنت لا تحتاج إلى الوصول إلى أعضاء أنها آمنة تماما استدعاء دالة ظاهري في C#.

فقط لإضافة أفكاري.إذا كنت دائما تهيئة الحقل الخاص عند تحديد ذلك ، هذه المشكلة ينبغي تجنبها.على الأقل تحت كود يعمل مثل السحر:

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

آخر الشيء المثير للاهتمام وجدت أن ReSharper الخطأ يمكن أن يكون "راضين" عن طريق القيام بشيء مثل أدناه والتي هي غبية لي (ومع ذلك ، وكما ذكر في وقت سابق العديد, فإنه لا ليست فكرة جيدة للاتصال الظاهري سند/طرق في المنشئ.

public class ConfigManager
{

   public virtual int MyPropOne { get; private set; }
   public virtual string MyPropTwo { get; private set; }

   public ConfigManager()
   {
    Setup();
   }

   private void Setup()
   {
    MyPropOne = 1;
    MyPropTwo = "test";
   }

}

أود فقط إضافة تهيئة (طريقة) إلى الفئة الأساسية ومن ثم استدعاء هذه مشتقة من المنشئات.هذا الأسلوب سوف استدعاء أي الظاهري/الأساليب التجريدية/خصائص بعد كل من منشئات تم تنفيذها :)

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top