كيف يمكنك منع IDisposable من الانتشار إلى جميع فصولك الدراسية؟

StackOverflow https://stackoverflow.com/questions/661815

سؤال

ابدأ بهذه الفصول البسيطة..

لنفترض أن لدي مجموعة بسيطة من الفئات مثل هذا:

class Bus
{
    Driver busDriver = new Driver();
}

class Driver
{
    Shoe[] shoes = { new Shoe(), new Shoe() };
}

class Shoe
{
    Shoelace lace = new Shoelace();
}

class Shoelace
{
    bool tied = false;
}

أ Bus لديه Driver, ، ال Driver لديه اثنين Shoeس، كل Shoe لديه Shoelace.كل شيء سخيف جدا.

قم بإضافة كائن IDisposable إلى رباط الحذاء

في وقت لاحق قررت أن بعض العمليات على Shoelace يمكن أن يكون متعدد الخيوط، لذلك أقوم بإضافة EventWaitHandle للمواضيع للتواصل معها.لذا Shoelace يبدو الآن مثل هذا:

class Shoelace
{
    private AutoResetEvent waitHandle = new AutoResetEvent(false);
    bool tied = false;
    // ... other stuff ..
}

قم بتطبيق IDisposable على رباط الحذاء

لكن الآن مايكروسوفت FxCop سوف يشكو: "قم بتطبيق IDisposable على 'Shoelace' لأنه ينشئ أعضاء من أنواع IDisposable التالية:""EventWaitHandle"."

حسنًا، أنا أنفذ IDisposable على Shoelace ويتحول صفي الصغير الأنيق إلى هذه الفوضى الفظيعة:

class Shoelace : IDisposable
{
    private AutoResetEvent waitHandle = new AutoResetEvent(false);
    bool tied = false;
    private bool disposed = false;

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    ~Shoelace()
    {
        Dispose(false);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                if (waitHandle != null)
                {
                    waitHandle.Close();
                    waitHandle = null;
                }
            }
            // No unmanaged resources to release otherwise they'd go here.
        }
        disposed = true;
    }
}

أو (كما أشار المعلقون) منذ ذلك الحين Shoelace في حد ذاته لا يحتوي على موارد غير مُدارة، فقد أستخدم تنفيذ التخلص الأبسط دون الحاجة إلى Dispose(bool) والمدمر:

class Shoelace : IDisposable
{
    private AutoResetEvent waitHandle = new AutoResetEvent(false);
    bool tied = false;

    public void Dispose()
    {
        if (waitHandle != null)
        {
            waitHandle.Close();
            waitHandle = null;
        }
        GC.SuppressFinalize(this);
    }
}

شاهد برعب بينما ينتشر IDisposable

صحيح أن هذا ثابت.ولكن الآن سوف تشكو FxCop من ذلك Shoe يخلق أ Shoelace, ، لذا Shoe لا بد وأن IDisposable أيضاً.

و Driver يخلق Shoe لذا Driver لا بد وأن IDisposableBus يخلق Driver لذا Bus لا بد وأن IDisposable وما إلى ذلك وهلم جرا.

فجأة تغير بلدي الصغيرة ل Shoelace يسبب لي الكثير من العمل ويتساءل مديري عن سبب حاجتي إلى الدفع Bus لإجراء تغيير على Shoelace.

السؤال

كيف يمكنك منع هذا الانتشار IDisposable, ، ولكن هل لا تزال تتأكد من التخلص من العناصر غير المُدارة بشكل صحيح؟

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

المحلول

لا يمكنك حقًا "منع" IDisposable من الانتشار.بعض الفصول تحتاج إلى التخلص منها، مثل AutoResetEvent, ، والطريقة الأكثر فعالية هي القيام بذلك في Dispose() طريقة لتجنب النفقات العامة للنهائيات.ولكن يجب استدعاء هذه الطريقة بطريقة ما، تمامًا كما في المثال الخاص بك، يجب على الفئات التي تغلف أو تحتوي على IDisposable أن تتخلص منها، لذلك يجب أن يتم التخلص منها أيضًا، وما إلى ذلك.والطريقة الوحيدة لتجنب ذلك هي:

  • تجنب استخدام فئات IDisposable حيثما أمكن ذلك، وقم بقفل الأحداث أو انتظارها في أماكن فردية، واحتفظ بالموارد باهظة الثمن في مكان واحد، وما إلى ذلك
  • قم بإنشائها فقط عندما تحتاج إليها والتخلص منها مباشرة بعد (ملف using نمط)

في بعض الحالات، يمكن تجاهل IDisposable لأنه يدعم حالة اختيارية.على سبيل المثال، يقوم WaitHandle بتطبيق IDisposable لدعم كائن Mutex المسمى.إذا لم يتم استخدام اسم، فإن أسلوب التخلص لا يفعل شيئًا.يعد MemoryStream مثالًا آخر، فهو لا يستخدم أي موارد للنظام كما أن تطبيق Dispose الخاص به لا يفعل شيئًا أيضًا.إن التفكير بعناية فيما إذا كان يتم استخدام مورد غير مُدار أم لا يمكن أن يكون أمرًا تعليميًا.وكذلك يمكن فحص المصادر المتاحة لمكتبات .net أو استخدام برنامج فك التحويل البرمجي.

نصائح أخرى

