10 اشتباه رایج در برنامه نویسی به زبان سی شارپ

در این بخش 10 مورد از رایج ترین اشتباهات برنامه نویسی را که برنامه نویسان سی شارپ مرتکب می شوند یا تله هایی که باید از آنها اجتناب کرد، شرح داده می شود.

10 اشتباه رایج در برنامه نویسی به زبان سی شارپ

10 اشتباه رایج در برنامه نویسی به زبان سی شارپ

سی شارپ یک زبان قدرتمند و انعطاف پذیر با مکانیسم ها و پارادایم های بسیاری است که می تواند بهره وری را تا حد زیادی بهبود بخشد. این مقاله 10 مورد از رایج ترین اشتباهات برنامه نویسی را که برنامه نویسان سی شارپ مرتکب می شوند یا تله هایی که باید از آنها اجتناب کرد، شرح داده می شود.

اشتباهات رایج در برنامه نویسی به زبان سی شارپ

درباره سی شارپ

سی شارپ یکی از چندین زبانی است که زمان اجرای زبان مشترک (Common Language Runtime) مایکروسافت (CLR) را هدف قرار می دهد. زبان هایی که CLR را هدف قرار می دهند از ویژگی هایی مانند یکپارچه سازی بین زبانی و مدیریت استثنا، امنیت پیشرفته، مدل ساده شده برای تعامل مؤلفه ها و خدمات اشکال زدایی و نمایه سازی بهره می برند. از میان زبان های CLR امروزی، سی شارپ بیشترین کاربرد را برای پروژه های توسعه پیچیده و حرفه ای دارد که دسکتاپ ویندوز، موبایل یا محیط های سرور را هدف قرار می دهند.

سی شارپ یک زبان شی گرای strongly-typed است. بررسی دقیق نوع (type) در سی شارپ، هم در زمان کامپایل و هم در زمان اجرا، منجر به این می شود که اکثر خطاهای معمول برنامه نویسی سی شارپ در اسرع وقت گزارش شوند و مکان های آنها کاملاً دقیق مشخص شود. این امر در برنامه نویسی سی شارپ می تواند در زمان بسیار زیادی صرفه جویی کند، در مقایسه با ردیابی علت خطاهای گیج کننده که می تواند مدت ها پس از انجام عملیات متخلف در زبان هایی رخ دهد که با اعمال ایمنی type آزادتر هستند. با این حال، بسیاری از کدنویسان سی شارپ ناخواسته (یا بدون دقت) مزایای این تشخیص را دور می اندازند، که منجر به برخی از مسائل مورد بحث در این آموزش سی شارپ می شود.

این آموزش 10 مورد از رایج ترین اشتباهات برنامه نویسی سی شارپ را که برنامه نویسان سی شارپ مرتکب شده اند یا مشکلاتی که باید از آنها اجتناب شود را شرح می دهد.

در حالی که بسیاری از اشتباهات مورد بحث در این مقاله مختص #C هستند، برخی از آنها نیز مربوط به زبان های دیگری هستند که CLR را هدف قرار می دهند یا از Framework Class Library (FCL) استفاده می کنند.

اشتباه رایج برنامه نویسی سی شارپ شماره 1: استفاده از مرجعی مانند یک مقدار یا بالعکس

برنامه نویسان ++C و بسیاری از زبان های دیگر عادت دارند که کنترل کنند مقادیری که به متغیرها نسبت می دهند صرفاً مقادیر هستند یا ارجاع به اشیاء موجود هستند. با این حال، در برنامه نویسی سی شارپ، این تصمیم توسط برنامه نویسی گرفته می شود که شی را نوشته است، نه برنامه نویسی که شی را نمونه سازی می کند و آن را به یک متغیر اختصاص می دهد. این یک "گوچا" رایج برای کسانی است که سعی در یادگیری برنامه نویسی #C دارند.

اگر نمی دانید شی مورد استفاده شما یک نوع مقدار (value) است یا نوع مرجع (reference)، ممکن است با شگفتی هایی روبرو شوید. مثلا:

اشتباهات رایج در برنامه نویسی به زبان سی شارپ

همانطور که می بینید، هر دو شی Point و Pen دقیقاً به یک شکل ایجاد شده اند، اما مقدار point1 بدون تغییر باقی ماند زمانی که یک مقدار مختصات جدید X به point2 اختصاص داده شد، در حالی که مقدار pen1 زمانی که یک رنگ جدید به pen2 اختصاص داده شد تغییر کرد. بنابراین می توانیم نتیجه بگیریم که point1 و point2 هر کدام دارای کپی مخصوص به خود از یک شی Point هستند، در حالی که pen1 و pen2 حاوی ارجاعاتی (references) به همان شی Pen هستند. اما چگونه می توانیم بدون انجام این آزمایش آن را بدانیم؟

پاسخ این است که به تعاریف انواع شی نگاه کنید (که می توانید به راحتی در ویژوال استودیو با قرار دادن مکان نما روی نام نوع شی و فشار دادن F12 انجام دهید):

