اندازهی قلم متن
تخمین مدت زمان مطالعهی مطلب:
چهار دقیقه
نگارش ابتدایی «iTextSharp.LGPLv2.Core » بر اساس کدهای اولیهی iTextSharp بود که مستقیما از جاوا به سیشارپ ترجمه شده بود. این کدها پر بودند از ساختارهای دادهای مانند Hashtable و ArrayList که مرتبط هستند با روزهای آغازین ارائهی دات نت 1؛ پیش از ارائهی Generics. برای مثال نوع Hashtable، همانند ساختار دادهی Dictionary عمل میکند، اما جنریک نیست؛ یعنی شبیه به <Dictionary<object, object عمل میکند و برای کار با آن، باید مدام از تبدیل نوعهای دادهها (یا همان boxing) از نوع object، به نوع دادهی مدنظر، استفاده کرد که این تبدیل نوعها، همیشه به همراه کاهش کارآیی هم هستند. به علاوه در حین کار با Hashtable، اگر کلیدی در مجموعهی آن وجود نداشته باشد، فقط نال را بازگشت میدهد، اما Dictionary، یک استثنای یافت نشدن کلید را صادر میکند. بنابراین فرض کنید که با هزاران سطر کد استفاده کنندهی از Hashtable طرف هستید که اگر آنها را تبدیل به Dictionaryهای جنریک متناسبی کنید تا کارآیی برنامه بهبود یابد، تمام موارد استفادهی از آنهارا نیز باید به همراه TryGetValueها کنید تا از شر استثنای یافت نشدن کلید درخواستی، در امان باشید. در این مطلب روش مواجه شدن با یک چنین حالتی را با حداقل تغییر در کدها بررسی خواهیم کرد.
ممنوع کردن استفادهی از ساختارهای دادهی غیرجنریک
قدم اول مواجه شدن با یک چنین کدهای قدیمی، ممنوع کردن استفادهی از ساختارهای دادهی غیرجنریک و الزام به تبدیل آنها به نوعهای جدید است. برای این منظور میتوان از Microsoft.CodeAnalysis.BannedApiAnalyzers استفاده کرد که توضیحات بیشتر آنرا در مطلب «غنی سازی کامپایلر C# 9.0 با افزونهها» پیشتر بررسی کردهایم. به صورت خلاصه، ابتدا بستهی نیوگت آنرا به صورت یک آنالایزر جدید به فایل csproj. برنامه معرفی میکنیم:
همچنین در اینجا نیاز است یک فایل متنی BannedSymbols.txt را نیز به آن معرفی کرد؛ برای مثال با این محتوا:
این تنظیمات سبب خواهند شد تا اگر در کدهای ما، ساختارهای دادهی غیرجنریکی در حال استفاده بودند، با یک اخطار ظاهر شوند و جهت سختگیری بیشتر، روش تبدیل اخطارها به خطاها را نیز در مطلب «غنی سازی کامپایلر C# 9.0 با افزونهها» بررسی کردهایم تا مجبور به اصلاح آنها شویم.
پیشنهاد یک دیکشنری کم دردسرتر!
برای نمونه پس از تنظیمات فوق، مجبور به تغییر تمام hash tableها به دیکشنریهای جدید جنریک خواهیم شد؛ اما ... اگر اینکار را انجام دهیم، برنامهای که تا پیش از این بدون مشکل کار میکرد، اکنون با استثناهای متعدد یافت نشدن کلیدها، خاتمه پیدا میکند! چون دیگر دیکشنریهای جدید، همانند hash tableهای قدیمی، در صورت عدم وجود کلیدی، نال را بازگشت نمیدهند.
برای رفع این مشکل و اصلاح انبوهی از کدها با حداقل تغییرات و عدم تکرار TryGetValueها در همهجا، میتوان دسترسی به ایندکسهای یک دیکشنری استاندارد دات نت را به صورت زیر با ارثبری از آن، بازنویسی کرد:
همانطور که مشاهده میکنید، اگر بجای Dictionary، از NullValueDictionary پیشنهادی استفاده کنیم، دیگر نیازی نیست تا هزاران TryGetValue را در سراسر کدهای برنامه، تکرار و پراکنده کنیم و با حداقل تغییرات میتوان معادل بهتری را بجای Hashtable قدیمی داشت.
ممنوع کردن استفادهی از ساختارهای دادهی غیرجنریک
قدم اول مواجه شدن با یک چنین کدهای قدیمی، ممنوع کردن استفادهی از ساختارهای دادهی غیرجنریک و الزام به تبدیل آنها به نوعهای جدید است. برای این منظور میتوان از Microsoft.CodeAnalysis.BannedApiAnalyzers استفاده کرد که توضیحات بیشتر آنرا در مطلب «غنی سازی کامپایلر C# 9.0 با افزونهها» پیشتر بررسی کردهایم. به صورت خلاصه، ابتدا بستهی نیوگت آنرا به صورت یک آنالایزر جدید به فایل csproj. برنامه معرفی میکنیم:
<Project Sdk="Microsoft.NET.Sdk"> <ItemGroup> <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> </ItemGroup> <ItemGroup> <AdditionalFiles Include="$(MSBuildThisFileDirectory)BannedSymbols.txt" Link="Properties/BannedSymbols.txt"/> </ItemGroup> </Project>
# https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/BannedApiAnalyzers.Help.md T:System.Collections.ICollection;Don't use a non-generic data structure. T:System.Collections.Hashtable;Don't use a non-generic data structure. T:System.Collections.ArrayList;Don't use a non-generic data structure. T:System.Collections.SortedList;Don't use a non-generic data structure. T:System.Collections.Stack;Don't use a non-generic data structure. T:System.Collections.Queue;Don't use a non-generic data structure.
پیشنهاد یک دیکشنری کم دردسرتر!
برای نمونه پس از تنظیمات فوق، مجبور به تغییر تمام hash tableها به دیکشنریهای جدید جنریک خواهیم شد؛ اما ... اگر اینکار را انجام دهیم، برنامهای که تا پیش از این بدون مشکل کار میکرد، اکنون با استثناهای متعدد یافت نشدن کلیدها، خاتمه پیدا میکند! چون دیگر دیکشنریهای جدید، همانند hash tableهای قدیمی، در صورت عدم وجود کلیدی، نال را بازگشت نمیدهند.
برای رفع این مشکل و اصلاح انبوهی از کدها با حداقل تغییرات و عدم تکرار TryGetValueها در همهجا، میتوان دسترسی به ایندکسهای یک دیکشنری استاندارد دات نت را به صورت زیر با ارثبری از آن، بازنویسی کرد:
/// <summary> /// This custom IDictionary doesn't throw a KeyNotFoundException while accessing its value by a given key /// </summary> public interface INullValueDictionary<TKey, TValue> : IDictionary<TKey, TValue> { new TValue this[TKey key] { get; set; } } /// <summary> /// This custom IDictionary doesn't throw a KeyNotFoundException while accessing its value by a given key /// </summary> public class NullValueDictionary<TKey, TValue> : Dictionary<TKey, TValue>, INullValueDictionary<TKey, TValue> { public new TValue this[TKey key] { get => TryGetValue(key, out var val) ? val : default; set => base[key] = value; } }