هذا سؤال طرحه أحد أعضاء فريقنا أثناء عملية البرمجة. نظرًا لأنه من السهل تجنب خطأ التجميع هذا، لم أفكر مطلقًا في هذه المشكلة بعناية حتى نظرت إلى الكود الخاص به وأدركت أن هذه المشكلة ليست بهذه البساطة.
ألق نظرة على هذا الكود أولاً:
شفرة
برنامج الصف
{
الفراغ الثابت الرئيسي (سلسلة [] الحجج)
{
بايت[] buf = بايت جديد[1024];
T t = جديد T();
سلسلة str = "1234";
كثافة العمليات ن = 1234؛
إنت؟ ن = 1234؛
DateTime dt = DateTime.Now;
كائن س = 1234؛
Console.WriteLine("finish");
}
}
فئة ت { }
كم عدد المتغيرات التي تعتقد أنها غير مستخدمة في هذا الكود؟
إذا نظرت إلى الأمر من وجهة نظر المبرمج، يجب أن تكون الإجابة أن جميع المتغيرات غير مستخدمة. لكن النتيجة التي قدمها المترجم غير بديهية بعض الشيء:
تم تعيين قيمة للمتغير "str"، ولكن لم يتم استخدام قيمته مطلقًا. تم تعيين قيمة للمتغير "n"، ولكن لم يتم استخدام قيمته مطلقًا، ولكن لم يتم استخدام القيمة قط.
الغريب أنه على الرغم من أن جميع المتغيرات تم الإعلان عنها بنفس الطريقة، إلا أن المترجم يعتقد فقط أن بعضها غير مستخدم. ماذا يحدث هنا؟
دعونا نحللهم واحدًا تلو الآخر. انظر أولاً إلى المصفوفة إذا تم استخدام القيمة الافتراضية، فستختلف المعلومات المقدمة من المترجم:
byte[] buf1 = null;
byte[] buf2 = new byte[1024];
يبدو أن هذه النتيجة تشير إلى أنه إذا كان تعيين المعلمة فارغًا، فلن يقوم المترجم فعليًا بتنفيذ التعيين، وسيتم التعامل مع المتغير كما لو لم يتم استخدامه. يمكن أن تثبت نتائج فحص IL أيضًا هذا البيان: بالنسبة للسطر الأول، لم يقم المترجم بإنشاء أي عبارة مقابلة؛ أما بالنسبة للسطر الثاني، فقد تم استخدام تعليمات newattr لإنشاء المصفوفة.
للفئات المخصصة:
T t1 = null; // يوجد تحذير
T t2 = جديد T();
يجب أن تكون هذه النتيجة مفهومة (على الرغم من أنها مفهومة، إلا أنني لا أعتقد أنها جيدة، للأسباب الموضحة أدناه). على الرغم من أننا لا نستدعي أي أساليب للفئة، إلا أن منشئ الفئة قد يستمر في إجراء بعض العمليات، لذلك طالما تم إنشاء فئة، فإن المترجم سيعاملها كما لو تم استخدامها.
بالنسبة لأنواع القيمة الأساسية، يختلف سلوكها عن الأنواع المرجعية، ولا يتعامل المترجم مع المهمة الأولية على أنها استخدام للمتغيرات:
int n1 = 0; // يوجد تحذير
int n2 = 1234; // يوجد تحذير
int n3 = null;
int n4 = 0; // هناك تحذير
int n5 = 1234;
يجب اعتبار السلسلة نوعًا مرجعيًا من حيث التنفيذ، ولكن أدائها يشبه إلى حد كبير نوع القيمة، ومعلومات التحذير هي نفس نوع القيمة.
بالنسبة لأنواع القيم الأكثر تعقيدًا قليلًا، تكون النتائج أكثر دقة قليلًا:
DateTime dt1; // يوجد تحذير
DateTime dt2 = new DateTime(); // يوجد تحذير
DateTime dt3 = new DateTime(2009,1,1); // لا يوجد تحذير
DateTime dt4 = DateTime.Now;
هناك شيء واحد يجب ملاحظته حول هذه النتيجة. على الرغم من أن المُنشئ الافتراضي لـ DateTime والمنشئ ذي المعلمات كلاهما مُنشئان من وجهة نظر المستخدم، إلا أنهما مختلفان عن منظور المترجم. يمكن أيضًا أن نرى من خلال إلغاء الترجمة باستخدام IL أنه إذا تم استدعاء المنشئ الافتراضي، فإن المترجم يستدعي تعليمات initobj، بينما يتم استخدام تعليمات call ctor للمنشئ ذي المعلمات. بالإضافة إلى ذلك، على الرغم من أن تنسيق رمز المهمة هو نفسه تمامًا من وجهة نظر المبرمج، إلا أن المترجم سيتبنى استراتيجيات بناء مختلفة اعتمادًا على القيمة المخصصة، وهو أمر غير بديهي أيضًا.
الاستنتاج النهائي مؤسف، وهو أن تحذيرات التجميع الخاصة بـ C# ليست كافية لمنح المبرمجين الحماية الكافية، خاصة للمصفوفات:
بايت[] buf = بايت جديد[1024];
إذا قمت فقط ببناء مثل هذه المصفوفة دون استخدامها، فلن يعطي المترجم أي رسالة تحذير للمبرمج.
هناك مسألة أخرى تستحق النظر فيها وهي الإعلان عن فئة دون استخدام أي أساليب، مثل فقط
تي تي = جديد تي()
فهل هذا التصرف معقول؟ هل يجب على المترجم إصدار تحذير لهذا؟
رأيي الشخصي هو أنه من وجهة نظر الاستخدام، هذا غير معقول ويجب تجنبه قدر الإمكان. يجب على المترجم إصدار تحذير إذا اكتشف هذا الاستخدام. إذا لزم الأمر، يمكنك تجنب رسائل التحذير عن طريق الإعلان عنها بشكل محدد من خلال توجيهات الترجمة أو أساليب السمات. ومع ذلك، فإن سلوك مترجم C# لا يتمثل في إصدار تحذيرات، وهو ما لا أتفق معه. وبطبيعة الحال، آمل أيضًا أن يطرح الجميع أفكارهم الخاصة.