اشتباهات رایج در برنامه نویسی به زبان سی شارپ

همانطور که در بالا نشان داده شد، در برنامه نویسی سی شارپ، از کلمه کلیدی struct برای تعریف یک نوع مقدار (value type) استفاده می شود، در حالی که کلمه کلیدی class برای تعریف یک نوع مرجع (reference type) استفاده می شود. برای کسانی که دانش ++C دارند، که با شباهت های فراوان بین کلمات کلیدی ++C و #C دچار احساس امنیت کاذب شده اند، این رفتار احتمالاً تعجب آور است که ممکن است شما را وادار کند از یک آموزش #C کمک بخواهید.

اگر می خواهید به رفتاری وابسته باشید که بین انواع value و reference متفاوت است - مانند توانایی ارسال یک شی به عنوان پارامتر یک متد و اینکه آن متد حالت شی را تغییر دهد - مطمئن شوید که برای جلوگیری از مشکلات برنامه نویسی #C با نوع صحیح شیء سروکار دارید.

اشتباه رایج برنامه نویسی سی شارپ شماره 2: درک اشتباه مقادیر پیش فرض برای متغیرهای بدون مقدار اولیه

در سی شارپ، انواع مقادیر نمی توانند null باشند. طبق تعریف، انواع مقادیر دارای یک مقدار هستند و حتی متغیرهای بدون مقدار اولیه انواع مقدار باید دارای یک مقدار باشند. این همان مقدار پیش فرض برای آن نوع نامیده می شود. این منجر به نتایج زیر (و معمولاً غیرمنتظره) هنگام بررسی اینکه آیا یک متغیر مقداردهی نشده است می شود:

اشتباهات رایج در برنامه نویسی به زبان سی شارپ

چرا point1 برابر با null نیست؟ پاسخ این است که Point یک نوع مقدار است و مقدار پیش فرض یک Point برابر با (0,0) است، نه null . عدم تشخیص این یک اشتباه بسیار آسان (و رایج) در سی شارپ است.

بسیاری از انواع مقادیر (اما نه همه) دارای ویژگی IsEmpty هستند که می توانید بررسی کنید که آیا value type برابر با مقدار پیش فرض آن است یا خیر:

اشتباهات رایج در برنامه نویسی به زبان سی شارپ

هنگامی که بررسی می کنید که آیا متغیری مقداردهی اولیه شده است یا خیر، مطمئن شوید که می دانید یک متغیر بدون مقدار اولیه از آن نوع به طور پیش فرض چه مقداری خواهد داشت و به تهی بودن (null) آن متکی نباشید.

اشتباه رایج برنامه نویسی سی شارپ شماره 3: استفاده از متد های مقایسه رشته نامشخص یا نامناسب

روش های مختلفی برای مقایسه رشته ها در سی شارپ وجود دارد.

اگرچه بسیاری از برنامه نویسان از عملگر == برای مقایسه رشته ها استفاده می کنند، اما در واقع یکی از نا مطلوب ترین متد ها برای استفاده است، در درجه اول به این دلیل که به صراحت در کد مشخص نمی کند که کدام نوع مقایسه مورد نظر است.

در عوض، روش ترجیحی برای آزمایش برابری رشته ها در برنامه نویسی #C متد Equals است:

اشتباهات رایج در برنامه نویسی به زبان سی شارپ

signature متد اول ( که بدون پارامتر ComparationTypeاست )، در واقع همان استفاده از عملگر == است، اما این مزیت را دارد که صریحاً روی رشته ها اعمال می شود. این مقایسه ترتیبی رشته ها را انجام می دهد که اساساً یک مقایسه بایت به بایت است. در بسیاری از موارد این دقیقاً همان نوع مقایسه ای است که می خواهید، به خصوص در هنگام مقایسه رشته هایی که مقادیر آنها در حین کد نویسی ست شده است، مانند نام فایل ها، متغیرهای محیطی، ویژگی ها و غیره. در این موارد، تا زمانی که یک مقایسه ترتیبی واقعاً نوع صحیح مقایسه برای آن موقعیت باشد، تنها نقطه ضعف استفاده از متد Equals بدون comparisonType این است که کسی که کد را می خواند ممکن است نداند که شما چه نوع مقایسه ای انجام می دهید.

استفاده از signature متد Equals که هر بار که رشته ها را با هم مقایسه می کنید شامل یک comparisonType می شود، نه تنها کد شما را واضح تر می کند، بلکه باعث می شود به صراحت درباره نوع مقایسه ای که باید انجام دهید فکر کنید. این کار ارزشمندی است، زیرا حتی اگر انگلیسی تفاوت زیادی بین مقایسه های ترتیبی و حساس به فرهنگ ایجاد نکند، زبان های دیگر موارد زیادی را فراهم می کنند و نادیده گرفتن امکان زبان های دیگر، پتانسیل زیادی را برای ایجاد خطاها و اشتباهات باز می کند. مثال زیر را ببینید:

اشتباهات رایج در برنامه نویسی به زبان سی شارپ

ایمن ترین روش این است که همیشه یک پارامتر ComparationType برای متد Equals ارائه دهید. در اینجا چند دستورالعمل اساسی وجود دارد:

1 - هنگام مقایسه رشته هایی که توسط کاربر وارد شده اند یا قرار است به کاربر نمایش داده شوند، از یک مقایسه حساس به فرهنگ (culture-sensitive comparison) استفاده کنید ( CurrentCulture یا CurrentCultureIgnoreCase).

2 - هنگام مقایسه رشته های برنامه ای، از مقایسه ترتیبی ( Ordinal یا OrdinalIgnoreCase ) استفاده کنید.

3 - InvariantCulture و InvariantCultureIgnoreCase معمولاً جز در شرایط بسیار محدود مورد استفاده قرار نمی گیرند، زیرا مقایسه های ترتیبی کارآمدتر هستند. اگر مقايسه آگاهانه فرهنگي لازم باشد، معمولاً بايد بر خلاف فرهنگ جاري يا فرهنگ خاص ديگر انجام شود.

علاوه بر متد Equals، رشته ها متد Compare را نیز ارائه می دهند که به جای فقط تست برابری، اطلاعاتی در مورد ترتیب نسبی رشته ها به شما می دهد. این متد برای عملگرهای <، <=، > و >= ترجیح داده می شود، به همان دلایلی که در بالا بحث شد – برای جلوگیری از مشکلات C#.

اشتباه رایج برنامه نویسی سی شارپ شماره 4: استفاده از عبارات تکراری (به جای اعلانی) برای دستکاری مجموعه ها

در سی شارپ 3.0، افزودنLanguage-Integrated Query یا LINQ به زبان، نحوه کویری گرفتن و دستکاری مجموعه ها را برای همیشه تغییر داد. از آن زمان، اگر از عبارات تکراری (iterative statements) برای دستکاری مجموعه ها استفاده می کنید، از LINQ در زمانی که احتمالاً باید استفاده می کردید، استفاده نکرده اید.

برخی از برنامه نویسان سی شارپ حتی از وجود LINQ اطلاعی ندارند، اما خوشبختانه این تعداد به طور فزاینده ای در حال کوچک شدن اشت. با این حال، بسیاری هنوز فکر می کنند که به دلیل شباهت بین کلمات کلیدی LINQ و عبارات SQL ، تنها کاربرد آن در کدهایی است که پایگاه های داده را کویری می کنند.

در حالی که پرس و جو در پایگاه داده استفاده بسیار رایج از عبارات LINQ است، آنها در واقع روی هر مجموعه شمارش پذیری کار می کنند (به عنوان مثال، هر شی که رابط IEnumerable را پیاده سازی می کند). به عنوان مثال، اگر آرایه ای از حساب ها دارید، به جای نوشتن یک C# List foreach:

اشتباهات رایج در برنامه نویسی به زبان سی شارپ

شما می توانید بنویسید:

اشتباهات رایج در برنامه نویسی به زبان سی شارپ

در حالی که این یک مثال بسیار ساده برای جلوگیری از این مشکل رایج برنامه نویسی #C است، مواردی وجود دارد که یک دستور LINQ می تواند به راحتی جایگزین ده ها دستور در یک حلقه تکراری (یا حلقه های تودرتو) شود. و کد کمتر به معنای فرصت کمتری برای ایجاد باگ است. به خاطر داشته باشید، با این حال، ممکن است یک معاوضه از نظر عملکرد وجود داشته باشد. در سناریوهایی که در آنها کارایی و عملکرد حیاتی است، به خصوص در جایی که کد تکراری شما می تواند مفروضاتی را درباره مجموعه شما ایجاد کند که LINQ نمی تواند، مطمئن شوید که یک مقایسه عملکرد بین دو متد انجام دهید.

اشتباه رایج برنامه نویسی سی شارپ شماره 5: شکست در در نظر گرفتن اشیاء زیرین در یک دستور LINQ

LINQ برای انتزاعی کردن کار دستکاری مجموعه ها، چه اشیاء درون حافظه، جداول پایگاه داده یا اسناد XML عالی است. در یک دنیای ایده آل، عالی و کامل، نیازی نیست که بدانید اشیاء زیرین (underlying objects) چیست. اما خطای اینجا این است که فرض کنیم در یک دنیای کامل زندگی می کنیم. در واقع، دستورات LINQ یکسان می توانند نتایج متفاوتی را هنگام اجرا بر روی همان داده به دست آورند، اگر آن داده ها در قالب متفاوتی باشند.

به عنوان مثال، دستور زیر را در نظر بگیرید:

اشتباهات رایج در برنامه نویسی به زبان سی شارپ

این زمانی اتفاق می افتد که یکی از account.Status های شی برابر با “Active” باشد (به A بزرگ توجه کنید)؟ خوب، اگر myAccounts یک شی DbSet بود (که با پیکربندی پیش فرض حساس به حروف بزرگ تنظیم شده بود)، عبارت where همچنان با آن عنصر مطابقت داشت. با این حال، اگر myAccounts در یک آرایه درون حافظه بود، مطابقت نمی کرد و در نتیجه برای total نتیجه متفاوتی داشت.

