كيف يمكنك منع IDisposable من الانتشار إلى جميع فصولك الدراسية؟
-
20-08-2019 - |
سؤال
ابدأ بهذه الفصول البسيطة..
لنفترض أن لدي مجموعة بسيطة من الفئات مثل هذا:
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
لا بد وأن IDisposable
.و Bus
يخلق 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();
}
}
}
ويقتصر نطاق التعامل مع الانتظار لTie
method، والطبقة لا يحتاج أن يكون حقل القابل للتصرف، وهكذا لن تحتاج إلى أن تكون نفسها المتاح.
ومنذ مقبض الانتظار هو تفصيل تطبيق داخل 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))}));
}
}
}