في شروط صحة، لا يمكن منع انتشار IDisposable من خلال علاقة الكائن إذا بإنشاء كائن الأم ويملك كائن تابع التي يجب أن تكون الآن التخلص منها أساسا. FxCop هو الصحيح في هذه الحالة، ويجب أن يكون الوالد IDisposable.

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

إذا يمكنك نقل WaitHandle إلى جمعية فضفاضة عن طريق الخريطة عند النقطة يتم استخدام WaitHandle فعلا ثم يمكنك كسر هذه السلسلة.

لمنع IDisposable من الانتشار، يجب أن تحاول أن تغلف استخدام كائن المتاح داخل أسلوب واحد. محاولة لتصميم Shoelace مختلف:

class Shoelace { 
  bool tied = false; 

  public void Tie() {

    using (var waitHandle = new AutoResetEvent(false)) {

      // you can even pass the disposable to other methods
      OtherMethod(waitHandle);

      // or hold it in a field (but FxCop will complain that your class is not disposable),
      // as long as you take control of its lifecycle
      _waitHandle = waitHandle;
      OtherMethodThatUsesTheWaitHandleFromTheField();

    } 

  }
} 

ويقتصر نطاق التعامل مع الانتظار لTiemethod، والطبقة لا يحتاج أن يكون حقل القابل للتصرف، وهكذا لن تحتاج إلى أن تكون نفسها المتاح.

ومنذ مقبض الانتظار هو تفصيل تطبيق داخل Shoelace، فإنه لا ينبغي تغيير بأي شكل من الأشكال اجهة العامة، مثل إضافة واجهة جديدة في إعلانها. ماذا سيحدث بعد ذلك عندما لا تحتاج حقل المتاح بعد الآن، وقمت بإزالة إعلان IDisposable؟ إذا كنت تفكر في Shoelace <ط> التجريد ، كنت أدرك بسرعة أنه لا ينبغي أن يكون ملوثا تبعيات البنية التحتية، مثل IDisposable. يجب أن تكون محفوظة IDisposable لفئات التي تغلف الموارد التي تدعو إلى حتمية تنظيف التجريد. أي لفئات حيث disposability هو جزء من <ط> التجريد .

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

ومن المثير للاهتمام إذا تم تعريف Driver على النحو الوارد أعلاه:

class Driver
{
    Shoe[] shoes = { new Shoe(), new Shoe() };
}

وبعد ذلك عندما يتم Shoe IDisposable، FxCop (v1.36) لا يشكون من أن Driver كما ينبغي IDisposable.

ولكن إذا تم تعريف مثل هذا:

class Driver
{
    Shoe leftShoe = new Shoe();
    Shoe rightShoe = new Shoe();
}

وبعد ذلك سوف الشكوى.

وأظن أن هذا هو مجرد وجود قيود على FxCop، وليس حلا، لأنه في النسخة الأولى لا تزال خلق حالات Shoe من قبل Driver ولا تزال بحاجة إلى التخلص منها بطريقة أو بأخرى.

وأنا لا أعتقد أن هناك وسيلة تقنية لمنع IDisposable من الانتشار اذا واصلتم التصميم الخاص بك حتى بإحكام. ينبغي للمرء ثم أتساءل عما إذا كان التصميم هو الصحيح.

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

وتصميم بديل يمكن أن يكون:

class Bus
{
   IDriver busDriver = null;
   public void SetDriver(IDriver d) { busDriver = d; }
}

class Driver : IDriver
{
   IShoePair shoes = null;
   public void PutShoesOn(IShoePair p) { shoes = p; }
}

class ShoePairWithDisposableLaces : IShoePair, IDisposable
{
   Shoelace lace = new Shoelace();
}

class Shoelace : IDisposable
{
   ...
}

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

وهذا هو أساسا ما يحدث عند خلط تركيب أو تجميع مع الطبقات يمكن التخلص منها. وكما ذكر، فإن أول مخرج يكون لريفاكتور وwaitHandle من رباط الحذاء.

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

ولكن يمكنك حذف المدمر وGC.SuppressFinalize (هذا)؛ وربما تنظيف الفراغ تخلص الظاهري (منطقي يتخلصون) قليلا.

وماذا عن استخدام عكس السيطرة؟

class Bus
{
    private Driver busDriver;

    public Bus(Driver busDriver)
    {
        this.busDriver = busDriver;
    }
}

class Driver
{
    private Shoe[] shoes;

    public Driver(Shoe[] shoes)
    {
        this.shoes = shoes;
    }
}

class Shoe
{
    private Shoelace lace;

    public Shoe(Shoelace lace)
    {
        this.lace = lace;
    }
}

class Shoelace
{
    bool tied;
    private AutoResetEvent waitHandle;

    public Shoelace(bool tied, AutoResetEvent waitHandle)
    {
        this.tied = tied;
        this.waitHandle = waitHandle;
    }
}

class Program
{
    static void Main(string[] args)
    {
        using (var leftShoeWaitHandle = new AutoResetEvent(false))
        using (var rightShoeWaitHandle = new AutoResetEvent(false))
        {
            var bus = new Bus(new Driver(new[] {new Shoe(new Shoelace(false, leftShoeWaitHandle)),new Shoe(new Shoelace(false, rightShoeWaitHandle))}));
        }
    }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top