اما یک دقیقه صبر کنید. وقتی قبلاً در مورد مقایسه رشته ها صحبت کردیم، دیدیم که عملگر == یک مقایسه ترتیبی از رشته ها را انجام می دهد. پس چرا در این مورد عملگر == مقایسه ای را انجام می دهد که حساس به حروف بزرگ و کوچک نیست؟

پاسخ این است که وقتی اشیاء زیرین و نهفته در یک دستور LINQ ارجاع به داده های جدول SQL هستند (همانطور که در مورد شیء Entity Framework DbSet در این مثال درست است و وجود دارد)، دستور به یک دستور T-SQL تبدیل می شود. سپس اپراتورها از قوانین برنامه نویسی T-SQL پیروی می کنند نه از قوانین برنامه نویسی #C، بنابراین مقایسه در مورد بالا به حروف بزرگ و کوچک حساس نخواهد بود.

به طور کلی، اگرچه LINQ یک روش مفید و ثابت برای پرس و جوی مجموعه اشیاء است، در واقع شما هنوز باید بدانید که آیا عبارت شما به چیزی غیر از #C ترجمه می شود یا خیر تا مطمئن شوید که رفتار کد شما در زمان اجرا مطابق انتظار باشد.

اشتباه رایج برنامه نویسی سی شارپ شماره 6: گیج شدن یا جعلی شدن با متد های توسعه

همانطور که قبلا ذکر شد، دستورات LINQ روی هر شی که IEnumerable را پیاده سازی می کند، کار می کند. به عنوان مثال، تابع ساده زیر موجودی هر مجموعه ای از حساب ها را جمع می کند:

اشتباهات رایج در برنامه نویسی به زبان سی شارپ

در کد بالا نوع پارامتر myAccounts به صورت <IEnumerable<Account اعلام شده است. از آنجایی که myAccounts به یک متد Sum ارجاع می دهد ( #C از “dot notation” برای ارجاع به یک متد در یک کلاس یا رابط استفاده می کند)، انتظار داریم متدی به نام ()Sum را در تعریف رابط <IEnumerable<T ببینیم. با این حال، تعریف <IEnumerable<T هیچ ریفرنسی به هیچ متد Sum ندارد و به سادگی به صورت زیر است:

اشتباهات رایج در برنامه نویسی به زبان سی شارپ

پس متد ()Sum کجا تعریف شده است؟ سی شارپ به شدت به نوشتن حساس است، بنابراین اگر ارجاع به متد Sum نامعتبر باشد، کامپایلر سی شارپ مطمئناً آن را به عنوان یک خطا علامت گذاری می کند. بنابراین ما می دانیم که باید وجود داشته باشد، اما کجا؟ علاوه بر این، تعاریف همه متد های دیگری که LINQ برای پرس و جو یا جمع آوری این مجموعه ها ارائه می کند کجاست؟

پاسخ این است که ()Sum یک متد تعریف شده در رابط IEnumerable نیست. در عوض، این یک متد static ( که “extension method” نامیده شد) است که در کلاس System.Linq.Enumerable تعریف شده است:

اشتباهات رایج در برنامه نویسی به زبان سی شارپ

بنابراین چه چیزی یک متد extension را از هر متد static دیگر متفاوت می کند و چه چیزی ما را قادر می سازد در کلاس های دیگر به آن دسترسی داشته باشیم؟

مشخصه متمایز یک متد extension ، این اصلاح کننده در اولین پارامتر آن است. این همان "جادو" است که آن را به عنوان یک متد extension به کامپایلر شناسایی می کند. نوع پارامتری که تغییر می دهد (در این مورد <IEnumerable<TSource است) کلاس یا رابطی را نشان می دهد که برای پیاده سازی این متد ظاهر می شود.

(به عنوان یک نکته، هیچ چیز جادویی در مورد شباهت بین نام رابط IEnumerable و نام کلاس Enumerable که متد extension بر روی آن تعریف شده است وجود ندارد. این شباهت فقط یک انتخاب سبک دلخواه است).

با این درک، همچنین می توانیم ببینیم که تابع sumAccounts که در بالا معرفی کردیم، می توانست به صورت زیر پیاده سازی شود:

اشتباهات رایج در برنامه نویسی به زبان سی شارپ

این واقعیت که ما می توانستیم آن را به این روش پیاده سازی کنیم، این سوال را ایجاد می کند که چرا اصلاً متدهای extension وجود دارند؟ extension methods اساساً راحتی زبان برنامه نویسی سی شارپ هستند که شما را قادر می سازد تا بدون ایجاد نوع مشتق شده جدید، کامپایل مجدد یا تغییر نوع اصلی، متدها را به انواع موجود اضافه کنید.

متدهای extension با استفاده از [namespace] وارد محدوده می شوند، دستور در بالای فایل. شما باید بدانید که فضای نام سی شارپ شامل متدهای extension است که به دنبال آن هستید، اما زمانی که بدانید به دنبال چه چیزی هستید، تشخیص آن بسیار آسان است.

هنگامی که کامپایلر سی شارپ با فراخوانی متدی در نمونه ای از شی مواجه می شود و آن متد تعریف شده در کلاس شی ارجاع شده را پیدا نمی کند، سپس به تمام متدهای extension که در محدوده هستند نگاه می کند تا متدی را پیدا کند که با signature و کلاس متد مورد نیاز مطابقت داشته باشد. اگر این یکی را پیدا کند، ریفرنس نمونه را به عنوان اولین آرگومان به آن متد توسعه ارسال می کند، سپس بقیه آرگومان ها، در صورت وجود، به عنوان آرگومان های بعدی به متد extension ارسال می شوند. (اگر کامپایلر سی شارپ هیچ متد extension مربوطه را در محدوده پیدا نکند، یک خطا ایجاد می کند).

متدهای extension نمونه ای از “syntactic sugar” در کامپایلر سی شارپ هستند که به ما امکان می دهد کدی بنویسیم که (معمولاً) واضح تر و قابل نگهداری تر باشد. واضح تر، یعنی از کاربرد آنها آگاه باشید. در غیر این صورت، به خصوص در ابتدا می تواند کمی گیج کننده باشد.

در حالی که مطمئناً استفاده از extension methods مزایایی دارد، اما می تواند مشکلاتی ایجاد کند و برای توسعه دهندگانی که از آن ها آگاه نیستند یا به درستی آن ها را درک نمی کنند، معضلی در کمک به برنامه نویسی سی شارپ شود. این امر به ویژه در هنگام مشاهده نمونه کدهای آنلاین یا هر کد از پیش نوشته شده دیگری صادق است. وقتی چنین کدی خطاهای کامپایلر ایجاد می کند (زیرا متدهایی را فراخوانی می کند که به وضوح در کلاس هایی که فراخوانی شده اند تعریف نشده اند)، مایل هستیم که فکر کنیم این کد برای نسخه دیگری از کتابخانه یا کلاً برای کتابخانه دیگری اعمال می شود. زمان زیادی را می توان صرف جستجوی نسخه جدید یا فانتوم «کتابخانه گمشده» که وجود ندارد، صرف کرد.

حتی توسعه دهندگانی که با متدهای extension آشنا هستند، هنوز هم گهگاه گرفتار می شوند، زمانی که متدی با همین نام روی شی وجود دارد، اما signature متد آن به نحوی ظریف با متد extension متفاوت است. زمان زیادی را می توان برای یافتن اشتباه تایپی یا خطایی که وجود ندارد تلف کرد.

استفاده از extension methods در کتابخانه های سی شارپ به طور فزاینده ای رایج می شود. علاوه بر LINQ، Unity Application Block و Web API Framework نمونه هایی از دو کتابخانه مدرن پرکاربرد مایکروسافت هستند که از extension methods نیز استفاده می کنند و بسیاری دیگر نیز وجود دارند. هرچه فریم ورک مدرن تر باشد، احتمال بیشتری وجود دارد که متدهای extension را در خود جای دهد.

البته می توانید extension methods خود را نیز بنویسید. با این حال، متوجه باشید که در حالی که به نظر می رسد extension methods مانند روش های نمونه معمولی (regular instance methods) مورد استفاده قرار می گیرند، این در واقع فقط یک توهم است. به طور خاص، متد های extension شما نمی توانند به اعضای خصوصی یا محافظت شده کلاسی که در حال گسترش هستند ارجاع دهند و بنابراین نمی توانند جایگزین کاملی برای وراثت کلاس باشند.

اشتباه رایج برنامه نویسی سی شارپ شماره 7: استفاده از نوع نامناسب مجموعه برای تسک مورد نظر

سی شارپ طیف گسترده ای از مجموعه اشیاء را ارائه می دهد که موارد زیر فقط یک لیست جزئی است:

Array, ArrayList, BitArray, BitVector32, Dictionary<K,V>, HashTable, HybridDictionary, List<T>, NameValueCollection, OrderedDictionary, Queue, Queue<T>, SortedList, Stack, Stack<T>, StringCollection, StringDictionary.

در حالی که ممکن است مواردی وجود داشته باشد که انتخاب های بسیار زیاد به همان اندازه بد باشد که انتخاب های کافی وجود ندارد، این مورد در مورد مجموعه اشیاء صدق نمی کند. تعداد گزینه های موجود قطعا می تواند به نفع شما باشد. کمی وقت اضافی را برای تحقیق و انتخاب نوع مجموعه بهینه برای هدف خود اختصاص دهید. این امر احتمالاً منجر به عملکرد بهتر و فضای کمتری برای خطا خواهد شد.

اگر نوع مجموعه ای وجود دارد که به طور خاص برای نوع عنصر شما (مانند رشته یا بیت) هدف قرار گرفته است، ابتدا به استفاده از آن متمایل شوید. پیاده سازی به طور کلی زمانی کارآمدتر است که نوع خاصی از عنصر را هدف قرار دهد.

برای استفاده از امنیت type سی شارپ، معمولاً باید یک رابط عمومی (generic interface) را به یک رابط غیرعمومی (non-generic) ترجیح دهید. عناصر یک اینترفیس عمومی از نوعی هستند که شما هنگام اعلام شیء خود مشخص می کنید، در حالی که عناصر رابط های غیرعمومی از نوع شی هستند. هنگام استفاده از یک رابط غیر عمومی، کامپایلر C# نمی تواند کد شما را type-check کند. همچنین، هنگام برخورد با مجموعه هایی از انواع مقدار اولیه، استفاده از مجموعه ای غیرعمومی منجر به boxing/unboxing تکراری آن انواع می شود که در مقایسه با مجموعه ای عمومی از نوع مناسب، می تواند تأثیر منفی قابل توجهی بر کارایی داشته باشد.

یکی دیگر از مشکلات رایج سی شارپ این است که شی مجموعه خود را بنویسید. این بدان معنا نیست که این کار هرگز مناسب نیست، اما با انتخاب جامعی که دات نت ارائه می دهد، احتمالاً می توانید به جای اختراع مجدد چرخ، با استفاده از یکی از موارد موجود یا گسترش آن در زمان زیادی صرفه جویی کنید. به طور خاص، C5 Generic Collection Library برای #C و CLI مجموعه گسترده ای از مجموعه های اضافی را ارائه می کند، مانند ساختارهای داده درختی پایدار (persistent tree data structures) ، صف های اولویت مبتنی بر پشته (heap based priority queues)، لیست های آرایه ایندکس شده هش (hash indexed array lists)، لیست های پیوندی (linked lists)، و موارد دیگر.

اشتباه رایج برنامه نویسی سی شارپ شماره 8: بی توجهی به منابع رایگان

محیط CLR از جمع آوری زباله (garbage collector) استفاده می کند، بنابراین نیازی به آزاد کردن حافظه ایجاد شده برای هیچ شی ای ندارید. در واقع، شما نمی توانید. هیچ معادلی برای عملگر delete در ++C یا تابع ()free در C وجود ندارد. اما این بدان معنا نیست که شما می توانید همه اشیا را پس از اتمام استفاده از آنها فراموش کنید. بسیاری از انواع اشیاء نوع دیگری از منابع سیستم (مانند یک فایل دیسک، اتصال پایگاه داده، سوکت شبکه و غیره) را در خود کپسوله می کنند. باز گذاشتن این منابع می تواند به سرعت تعداد کل منابع سیستم را تخلیه کند، عملکرد را کاهش داده و در نهایت منجر به خطاهای برنامه شود.

در حالی که یک متد تخریبگر (destructor method) را می توان بر روی هر کلاس C# تعریف کرد، مشکل تخریب کننده ها (که در سی شارپ نهایی کننده یا finalizers نیز نامیده می شود) این است که نمی توانید با اطمینان از زمان فراخوانی آنها مطلع شوید. آنها توسط جمع کننده زباله (روی یک thread جداگانه، که می تواند باعث پیچیدگی اضافی شود) در یک زمان نامشخص در آینده فراخوانی می شوند. تلاش برای دور زدن این محدودیت ها با اجبار جمع آوری زباله با ()GC.Collect بهترین روش سی شارپ نیست، زیرا باعث می شود که thread برای مدت زمان نامعلومی مسدود شود در حالی که این تمام اشیاء واجد شرایط برای جمع آوری را جمع آوری می کند.

این بدان معنا نیست که هیچ استفاده خوبی برای finalizers وجود ندارد، اما آزاد کردن منابع به روشی قطعی یکی از آنها نیست. در عوض، هنگامی که روی یک فایل، شبکه یا اتصال پایگاه داده کار می کنید، می خواهید به محض اتمام کار، منبع زیرین را آزاد کنید.

نشت منابع (resource leaks) تقریباً در هر محیطی یک نگرانی است. با این حال، سی شارپ مکانیزمی را ارائه می کند که استفاده از آن قوی و ساده است که در صورت استفاده، می تواند نشت را به یک اتفاق بسیار نادر تبدیل کند. چارچوب دات نت اینترفیس IDisposable را تعریف می کند که صرفاً از متد Dispose() تشکیل شده است. هر شیئی که IDisposable را پیاده سازی می کند انتظار دارد هر زمان که مصرف کننده شیء دستکاری آن را به پایان رساند، آن متد فراخوانی شود. این منجر به آزادسازی صریح و قطعی منابع می شود.

اگر در حال ایجاد و حذف یک شی در متن یک بلوک کد واحد هستید، اساساً فراموشی فراخوانی ()Dispose غیرقابل توجیه است، زیرا سی شارپ یک دستور استفاده را ارائه می دهد که اطمینان حاصل می کند که ()Dispose بدون توجه به اینکه بلوک کد چگونه خارج می شود فراخوانی می شود. (خواه یک استثنا باشد، یک عبارت بازگشتی یا صرفاً بسته شدن بلوک). و بله، این همان عبارتی است که قبلا ذکر شد و برای گنجاندن فضاهای نام #C در بالای فایل شما استفاده می شود. این یک هدف دوم و کاملا نامرتبط دارد که بسیاری از توسعه دهندگان سی شارپ از آن بی اطلاع هستند، یعنی، برای اطمینان از اینکه هنگام خروج از بلوک کد، ()Dispose بر روی یک شیء فراخوانی می شود:

اشتباهات رایج در برنامه نویسی به زبان سی شارپ

با ایجاد یک بلوک استفاده (using block) در مثال بالا، مطمئناً می دانید که ()myFile.Dispose به محض اتمام کار با فایل فراخوانی می شود، خواه ()Read استثنایی ایجاد کند یا نه.

اشتباه رایج برنامه نویسی سی شارپ شماره 9: دوری از استثناها و خطاها

سی شارپ به اجرای ایمنی type خود در زمان اجرا ادامه می دهد. این به شما امکان می دهد تا بسیاری از انواع خطاها را در سی شارپ خیلی سریع تر از زبان هایی مانند ++C مشخص کنید، جایی که تبدیل type مشکلدار می تواند منجر به تخصیص مقادیر دلخواه به فیلدهای یک شی شود. با این حال، یک بار دیگر، برنامه نویسان می توانند این ویژگی عالی را هدر دهند و منجر به مشکلات #C شود. آنها در این تله می افتند زیرا سی شارپ دو روش مختلف برای انجام کارها ارائه می دهد، یکی که می تواند استثنا ایجاد کند و دیگری که این کار را نمی کند. برخی از مسیر استثنا و خطا دوری می کنند و تصور می کنند که عدم نیاز به نوشتن بلوک try/catch باعث می شود که مقداری کدنویسی کمتر شود.

به عنوان مثال، در اینجا دو روش مختلف برای انجام یک Cast نوع صریح در سی شارپ وجود دارد:

اشتباهات رایج در برنامه نویسی به زبان سی شارپ

واضح ترین خطایی که می تواند با استفاده از Method 2 رخ دهد، شکست بررسی مقدار بازگشتی است. این احتمالاً منجر به یک NullReferenceException نهایی می شود که احتمالاً در زمان دیگری ظاهر می شود و ردیابی منبع مشکل را بسیار سخت تر می کند. در مقابل، Method 1 فوراً یک InvalidCastException ایجاد می کرد که منبع مشکل را بلافاصله آشکار می کرد.

علاوه بر این، حتی اگر به یاد داشته باشید که مقدار بازگشتی را در Method 2 بررسی کنید، اگر آن را تهی یافتید، چه کاری انجام می دهید؟ آیا متدی که می نویسید مکان مناسبی برای گزارش خطا است؟ آیا چیز دیگری وجود دارد که بتوانید در صورت شکست آن cast امتحان کنید؟ اگر نه، پس ایجاد یک استثنا کار درستی است، بنابراین ممکن است اجازه دهید تا آنجا که ممکن است به منبع مشکل نزدیک شود.

در اینجا چند نمونه از جفت متد های متداول دیگری وجود دارد که در آنها یکی استثنا ایجاد می کند و دیگری نه:

اشتباهات رایج در برنامه نویسی به زبان سی شارپ

برخی از توسعه دهندگان سی شارپ به قدری «مخالف با استثنا» هستند که به طور خودکار متدی را که استثناء ایجاد نمی کند برتر فرض می کنند. در حالی که موارد خاصی وجود دارد که ممکن است این درست باشد، این به عنوان یک تعمیم و کلی سازی به هیچ وجه صحیح نیست.

به عنوان یک مثال خاص، در موردی که شما یک اقدام قانونی جایگزین (به عنوان مثال، پیش فرض) دارید که در صورت ایجاد یک استثنا باید انجام دهید، آن رویکرد غیر استثنایی می تواند یک انتخاب قانونی باشد. در چنین حالتی شاید بهتر باشد چیزی شبیه این بنویسید:

اشتباهات رایج در برنامه نویسی به زبان سی شارپ

به جای:

اشتباهات رایج در برنامه نویسی به زبان سی شارپ

با این حال، فرض اینکه TryParse لزوماً متد "بهتر" است، نادرست است. گاهی اوقات اینطور است، گاهی اوقات اینطور نیست. به همین دلیل دو راه برای انجام آن وجود دارد. در آن context که در آن قرار دارید از مورد درست استفاده کنید، به یاد داشته باشید که استثناها یا همان exceptions قطعاً می توانند دوست شما به عنوان یک توسعه دهنده باشند.

اشتباه رایج برنامه نویسی سی شارپ شماره 10: اجازه جمع آوری هشدارهای کامپایلر

در حالی که این مشکل قطعاً مختص سی شارپ نیست، به ویژه در برنامه نویسی سی شارپ فاحش است، زیرا مزایای بررسی دقیق نوع ارائه شده توسط کامپایلر C# را کنار گذاشته است.

هشدارها (warnings) به دلیلی ایجاد می شوند. در حالی که تمام خطاهای کامپایلر سی شارپ نشان دهنده نقص در کد شما هستند، بسیاری از هشدارها نیز این کار را انجام می دهند. چیزی که این دو را متمایز می کند این است که در صورت هشدار، کامپایلر مشکلی در ارسال دستورالعمل هایی که کد شما نشان می دهد ندارد. حتی در این صورت، کد شما را کمی بدبین می بیند، و احتمال معقولی وجود دارد که کد شما دقیقاً هدف شما را منعکس نکند.

یک مثال ساده رایج برای این آموزش برنامه نویسی سی شارپ زمانی است که الگوریتم خود را تغییر می دهید تا استفاده از متغیری را که استفاده می کردید حذف کنید، اما فراموش می کنید که تعریف متغیر را حذف کنید. برنامه به خوبی اجرا می شود، اما کامپایلر هشداری در مورد بی استفاده بودن تعریف متغیر می دهد. این واقعیت که برنامه به طور کامل اجرا می شود باعث می شود برنامه نویسان از رفع علت هشدار غافل شوند. علاوه بر این، برنامه نویسان از آن ویژگی ویژوال استودیو استفاده می کنند که به راحتی می توانند اخطارها را در پنجره "لیست خطا" پنهان کنند تا بتوانند فقط بر روی خطاها تمرکز کنند. طولی نمی کشد تا ده ها هشدار وجود داشته باشد که همه آنها نادیده گرفته شده اند (یا حتی بدتر از آن، پنهان شده اند).

اما اگر این نوع warning را نادیده بگیرید، دیر یا زود، چیزی شبیه به این ممکن است به خوبی به کد شما راه پیدا کند:

اشتباهات رایج در برنامه نویسی به زبان سی شارپ

و با سرعتی که Intellisense به ما اجازه می دهد کد بنویسیم، این خطا آنقدر که به نظر می رسد بعید نیست.

اکنون یک خطای جدی در برنامه خود دارید (اگرچه کامپایلر به دلایلی که قبلاً توضیح داده شد فقط آن را به عنوان یک هشدار علامت گذاری کرده است) و بسته به اینکه برنامه شما چقدر پیچیده است، می توانید زمان زیادی را برای پیگیری این یکی از دست بدهید. اگر در وهله اول به این هشدار توجه کرده بودید، با یک راه حل ساده پنج ثانیه ای از این مشکل جلوگیری می کردید.

به یاد داشته باشید، کامپایلر سی شارپ اطلاعات مفید زیادی در مورد استحکام کدتان به شما می دهد البته اگر به آن گوش دهید. هشدارها را نادیده نگیرید تعمیر آن ها معمولاً فقط چند ثانیه طول می کشد و تعمیر موارد جدید در صورت وقوع می تواند باعث صرفه جویی در ساعات شما شود. خود را طوری آموزش دهید که انتظار داشته باشید پنجره "فهرست خطا" ویژوال استودیو "0 خطا، 0 هشدار" را نمایش دهد، به طوری که هر هشداری شما را به اندازه کافی ناراحت کند که بلافاصله به آنها رسیدگی کنید.

البته در هر قاعده و قانونی استثنا وجود دارد. بر این اساس، ممکن است مواقعی پیش بیاید که کد شما از نظر کامپایلر کمی بد به نظر برسد، حتی اگر دقیقاً همان چیزی باشد که شما قصد دارید. در موارد بسیار نادر، از #pragma warning disable [warning id] فقط در اطراف کدی که اخطار را ایجاد می کند و فقط برای شناسه هشداری (warning ID) که این ایجاد می کند، استفاده کنید. این اخطار را سرکوب می کند، و فقط آن اخطار را سرکوب می کند تا همچنان بتوانید برای هشدارهای جدید هوشیار بمانید.

خلاصه مطالب بالا

سی شارپ یک زبان قدرتمند و انعطاف پذیر با مکانیسم ها و پارادایم های بسیاری است که می تواند بهره وری را تا حد زیادی بهبود بخشد. با این حال، مانند هر ابزار یا زبان نرم افزاری، داشتن درک یا درک محدود از قابلیت های آن گاهی اوقات می تواند بیشتر از یک مزیت، مانع باشد و فرد را در حالت ضرب المثل “knowing enough to be dangerous” قرار دهد.

استفاده از یک آموزش سی شارپ مانند این برای آشنایی با نکات کلیدی C#، مانند (اما به هیچ وجه محدود به) مشکلات مطرح شده در این مقاله، به بهینه سازی سی شارپ کمک می کند و در عین حال از برخی از دام های رایج تر آن جلوگیری می کند.

10 اشتباه رایج در برنامه نویسی به زبان سی شارپ آموزش زبان سی شارپ

مقالات این دسته بندی