اگر مطلب «تفاوت بین Interface و کلاس Abstract در چیست؟» را مطالعه کرده باشید، به این نتیجه میرسید که طراحی یک کتابخانهی عمومی با اینترفیسها، بسیار شکنندهاست. اگر عضو جدیدی را به یک اینترفیس عمومی اضافه کنیم، تمام پیاده سازی کنندههای آنرا از درجهی اعتبار ساقط میکند و آنها نیز باید این عضو را حتما پیاده سازی کنند تا برنامهای که پیش از این به خوبی کار میکرده، باز هم بدون مشکل کامپایل شده و کار کند. هدف از ویژگی جدید «پیاده سازیهای پیشفرض در اینترفیسها» در C# 8.0، پایان دادن به این مشکل مهم است. با استفاده از این ویژگی جدید، میتوان یک عضو جدید را با پیاده سازی پیشفرضی داخل خود اینترفیس قرار داد. به این ترتیب تمام برنامههایی که از کتابخانههای عمومی شما استفاده میکنند، با به روز رسانی آن، به یکباره از کار نخواهند افتاد.
همچنین مزیت دیگر آن، انتقال سادهتر کدهای جاوا به سیشارپ است؛ از این لحاظ که ویژگی مشابهی در زبان جاوا تحت عنوان «Default Methods» سالها است که وجود دارد.
یک مثال از ویژگی «پیاده سازیهای پیشفرض در اینترفیسها»
فرض کنید کتابخانهی شما، اینترفیس ILogger را ارائه دادهاست و در برنامهای دیگر، استفاده کننده، کلاس ConsoleLogger را بر مبنای آن پیاده سازی و استفاده کردهاست.
مدتی بعد بر اساس نیازمندیهای مشخصی به این نتیجه خواهید رسید که بهتر است overload دیگری را برای متد Log در اینترفیس ILogger، درنظر بگیریم. مشکلی که این تغییر به همراه دارد، کامپایل نشدن کلاس ConsoleLogger در یک برنامهی ثالث است و این کلاس باید الزاما این overload جدید را پیاده سازی کند؛ در غیراینصورت قادر به کامپایل برنامهی خود نخواهد شد. اکنون در C# 8.0 میتوان برای این نوع تغییرات، در همان اینترفیس اصلی، یک پیاده سازی پیشفرض را نیز قرار داد:
به این ترتیب استفاده کنندگان از این اینترفیس، برای کامپایل برنامهی خود به مشکلی برنخواهند خورد و اگر از این overload جدید استفاده کنند، از همان پیاده سازی پیشفرض آن بهره خواهند برد. بدیهی است هنوز هم پیاده سازی کنندههای اینترفیس ILogger میتوانند پیاده سازیهای سفارشی خودشان را در مورد این overload جدید ارائه دهند. در این حالت از پیاده سازی پیشفرض صرفنظر خواهد شد.
ویژگی «پیاده سازیهای پیشفرض در اینترفیسها» چگونه پیاده سازی شدهاست؟
واقعیت این است که امکان پیاده سازی این ویژگی، سالها است که در سطح کدهای IL دات نت وجود داشته (از زمان دات نت 2) و اکنون از طریق کدهای برنامه با بهبود کامپایلر آن، قابل دسترسی شدهاست.
تاثیر زمینهی کاری بر روی دسترسی به پیاده سازیهای پیشفرض
مثال زیر را درنظر بگیرید:
در اینجا اینترفیس IDeveloper، به همراه یک پیاده سازی پیشفرض است و بر این اساس، کلاس BackendDev پیاده سازی کنندهی آن، دیگر نیازی به پیاده سازی اجباری متد LearnNewLanguage ای که تنها یک رشته را میپذیرد، ندارد.
سؤال: به نظر شما اکنون کدامیک از کاربردهای زیر از کلاس BackendDev، کامپایل میشود و کدامیک خیر؟
پاسخ: فقط مورد اول. مورد دوم با خطای کامپایلر زیر مواجه خواهد شد:
به این معنا که اگر کلاس BackendDev را به خودی خود (دقیقا از نوع BackendDev) و بدون معرفی آن از نوع اینترفیس IDeveloper، بکار بگیریم، فقط همان متدهایی که داخل این کلاس تعریف شدهاند، قابل دسترسی میباشند و نه متدهای پیشفرض تعریف شدهی در اینترفیس مشتق شدهی از آن.
ارثبری چندگانه چطور؟
احتمالا حدس زدهاید که این قابلیت ممکن است ارثبری چندگانه را که در سیشارپ ممنوع است، میسر کند. تا C# 8.0، یک کلاس تنها از یک کلاس دیگر میتواند مشتق شود؛ اما این محدودیت در مورد اینترفیسها وجود ندارد. به علاوه تاکنون اینترفیسها مانند کلاسها، امکان تعریف پیاده سازی خاصی را نداشتند و صرفا یک قرارداد بیشتر نبودند. بنابراین اکنون این سؤال مطرح میشود که آیا میتوان با ارائهی پیاده سازی پیشفرض متدها در اینترفیسها، ارثبری چندگانه را در سیشارپ پیاده سازی کرد؛ مانند مثال زیر؟!
سؤال: کد فوق بدون مشکل کامپایل میشود. اما در فراخوانی زیر، دقیقا از کدام متد LearnNewLanguage استفاده خواهد شد؟ آیا پیاده سازی آن از IBackendDev فراهم میشود و یا از IFrontendDev؟
پاسخ: هیچکدام! برنامه با خطای زیر کامپایل نخواهد شد:
کامپایلر سیشارپ در این مورد خاص از قانونی به نام «the most specific override rule» استفاده میکند. یعنی اگر برای مثال در IFullStackDev متد LearnNewLanguage به صورت صریحی بازنویسی و تامین شد، آنگاه امکان استفادهی از آن وجود خواهد داشت. یا حتی میتوان این پیاده سازی را در کلاس Dev نیز ارائه داد و از نوع آن (بجای نوع اینترفیس) استفاده کرد.
تفاوت امکانات کلاسهای Abstract با متدهای پیشفرض اینترفیسها چیست؟
اینترفیسها هنوز نمیتوانند مانند کلاسها، سازندهای را تعریف کنند. نمیتوانند متغیرها/فیلدهایی را در سطح اینترفیس داشته باشند. همچنین در اینترفیسها همهچیز public است و امکان تعریف سطح دسترسی دیگری وجود ندارد.
بنابراین باید بخاطر داشت که هدف از تعریف اینترفیسها، ارائهی «یک رفتار» است و هدف از تعریف کلاسها، ارائه «یک حالت».
یک نکته: در نگارشهای پیش از C# 8.0 هم میتوان ویژگی «متدهای پیشفرض» را شبیه سازی کرد
واقعیت این است که توسط ویژگی «متدهای الحاقی»، سالها است که امکان افزودن «متدهای پیشفرضی» به اینترفیسها در زبان سیشارپ وجود دارد:
و در این حالت هرچند به نظر اینترفیس IMyInterface دارای متدی نیست، اما فراخوانی زیر مجاز است:
همچنین مزیت دیگر آن، انتقال سادهتر کدهای جاوا به سیشارپ است؛ از این لحاظ که ویژگی مشابهی در زبان جاوا تحت عنوان «Default Methods» سالها است که وجود دارد.
یک مثال از ویژگی «پیاده سازیهای پیشفرض در اینترفیسها»
interface ILogger { void Log(string message); } class ConsoleLogger : ILogger { public void Log(string message) { Console.WriteLine(message); } }
مدتی بعد بر اساس نیازمندیهای مشخصی به این نتیجه خواهید رسید که بهتر است overload دیگری را برای متد Log در اینترفیس ILogger، درنظر بگیریم. مشکلی که این تغییر به همراه دارد، کامپایل نشدن کلاس ConsoleLogger در یک برنامهی ثالث است و این کلاس باید الزاما این overload جدید را پیاده سازی کند؛ در غیراینصورت قادر به کامپایل برنامهی خود نخواهد شد. اکنون در C# 8.0 میتوان برای این نوع تغییرات، در همان اینترفیس اصلی، یک پیاده سازی پیشفرض را نیز قرار داد:
interface ILogger { void Log(string message); void Log(Exception exception) => Console.WriteLine(exception); }
ویژگی «پیاده سازیهای پیشفرض در اینترفیسها» چگونه پیاده سازی شدهاست؟
واقعیت این است که امکان پیاده سازی این ویژگی، سالها است که در سطح کدهای IL دات نت وجود داشته (از زمان دات نت 2) و اکنون از طریق کدهای برنامه با بهبود کامپایلر آن، قابل دسترسی شدهاست.
تاثیر زمینهی کاری بر روی دسترسی به پیاده سازیهای پیشفرض
مثال زیر را درنظر بگیرید:
interface IDeveloper { void LearnNewLanguage(string language, DateTime dueDate); void LearnNewLanguage(string language) { // default implementation LearnNewLanguage(language, DateTime.Now.AddMonths(6)); } } class BackendDev : IDeveloper // compiles OK { public void LearnNewLanguage(string language, DateTime dueDate) { // Learning new language... } }
سؤال: به نظر شما اکنون کدامیک از کاربردهای زیر از کلاس BackendDev، کامپایل میشود و کدامیک خیر؟
IDeveloper dev1 = new BackendDev(); dev1.LearnNewLanguage("Rust"); var dev2 = new BackendDev(); dev2.LearnNewLanguage("Rust");
There is no argument given that corresponds to the required formal parameter 'dueDate' of 'BackendDev.LearnNewLanguage(string, DateTime)' (CS7036) [ConsoleApp]
ارثبری چندگانه چطور؟
احتمالا حدس زدهاید که این قابلیت ممکن است ارثبری چندگانه را که در سیشارپ ممنوع است، میسر کند. تا C# 8.0، یک کلاس تنها از یک کلاس دیگر میتواند مشتق شود؛ اما این محدودیت در مورد اینترفیسها وجود ندارد. به علاوه تاکنون اینترفیسها مانند کلاسها، امکان تعریف پیاده سازی خاصی را نداشتند و صرفا یک قرارداد بیشتر نبودند. بنابراین اکنون این سؤال مطرح میشود که آیا میتوان با ارائهی پیاده سازی پیشفرض متدها در اینترفیسها، ارثبری چندگانه را در سیشارپ پیاده سازی کرد؛ مانند مثال زیر؟!
using System; namespace ConsoleApp { public interface IDev { void LearnNewLanguage(string language) => Console.Write($"Learning {language} in a default way."); } public interface IBackendDev : IDev { void LearnNewLanguage(string language) => Console.Write($"Learning {language} in a backend way."); } public interface IFrontendDev : IDev { void LearnNewLanguage(string language) => Console.Write($"Learning {language} in a frontend way."); } public interface IFullStackDev : IBackendDev, IFrontendDev { } public class Dev : IFullStackDev { } }
IFullStackDev dev = new Dev(); dev.LearnNewLanguage("TypeScript");
The call is ambiguous between the following methods or properties: 'IBackendDev.LearnNewLanguage(string)' and 'IFrontendDev.LearnNewLanguage(string)' (CS0121)
تفاوت امکانات کلاسهای Abstract با متدهای پیشفرض اینترفیسها چیست؟
اینترفیسها هنوز نمیتوانند مانند کلاسها، سازندهای را تعریف کنند. نمیتوانند متغیرها/فیلدهایی را در سطح اینترفیس داشته باشند. همچنین در اینترفیسها همهچیز public است و امکان تعریف سطح دسترسی دیگری وجود ندارد.
بنابراین باید بخاطر داشت که هدف از تعریف اینترفیسها، ارائهی «یک رفتار» است و هدف از تعریف کلاسها، ارائه «یک حالت».
یک نکته: در نگارشهای پیش از C# 8.0 هم میتوان ویژگی «متدهای پیشفرض» را شبیه سازی کرد
واقعیت این است که توسط ویژگی «متدهای الحاقی»، سالها است که امکان افزودن «متدهای پیشفرضی» به اینترفیسها در زبان سیشارپ وجود دارد:
namespace MyNamespace { public interface IMyInterface { IList<int> Values { get; set; } } public static class MyInterfaceExtensions { public static int CountGreaterThan(this IMyInterface myInterface, int threshold) { return myInterface.Values?.Where(p => p > threshold).Count() ?? 0; } } }
var myImplementation = new MyInterfaceImplementation(); // Note that there's no typecast to IMyInterface required var countGreaterThanFive = myImplementation.CountGreaterThan(5);
اگر مایل هستید که پروژه خود را به صورت سورس باز ارائه دهید، نیاز است یک سری شرایط را رعایت کنید تا کاربران این پروژه بتوانند به سادگی از آن استفاده نمایند.
- فایل ReadMe را فراموش نکنید
حتی اگر پروژه شما از یک سایت اختصاصی استفاده میکند، اولین محلی که عموم کاربران برای دریافت اطلاعات کار با پروژه، به آن مراجعه میکنند، فایل ReadMe برنامه است. این فایل میتواند حاوی مشخصات ذیل باشد:
الف) وابستگیهای پروژه را مشخص کنید
واقعیت این است که برخلاف شمای برنامه نویس، عموم استفاده کنندگان، آشنایی چندانی با جزئیات محیط و شرایط تهیه برنامه شما ندارند. به این ترتیب بسیاری از مسایلی که برای شما بدیهی هستند، برای عموم اینگونه نخواهند بود. بنابراین مسالهای که به سرعت میتواند سبب خشم کاربران و صرفنظر از کار شما گردد، مشخص نبودن نحوه نصب و وابستگیهای لازم برای اجرای برنامه است.
ب) وضعیت بلوغ پروژه خود را مشخص کنید
آیا از این برنامه، مدتی است که در محیط کاری استفاده میکنید؟ آیا به نظر شما هنوز ناتمام است؟ آیا API کتابخانه شما در نگارش بعدی کاملا دگرگون خواهد شد؟ تمام این مسایل و سؤالات را به نحو واضحی توضیح دهید و مشخص کنید. همین توضیحات کوتاه میتوانند ساعتهای بسیاری از زندگی دیگران را صرفه جویی کند.
ج) اگر پروژه شما یک کتابخانه است، نوع زبان و Runtimeهای پشتیبانی شده را مشخص کنید
برای مثال اگر یک کتابخانه دات نتی را ارائه میدهید، مشخص کنید که از کدام نگارش دات نت به بعد را پشتیبانی میکنید.
د) مجوز استفاده از پروژه را مشخص کنید
مطلب مقایسه مجوزهای سورس باز را یکبار مطالعه نمائید و سپس مجوز صحیحی را برای کار خود انتخاب کنید. همچنین آنرا به نحو واضحی در مستندات پروژه خود قید نمائید.
به علاوه بهخاطر داشته باشید که امکان ارائه مجوزهای دوگانه مانند AGPL نیز وجود دارند. در این حالت کاربر یا باید سورس محصول خودش را ارائه دهد، یا مجوز کتابخانه شما را خریداری کند. مانند RavenDB که از این نوع مجوز استفاده میکند.
- یک پروژه نیاز به مستندات دارد
مستند سازی کار، سخت و زمانبر است؛ اما بهترین لطفی است که میتوانید به کاربران خود نمائید. مستندات نه تنها زمان جستجوی بسیاری را صرفه جویی خواهند کرد، همچنین حس اطمینان خاطر را به کاربر القاء میکنند. از این جهت که احساس میکنند شما برای کارتان ارزش قائل بودهاید و احتمال اینکه این برنامه در آینده نزدیک به یک abandonware تبدیل شود، کم است (منظور یک برنامه فراموش شده و خاتمه یافته).
- به روز رسانی را ساده کنید
بالاخره زمانی نیاز خواهد بود تا نگارش جدیدی از کار خود را ارائه دهید. در این حالت نیاز است یک سری از شرایط را مدنظر داشته باشید:
الف) سازگاری قبلی را مدنظر داشته باشید
یکی از بدترین حالات به روز رسانی یک کتابخانه زمانی است که کاربر آن با دهها خطای کامپایل حاصل از به روز رسانی مواجه شود. اگر نیاز است قسمتی از کد خود را حذف کنید یا تغییر دهید، استفاده از ویژگی Obsolete را فراموش نکنید و اینکار باید مرحله به مرحله انجام شود. در یک نگارش، ویژگی Obsolete را معرفی کنید. در دو نگارش بعد، API را تغییر دهید.
ب) حتما یک Change log را تکمیل کنید
پس از ارائه یک نگارش جدید، حداقل در چند سطر مشخص کنید که چه مواردی تغییر کردهاند، چه مواردی اضافه شدهاند و چه مواردی را حذف کردهاید.
همچنین اگر مواردی تغییر کردهاند، نحوه ارتقاء کدهای قدیمی را به نگارش جدید، شرح دهید. اگر مورد جدیدی اضافه شدهاست، لینکی را به مثالی دربارهی آن ارائه دهید.
- نگارشهای جدید را اعلام کنید
برای مثال در طی ارائه یک مطلب جدید در وبلاگ خود، ارائه نگارش جدیدی از کتابخانه یا برنامه خود را به عموم اعلام کنید. در این حالت، حتما لینکی را به change log، ارائه داده و مشخص کنید که وضعیت سازگاری آن با قبل چگونه است.
- محلی را برای دریافت بازخوردهای پروژه خود مشخص کنید
نیاز است بتوانید پروژه خود را پشتیبانی کنید یا به سؤالات مربوطه پاسخ دهید. اگر سورس کنترل یا برنامه مدیریت پروژه شما، امکان پرسش و پاسخ را دارد، که بسیار خوب. اگر خیر، میتوانید مثلا یک گروه گوگل جدید و امثال آنرا برای دریافت بازخوردهای پروژه ایجاد کنید.
همچنین نیاز است لینک به این محل را در فایل ReadME پروژه به صراحت مشخص کنید.
- گذر از پروژه
بالاخره روزی فراخواهد رسید که دیگر علاقهای به نگهداری پروژه نداشته باشید. این مساله را در مکان جمع آوری بازخوردهای خود اعلام کنید یا شخص دیگری را به نگهداری پروژه دعوت نمائید. اگر این کار را انجام ندهید، سبب خواهید شد forkهای متعددی از این پروژه بیجهت ایجاد شده و در نهایت مشخص نباشد که کدامیک بهتر است و کدامیک مشکلات کمتری دارند.
- فایل ReadMe را فراموش نکنید
حتی اگر پروژه شما از یک سایت اختصاصی استفاده میکند، اولین محلی که عموم کاربران برای دریافت اطلاعات کار با پروژه، به آن مراجعه میکنند، فایل ReadMe برنامه است. این فایل میتواند حاوی مشخصات ذیل باشد:
الف) وابستگیهای پروژه را مشخص کنید
واقعیت این است که برخلاف شمای برنامه نویس، عموم استفاده کنندگان، آشنایی چندانی با جزئیات محیط و شرایط تهیه برنامه شما ندارند. به این ترتیب بسیاری از مسایلی که برای شما بدیهی هستند، برای عموم اینگونه نخواهند بود. بنابراین مسالهای که به سرعت میتواند سبب خشم کاربران و صرفنظر از کار شما گردد، مشخص نبودن نحوه نصب و وابستگیهای لازم برای اجرای برنامه است.
ب) وضعیت بلوغ پروژه خود را مشخص کنید
آیا از این برنامه، مدتی است که در محیط کاری استفاده میکنید؟ آیا به نظر شما هنوز ناتمام است؟ آیا API کتابخانه شما در نگارش بعدی کاملا دگرگون خواهد شد؟ تمام این مسایل و سؤالات را به نحو واضحی توضیح دهید و مشخص کنید. همین توضیحات کوتاه میتوانند ساعتهای بسیاری از زندگی دیگران را صرفه جویی کند.
ج) اگر پروژه شما یک کتابخانه است، نوع زبان و Runtimeهای پشتیبانی شده را مشخص کنید
برای مثال اگر یک کتابخانه دات نتی را ارائه میدهید، مشخص کنید که از کدام نگارش دات نت به بعد را پشتیبانی میکنید.
د) مجوز استفاده از پروژه را مشخص کنید
مطلب مقایسه مجوزهای سورس باز را یکبار مطالعه نمائید و سپس مجوز صحیحی را برای کار خود انتخاب کنید. همچنین آنرا به نحو واضحی در مستندات پروژه خود قید نمائید.
به علاوه بهخاطر داشته باشید که امکان ارائه مجوزهای دوگانه مانند AGPL نیز وجود دارند. در این حالت کاربر یا باید سورس محصول خودش را ارائه دهد، یا مجوز کتابخانه شما را خریداری کند. مانند RavenDB که از این نوع مجوز استفاده میکند.
- یک پروژه نیاز به مستندات دارد
مستند سازی کار، سخت و زمانبر است؛ اما بهترین لطفی است که میتوانید به کاربران خود نمائید. مستندات نه تنها زمان جستجوی بسیاری را صرفه جویی خواهند کرد، همچنین حس اطمینان خاطر را به کاربر القاء میکنند. از این جهت که احساس میکنند شما برای کارتان ارزش قائل بودهاید و احتمال اینکه این برنامه در آینده نزدیک به یک abandonware تبدیل شود، کم است (منظور یک برنامه فراموش شده و خاتمه یافته).
- به روز رسانی را ساده کنید
بالاخره زمانی نیاز خواهد بود تا نگارش جدیدی از کار خود را ارائه دهید. در این حالت نیاز است یک سری از شرایط را مدنظر داشته باشید:
الف) سازگاری قبلی را مدنظر داشته باشید
یکی از بدترین حالات به روز رسانی یک کتابخانه زمانی است که کاربر آن با دهها خطای کامپایل حاصل از به روز رسانی مواجه شود. اگر نیاز است قسمتی از کد خود را حذف کنید یا تغییر دهید، استفاده از ویژگی Obsolete را فراموش نکنید و اینکار باید مرحله به مرحله انجام شود. در یک نگارش، ویژگی Obsolete را معرفی کنید. در دو نگارش بعد، API را تغییر دهید.
ب) حتما یک Change log را تکمیل کنید
پس از ارائه یک نگارش جدید، حداقل در چند سطر مشخص کنید که چه مواردی تغییر کردهاند، چه مواردی اضافه شدهاند و چه مواردی را حذف کردهاید.
همچنین اگر مواردی تغییر کردهاند، نحوه ارتقاء کدهای قدیمی را به نگارش جدید، شرح دهید. اگر مورد جدیدی اضافه شدهاست، لینکی را به مثالی دربارهی آن ارائه دهید.
- نگارشهای جدید را اعلام کنید
برای مثال در طی ارائه یک مطلب جدید در وبلاگ خود، ارائه نگارش جدیدی از کتابخانه یا برنامه خود را به عموم اعلام کنید. در این حالت، حتما لینکی را به change log، ارائه داده و مشخص کنید که وضعیت سازگاری آن با قبل چگونه است.
- محلی را برای دریافت بازخوردهای پروژه خود مشخص کنید
نیاز است بتوانید پروژه خود را پشتیبانی کنید یا به سؤالات مربوطه پاسخ دهید. اگر سورس کنترل یا برنامه مدیریت پروژه شما، امکان پرسش و پاسخ را دارد، که بسیار خوب. اگر خیر، میتوانید مثلا یک گروه گوگل جدید و امثال آنرا برای دریافت بازخوردهای پروژه ایجاد کنید.
همچنین نیاز است لینک به این محل را در فایل ReadME پروژه به صراحت مشخص کنید.
- گذر از پروژه
بالاخره روزی فراخواهد رسید که دیگر علاقهای به نگهداری پروژه نداشته باشید. این مساله را در مکان جمع آوری بازخوردهای خود اعلام کنید یا شخص دیگری را به نگهداری پروژه دعوت نمائید. اگر این کار را انجام ندهید، سبب خواهید شد forkهای متعددی از این پروژه بیجهت ایجاد شده و در نهایت مشخص نباشد که کدامیک بهتر است و کدامیک مشکلات کمتری دارند.
Angular یکی از محبوبترین فریم ورکها، برای ساختن برنامههای تک صفحهای میباشد. اگرچه گفتیم تک صفحهای، اما ضرورتا منظور این نیست که پروژهی شما تنها شامل یک صفحه باشد. شما میتوانید با Angular یک وب سایت را با هزاران صفحه نیز ایجاد کنید. با این حال وقتی بحث از کارآیی باشد، بهتر است همیشه در رابطه با تعدادی فکر کنیم که باعث میشوند پروژه، نرم و سریع اجرا شود.
توجه کنید که اگر خروجی برنامه شما مستقیما در dist میباشد، به صورت بالا عمل کنید؛ ولی اگر خروجی برنامه شما در پوشهی dist/YourApplicationName باشد، آن را به حالت زیر ویرایش کنید:
دستور بالا یک بیلد را برای پروژه برای حالت ارائهی نهایی (Production) همراه با آمارهایی در رابطه با هر باندل ایجاد میکند. در اینجا میتوانیم ببینیم که چه ماژولها/فایلهایی در هر باندل استفاده شدهاست. این مورد فوق العاده کمک میکند. هم چنین میتوانیم به صورت بصری ببینیم که چه چیزهایی در هر ماژول شامل شدهاند که بهتر بود آنجا نباشند:
#1 Optimize main bundle with Lazy Loading
وقتی که پروژه را برای حالت ارائهی نهایی (Production ) بدون در نظر گرفتن Lazy Load، بیلد میکنیم احتمالا فایلهای تولید شده زیر را در پوشهی dist خواهیم دید:
- polyfills.js : برای ساختن برنامههای سازگار با انواع مرورگرها میباشد. به دلیل اینکه وقتی کدها را با جدیدترین ویژگیها مینویسیم، ممکن است که همهی مرورگرها توانایی پشتیبانی از آن ویژگیها را نداشته باشند.
- scripts.js : شامل اسکریپتهایی میباشد که در بخش scripts، در فایل angular.json تعریف کردهایم.
- webpack loader : runtime.js میباشد. این فایل شامل webpack utilitiesهایی میباشد که برای بارگذاری دیگر فایلها مورد نیاز است.
- styles.css : شامل style هایی است که در بخش styles، در فایل angular.json تعریف کردهایم.
- main.js : شامل تمامی کدها از قبیل کامپوننتها ( کدهای مربوط به css ، html ، ts) ، دایرکتیوها، pipes و سرویسها و ماژولهای ایمپورت شده از جمله third partyهای میباشد.
همانطور که متوجه هستید، فایل main.js در طول زمان بزرگتر و بزرگتر خواهد شد که این یک مشکل است. در این حالت برای مشاهدهی وب سایت، مرورگر نیاز دارد که فایل main.js را دانلود کرده و سپس در صفحه، آن را اجرا و رندر کند که این یک چالش برای کاربران موبایل با اینترنت ضعیف و هم چنین کاربران دسکتاپ میباشد.
آسانترین راه برای برطرف کردن این مشکل این است که پروژه را به چندین ماژول lazy load، تقسیم کنیم. وقتی که از lazy moduleها استفاده میکنیم، انگیولار chunk مربوط به آن را تولید میکند که در ابتدا بارگذاری نخواهد شد؛ مگر اینکه مورد نیاز باشند (معمولا با فعال سازی یک مسیر اتفاق میافتد).
وقتی که از lazy loading استفاده میکنیم، بعد از فرایند build، فایلهای جدیدی تولید خواهند شد، مثل 4.386205799sfghe4.js که یک چانک از یک lazy module میباشد و در زمان راه اندازی صفحه اول اجرا نخواهد شد که نتیجهی آن داشتن فایل main.js ای با حجم کم میباشد. بنابراین بارگذاری صفحهی اول، خیلی سریع انجام خواهد شد.
با این حال، بارگذاری هر قسمت میتواند بر روی کارآیی تاثیر داشته باشد (بارگذاری ممکن است کند باشد). خوشبختانه انگیولار یک راه را برای برطرف کردن این مشکل فراهم کرده است ( PreloadingStrategy ) . بعد از اینکه فایل main.js به صورت کامل بارگذاری و اجرا شد، کار پیش واکشی ماژولها را انجام میدهد و زمانیکه کاربری مسیری را درخواست میدهد، آْن مسیر را بدون درنگ مشاهده خواهد کرد.
مطالعه بیشتر : مسیریابی در Angular - قسمت دهم - Lazy loading
#2 Debug bundles with Webpack Bundle Analyzer
حتی ممکن است بعد از تقسیم کردن منطق برنامه به چند ماژول lazy load، شما یک فایل main.js بزرگ داشته باشید. در این حالت میتوانید بهینه سازی بیشتری را با استفاده از Webpack Bundle Analyzer انجام دهید. با استفاده از این پکیج میتوانید آمارهایی را در رابطه با هر باندل داشته باشید. در ابتدا با استفاده از دستور زیر پکیج آنرا نصب کنید:
npm install --save-dev webpack-bundle-analyzer
سپس فایل package.json را باز کرده و در بخش scripts، مدخل زیر را اضافه کنید:
"bundle-report": "ng build --prod --stats-json && webpack-bundle-analyzer dist/stats.json"
"bundle-report": "ng build --prod --stats-json && webpack-bundle-analyzer dist/YourApplicationName/stats.json"
در نهایت دستور زیر را اجرا کنید:
npm run bundle-report
#3 Use Lazy Loading for images that are not visible in page
وقتی که صفحه اصلی را در اولین بار، بارگذاری میکنیم، میتوانیم تصاویری را داشته باشیم که در صفحهی نمایش، نمایان نباشند (منظور viewport میباشد) و کاربر برای دیدن آن تصاویر باید صفحه را به پایین اسکرول کند. با این وجود وقتی که صفحه بارگذاری میشود، تصاویر هم بلافاصله دانلود میشوند. اگر تعداد تصاویر زیاد باشند این مورد میتواند بر روی performance تاثیر منفی داشته باشد. برای برطرف کردن این مشکل میتوان از lazy loading تصاویر استفاده کرد. در این حالت تصاویر زمانی بارگذاری میشوند که کاربر به آنها میرسد. یک JavaScript API به نام Intersection Observer API وجود دارد که باعث میشود پیاده سازی lazy load خیلی آسان شود. علاوه بر این میتوان یک دایرکتیو را با قابلیت استفاده مجدد طراحی کرد ( lazy loading برای تصاویر با استفاده از Intersection Observer).
#4 Use virtual scrolling for large lists
با استفاده از virtual scrolling میتوان المنتها را در Dom بر اساس بخشهای قابل مشاهدهای از یک لیست، Load یا unload کرد که برنامه را فوق العاده سریع میکند.
#5 Use FOUT instead of FOIT for fonts
در بیشتر وب سایتها میتوان فونتهای سفارشی زیبایی را به جای فونتهای معمول دید. با این حال برای استفاده از فونتهای فراهم شده توسط سرویسهای دیگر لازم است که مرورگر آنها را دانلود و parse کند. اگر از فونتهای سفارشی استفاده کنیم، مثل Google Fonts، چه اتفاقی میافتد؟ در اینجا دو سناریو وجود دارد:
- در این حالت مرورگر منتظر میماند تا فونت دانلود شود و آن را parse کند و تنها بعد از آن متن را بر روی صفحه نمایش میدهد. متن روی صفحه تا زمانیکه فونت دانلود و parse نشده باشد، قابل مشاهده نیست؛ این FOIT است (Flash of invisible text) .
- مرورگر در ابتدا متن را با استفاده از فونت معمول نمایش میدهد و بعد از آن سعی میکند که ساختارهای فونت خارجی را دریافت کند. وقتی که دانلود انجام شد و سپس آن را parse کرد، فونت سفارشی دانلود شده، با فونت معمول جایگزین خواهد شد؛ این FOUT است ( Flash of unstyled text ).
بیشتر مرورگرها از FOIT استفاده میکنند و تنها Internet Explorer از FOUT استفاده میکند. برای برطرف کردن این مشکل میتوان از توصیفگر font-display استفاده کرد و به مرورگر بگوییم که میخواهیم در ابتدا متن را با فونت معمول نمایش دهیم و جایگزینی فونت، بعد از دانلود باشد (بیشتر).
یکی از امکانات کمتر شناخته شده در دات نت، امکان تولید اتوماتیک کد (Code Generator)، توسط فایلهایی به عنوان Text template میباشد. اگر چه فایلهای Text template با پسوند tt از Visual Studio 2008 بطور آشکار به IDE اضافه گردیدهاند، این امکان پیش از این نیز بصورت توکار در Visual Studio جهت تولید کد دات نت برای ابزارهایی مانند Dataset ، Report و ... وجود داشته است. در حال حاضر Visual Studio بصورت توکار از این امکان برای تولیدکلاسها و کدهای EntityFramework ( edmx ) ،dbml ، Dataset و ... استفاده مینماید.
شما نیز با دانستن قواعد ساده کد نویسی برای Text templateها میتوانید از این امکان برای تولید اتوماتیک کلاسها، HTML ، XML و بطور کلی هر نوع فایل متنی استفاده نمایید. کاربردهای عملی Text template بیشتر برای تولید کد از روی دیتابیس و مثلا بر اساس فیلدهای هر جدول و امثال آن میباشد. اما با کمی خلاقیت استفادههای بسیار زیاد و جالبی را میتوانید از آن مشاهده نمایید.
کاربردهایی مانند : تولید خود کار فایل Config، تولید خودکار Unit Test، تولید خودکار کلاسهای CRUD برای هر موجودیت در لایه Data، تولید خودکار SQL برای StoredProcedure ها، تولید خودکار Html و Js، تولید خودکار فرمهای ASPX و ... برای فرمهای ثابت و تکراری با فرآیند مشخص، تولید خودکار مستندات پروژه در قالب Word Document , … ، تولید خودکار فایلهای Excel از اطلاعات خاص و تولید خودکار فرمهای xaml و .....
در این آموزش با قواعد اصلی نوشتن کد برای Text templateها آشنا میشویم و چند نمونه از کاربردهای آن را به عنوان مثال کاربردی مورد بررسی قرار خواهیم داد.
برای شروع قبل از توضیح در مورد قواعد کد نویسی Text template، یک فایل Text template ایجاد نمایید. برای ایجاد، از پنجره Add New Item یک فایل Text template به پروژه اضافه نمایید:
Language زبانی را مشخص میکند که میخواهید با آن داخل فایل Text template کد نویسی نمایید. تعیین آن برای کامپایل الزامی است.
Extension نیز پسوند فایل خروجی تولید شده را مشخص مینماید .
اگر در ادامه متن Hello dotnettips را بنویسید و فایل را Save کنید، کنار فایل tt مذکور، یک فایل با همان نام و پسوند txt ایجاد خواهد شد که داخل آن نوشته است:
Hello dotnettips
برای ایجاد فایل خروجی همچنین میتوانید روی فایل tt کلیک راست نموده و از منوی باز شده Run Custom Tool را انتخاب نمایید. توجه داشته باشید که در تنظیمات Custom Tool باید مقدار TextTemplatingFileGenerator وجود داشته باشد:
شما نیز با دانستن قواعد ساده کد نویسی برای Text templateها میتوانید از این امکان برای تولید اتوماتیک کلاسها، HTML ، XML و بطور کلی هر نوع فایل متنی استفاده نمایید. کاربردهای عملی Text template بیشتر برای تولید کد از روی دیتابیس و مثلا بر اساس فیلدهای هر جدول و امثال آن میباشد. اما با کمی خلاقیت استفادههای بسیار زیاد و جالبی را میتوانید از آن مشاهده نمایید.
کاربردهایی مانند : تولید خود کار فایل Config، تولید خودکار Unit Test، تولید خودکار کلاسهای CRUD برای هر موجودیت در لایه Data، تولید خودکار SQL برای StoredProcedure ها، تولید خودکار Html و Js، تولید خودکار فرمهای ASPX و ... برای فرمهای ثابت و تکراری با فرآیند مشخص، تولید خودکار مستندات پروژه در قالب Word Document , … ، تولید خودکار فایلهای Excel از اطلاعات خاص و تولید خودکار فرمهای xaml و .....
در این آموزش با قواعد اصلی نوشتن کد برای Text templateها آشنا میشویم و چند نمونه از کاربردهای آن را به عنوان مثال کاربردی مورد بررسی قرار خواهیم داد.
برای شروع قبل از توضیح در مورد قواعد کد نویسی Text template، یک فایل Text template ایجاد نمایید. برای ایجاد، از پنجره Add New Item یک فایل Text template به پروژه اضافه نمایید:
<#@ template debug="false" hostspecific="false" language="C#" #> <#@ output extension=".txt" #>
Extension نیز پسوند فایل خروجی تولید شده را مشخص مینماید .
اگر در ادامه متن Hello dotnettips را بنویسید و فایل را Save کنید، کنار فایل tt مذکور، یک فایل با همان نام و پسوند txt ایجاد خواهد شد که داخل آن نوشته است:
Hello dotnettips
برای ایجاد فایل خروجی همچنین میتوانید روی فایل tt کلیک راست نموده و از منوی باز شده Run Custom Tool را انتخاب نمایید. توجه داشته باشید که در تنظیمات Custom Tool باید مقدار TextTemplatingFileGenerator وجود داشته باشد:
خوب؛ برای ادامه، باید با قواعد کد نویسی در Text template آشنا شوید. جلسه بعدی قواعد کد نویسی T4 را بررسی خواهیم کرد.
در فصل دوم کتاب تا به الان یاد گرفتیم چگونه ماژولها را کامپایل کنیم و چگونه آنها را در یک اسمبلی قرار دهیم. حال وقت آن فرا رسیده است که با بسته بندی کردن (Package) و انتشار آن (Deploy) به طوری که کاربران بتوانند برنامه را اجرا کنند آشنا شویم.
نصب برنامه از طریق فروشگاه ویندوز
در فروشگاه ویندوز Windows Store Apps قوانین سخت و شدیدی برای بسته بندی کردن اسمبلیها وجود دارد. ویژوال استودیو تمام اسمبلیهای مورد نیاز برنامه را در یک فایل با پسوند appx قرار داده و آن را به سمت فروشگاه آپلود میکند. هر کاربری که این فایل appx را نصب کند، همهی اسمبلیهایی را که در دایرکتوری مربوطه قرار گرفته است، توسط CLR بار شده و آیکن برنامه هم در صفحهی start ویندوز قرار میگیرد و اگر دیگر کاربران همان سیستم هم این فایل appx را نصب کنند، از آنجا که قبلا روی سیستم موجود هست، تنها آیکن برنامه به صفحهی start اضافه میگردد و برای حذف هم تنها آیکن برنامه از روی این صفحه حذف میشود؛ مگر اینکه تنها کاربری باشد که این برنامه را نصب کردهاست که در آن صورت کلا همهی اسمبلیهای آن از روی سیستم حذف میشود.
در صورتیکه کاربرهای مختلف نسخههای مختلفی از همان برنامه را روی سیستم نصب کنند، برای اسمبلیها هر کدام یک دایرکتوری ایجاد شده و به ازای نسخهی نصب شده آن کاربر، یکی از این دایرکتوریها مورد استفاده قرار میگیرند. کاربران مختلف میتوانند روی سیستم به طور همزمان از نسخههای مختلف برنامه استفاده کنند.
روشهای پکیج گذاری
برای برنامههای دسکتاپ که ربطی به فروشگاه ندارند و بین ایرانیان طرفدار زیادی دارد، نیازی به استفاده از هیچ روش خاصی نیست و یک کپی معمولی هم کفایت میکند. همهی فایلهای مثل اسمبلی، باید در یک دایرکتوری قرار گرفته و به روش کپی کردن آن را انتقال داد. یا برای بسته بندی از یک فایل batch کمک گرفت و آن را روی سیستم نصب کرد و نیازی به هیچ تغییری در رجیستری نیست. برای حذف برنامه هم، حذف معمولی دایرکتوری مربوطه کفایت میکند.
البته گزینههای دیگری هم برای پکیج کردن این نوع برنامهها وجود دارند:
یکی از روشهای پکیج کردن فایلها به صورت cab هست که عموما برای سناریوهای اینترنتی و فشرده سازی و کاهش زمان دانلود به کار میرود.
روش دوم استفاده از پکیج MSI است که توسط سرویس نصب مایکروسافت Microsoft Installer Service یا MSIExec.exe انجام میگیرد. فایلهای MSI به اسمبلیها اجازه میدهند که بر اساس زمان تقاضای CLR برای بارگیری اولیه نصب شوند. البته این ویژگی جدیدی نیست و برای فایلهای exe یا dll مدیریت نشده هم به کار میرود.
استفاده از نصاب سازها
بهتر هست که برای انتشار برنامه از برنامههای نصاب سازی استفاده کنید که با واسطی جذابتر به نصب پرداخته و امکاناتی از قبیل shotrcutها، حذف و بازیابی و نصب و .. را هم به کاربر میدهند.
نصاب سازهای متفاوتی وجود دارند که در زیر به تعدادی از آنها اشاره میکنیم:
Install Shield (+ ) : این برنامه نسخههای متفاوتی را با قیمتهای متفاوتی، عرضه میکند و در این زمینه، جزء بهترینها نام برده میشود. حتی ویژگیهای مخصوصی هم برای ویژوال استودیو دارد. شرکت سازنده، برنامهی دیگری را هم اخیر تحت نام Install Anywhere عرضه کرده است که اجازه میدهد از روی یک برنامه برای پلتفرمهای مختلف setup بسازد.
NSIS : این برنامه هم در زمینهی ساخت setup محبوبیت زیادی دارد. این برنامه به صورت متن باز منتشر شده و رایگان است. امکانات این برنامه ساده است و برای راه اندازی سریع یک setup و اجرای راحت آن توسط کاربر، کاملا کاربردی است.
Tarma Installmate : این نرم افزار نسبت به InstallShield سادهتر و کم حجمتر است. حداقل برای برنامههای عادی امکانات مناسبی دارد.
DeployMaster : یک برنامهی دیگر با امکانات حرفهای جهت انشار برنامههای دسکتاپ، که از ویندوز 98 تا 8.1 را در حال حاضر پشتیبانی میکند.
QSetup Installation Suite : یک برنامهی نصب حرفهای که فایل نهایی آن میتواند به دو فرمت exe یا MSI باشد و قابلیتهایی چون پشتیبانی از زبان فارسی، ورود لایسنس، سریال نرم افزار و ... را نیز پشتیبانی میکند.
Inno Setup : این برنامه هم امکانات خوبی را برای ساخت یک نصاب ساز دارد و همچنین از زبان پاسکال جهت اسکریپت نویسی جهت توسعه امکانات بهره میبرد.
Visual Patch : وب سایت پی سی دانلود این برنامه را اینگونه توضیح میدهد:
انتشار توسط ویژوال استودیو
ویژوال استودیو هم امکانات خوبی برای انتشار در بخش Properties پروژه، برگهی publish ارائه میکند و فایل MSI نتیجه را به سمت وب سرور، FTP Server یا روی دیسک ارسال میکند. یکی از خصوصیات خوب این روش این است که میتواند پیش نیازهایی مانند فریم ورک دات نت یا sql server Express را به سیستم اضافه کنید؛ در نهایت با مزیت آپدیت و نصب تک کلیکی، کاربر، برنامه را بر روی سیستم نصب کند.
اسمبلیهای انتشاریافته اختصاصی
در روشهایی که ذکر کردیم، از آنجا که اسمبلیها در همان شاخه یا دایرکتوری برنامه قرار گرفتهاند و نمیتوان آنها را با برنامههای دیگر به اشتراک گذاشت (مگر اینکه برنامه دیگری را هم در همان دایرکتوری قرار داد) به این روش Privately Deployed Assemblies میگویند. این روش برگ برنده بزرگی برای برنامه نویسان، کاربران و مدیران سیستمها محسوب میشود. زیرا که جابجایی آنها راحت بوده و CLR در همانجا اسمبلیها را در حافظه بار کرده و اجرا میکند. در این نوع برنامهها عملیات نصب/جابجایی/ حذف به راحتی صورتی میگرد و نیازی به تنظیمات خارجی مانند رجیستری ندارد. یکی از خصوصیات مهمی که دارد این هست که جداول متادیتا به اسمبلی اشاره میکنند که برنامه بر پایه آن ساخته شده و با آن تست شده است؛ نه با اسمبلی موجود دیگر در سیستم که شاید نام نوع مورد استفاده آن یا اسمبلی آن به طور تصادفی با آن یکی است.
در مقالات آتی در مورد اشتراک گذاری اسمبلیها بین چند برنامه مفصلتر صحبت خواهیم کرد.
نصب برنامه از طریق فروشگاه ویندوز
در فروشگاه ویندوز Windows Store Apps قوانین سخت و شدیدی برای بسته بندی کردن اسمبلیها وجود دارد. ویژوال استودیو تمام اسمبلیهای مورد نیاز برنامه را در یک فایل با پسوند appx قرار داده و آن را به سمت فروشگاه آپلود میکند. هر کاربری که این فایل appx را نصب کند، همهی اسمبلیهایی را که در دایرکتوری مربوطه قرار گرفته است، توسط CLR بار شده و آیکن برنامه هم در صفحهی start ویندوز قرار میگیرد و اگر دیگر کاربران همان سیستم هم این فایل appx را نصب کنند، از آنجا که قبلا روی سیستم موجود هست، تنها آیکن برنامه به صفحهی start اضافه میگردد و برای حذف هم تنها آیکن برنامه از روی این صفحه حذف میشود؛ مگر اینکه تنها کاربری باشد که این برنامه را نصب کردهاست که در آن صورت کلا همهی اسمبلیهای آن از روی سیستم حذف میشود.
در صورتیکه کاربرهای مختلف نسخههای مختلفی از همان برنامه را روی سیستم نصب کنند، برای اسمبلیها هر کدام یک دایرکتوری ایجاد شده و به ازای نسخهی نصب شده آن کاربر، یکی از این دایرکتوریها مورد استفاده قرار میگیرند. کاربران مختلف میتوانند روی سیستم به طور همزمان از نسخههای مختلف برنامه استفاده کنند.
روشهای پکیج گذاری
برای برنامههای دسکتاپ که ربطی به فروشگاه ندارند و بین ایرانیان طرفدار زیادی دارد، نیازی به استفاده از هیچ روش خاصی نیست و یک کپی معمولی هم کفایت میکند. همهی فایلهای مثل اسمبلی، باید در یک دایرکتوری قرار گرفته و به روش کپی کردن آن را انتقال داد. یا برای بسته بندی از یک فایل batch کمک گرفت و آن را روی سیستم نصب کرد و نیازی به هیچ تغییری در رجیستری نیست. برای حذف برنامه هم، حذف معمولی دایرکتوری مربوطه کفایت میکند.
البته گزینههای دیگری هم برای پکیج کردن این نوع برنامهها وجود دارند:
یکی از روشهای پکیج کردن فایلها به صورت cab هست که عموما برای سناریوهای اینترنتی و فشرده سازی و کاهش زمان دانلود به کار میرود.
روش دوم استفاده از پکیج MSI است که توسط سرویس نصب مایکروسافت Microsoft Installer Service یا MSIExec.exe انجام میگیرد. فایلهای MSI به اسمبلیها اجازه میدهند که بر اساس زمان تقاضای CLR برای بارگیری اولیه نصب شوند. البته این ویژگی جدیدی نیست و برای فایلهای exe یا dll مدیریت نشده هم به کار میرود.
استفاده از نصاب سازها
بهتر هست که برای انتشار برنامه از برنامههای نصاب سازی استفاده کنید که با واسطی جذابتر به نصب پرداخته و امکاناتی از قبیل shotrcutها، حذف و بازیابی و نصب و .. را هم به کاربر میدهند.
نصاب سازهای متفاوتی وجود دارند که در زیر به تعدادی از آنها اشاره میکنیم:
Install Shield (+ ) : این برنامه نسخههای متفاوتی را با قیمتهای متفاوتی، عرضه میکند و در این زمینه، جزء بهترینها نام برده میشود. حتی ویژگیهای مخصوصی هم برای ویژوال استودیو دارد. شرکت سازنده، برنامهی دیگری را هم اخیر تحت نام Install Anywhere عرضه کرده است که اجازه میدهد از روی یک برنامه برای پلتفرمهای مختلف setup بسازد.
NSIS : این برنامه هم در زمینهی ساخت setup محبوبیت زیادی دارد. این برنامه به صورت متن باز منتشر شده و رایگان است. امکانات این برنامه ساده است و برای راه اندازی سریع یک setup و اجرای راحت آن توسط کاربر، کاملا کاربردی است.
Tarma Installmate : این نرم افزار نسبت به InstallShield سادهتر و کم حجمتر است. حداقل برای برنامههای عادی امکانات مناسبی دارد.
DeployMaster : یک برنامهی دیگر با امکانات حرفهای جهت انشار برنامههای دسکتاپ، که از ویندوز 98 تا 8.1 را در حال حاضر پشتیبانی میکند.
QSetup Installation Suite : یک برنامهی نصب حرفهای که فایل نهایی آن میتواند به دو فرمت exe یا MSI باشد و قابلیتهایی چون پشتیبانی از زبان فارسی، ورود لایسنس، سریال نرم افزار و ... را نیز پشتیبانی میکند.
Inno Setup : این برنامه هم امکانات خوبی را برای ساخت یک نصاب ساز دارد و همچنین از زبان پاسکال جهت اسکریپت نویسی جهت توسعه امکانات بهره میبرد.
Visual Patch : وب سایت پی سی دانلود این برنامه را اینگونه توضیح میدهد:
نرم افزار Visual Patch یک ابزار توسعه یافتهی نرم افزاری برای ساخت پچ و آپدیت برنامهها میباشد. این سازنده پچ باینری، استفاده از فشرده سازی داده DeltaMAX برای سریعتر کردن توسعهی نرم افزار، یکپارچگی با نصب نرم افزار و ابزارهای مدیریت پچ از فروشندگانی نظیر Installshield, Lumension, Patchlink, Shavlik, Indigo Rose و ...، را به طور برجسته نمایان ساخته است.و ...
با استفاده از این ابزار پچ کردن برنامهها که برای توسعه دهندگان نرم افزار و برنامه نویسان طراحی شده است، توزیع نرم افزار و سیستم گسترش پچ بهبود مییابد. Visual Patch الگوریتمهای فشرده سازی و state-of- the-art binary differencing را نمایان میسازد و این کمک میکند که شما به کوچکتر شدن و بهتر شدن پچهای نرم افزار اطمینان داشته باشید.
انتشار توسط ویژوال استودیو
ویژوال استودیو هم امکانات خوبی برای انتشار در بخش Properties پروژه، برگهی publish ارائه میکند و فایل MSI نتیجه را به سمت وب سرور، FTP Server یا روی دیسک ارسال میکند. یکی از خصوصیات خوب این روش این است که میتواند پیش نیازهایی مانند فریم ورک دات نت یا sql server Express را به سیستم اضافه کنید؛ در نهایت با مزیت آپدیت و نصب تک کلیکی، کاربر، برنامه را بر روی سیستم نصب کند.
اسمبلیهای انتشاریافته اختصاصی
در روشهایی که ذکر کردیم، از آنجا که اسمبلیها در همان شاخه یا دایرکتوری برنامه قرار گرفتهاند و نمیتوان آنها را با برنامههای دیگر به اشتراک گذاشت (مگر اینکه برنامه دیگری را هم در همان دایرکتوری قرار داد) به این روش Privately Deployed Assemblies میگویند. این روش برگ برنده بزرگی برای برنامه نویسان، کاربران و مدیران سیستمها محسوب میشود. زیرا که جابجایی آنها راحت بوده و CLR در همانجا اسمبلیها را در حافظه بار کرده و اجرا میکند. در این نوع برنامهها عملیات نصب/جابجایی/ حذف به راحتی صورتی میگرد و نیازی به تنظیمات خارجی مانند رجیستری ندارد. یکی از خصوصیات مهمی که دارد این هست که جداول متادیتا به اسمبلی اشاره میکنند که برنامه بر پایه آن ساخته شده و با آن تست شده است؛ نه با اسمبلی موجود دیگر در سیستم که شاید نام نوع مورد استفاده آن یا اسمبلی آن به طور تصادفی با آن یکی است.
در مقالات آتی در مورد اشتراک گذاری اسمبلیها بین چند برنامه مفصلتر صحبت خواهیم کرد.
- فعال شدن آنالایزرها را بر اساس debug و یا release تنظیم کنید. وجود آنها build را کند میکنند.
- ارتقاء سخت افزاری را هم فراموش نکنید. اگر 3 دقیقه طول میکشد، یعنی نیاز به یک سیستم بهتر هست و ... در سایت دیوار موارد دست دوم خوبی را با 4 میلیون تومان میتوانید پیدا کنید که نمونهی نو آنها شاید بالای 20 میلیون تومن قیمت داشته باشند.
تواناییهای Reflection.Emit صرفا به ایجاد متدهایی کاملا جدید و پویا در زمان اجرا محدود نمیشود. برای نمونه کلاس ذیل را درنظر بگیرید:
در ادامه قصد داریم معادل این کلاس را به همراه وهلهای از آن، به صورتی کاملا پویا در زمان اجرا ایجاد کرده (تصور کنید این کلاس در برنامه وجود خارجی نداشته و تنها جهت درک بهتر کدهای IL ادامه بحث، معرفی گردیده است) و سپس مقداری را به سازنده آن ارسال کنیم.
کدهای کامل و توضیحات این typeBuilder را در ادامه ملاحظه میکنید:
در اینجا ایجاد یک کلاس جدید با ایجاد یک TypeBuilder واقع در فضای نام System.Reflection.Emit آغاز میشود. پیش از آن نیاز است یک اسمبلی پویا و ماژولی در آنرا برای قرار دادن کدهای پویای این TypeBuilder ایجاد کنیم. توضیحات مرتبط با دستورات مختلف را به صورت کامنت در کدهای فوق ملاحظه میکنید. با استفاده از TypeBuilder و متد DefineField آن میتوان یک فیلد در سطح کلاس ایجاد کرد و یا توسط متد DefineConstructor آن، سازنده کلاس را با امضایی ویژه تعریف نمود و سپس با دسترسی به ILGenerator آن، بدنه این سازنده را همانند متدهای پویا ایجاد کرد.
اگر به کدهای فوق دقت کرده باشید، متد get_Name به خاصیت Name انتساب داده شده است. علت را در قسمت معرفی اجمالی Reflection زمانیکه لیست متدهای کلاس Person را نمایش دادیم، ملاحظه کردهاید. تمام خواص Auto implemented در دات نت، هر چند ظاهر سادهای دارند اما در عمل به دو متد get_Name و set_Name در کدهای IL توسط کامپایلر تبدیل میشوند. به همین جهت در اینجا نیاز بود تا get_Name را نیز تعریف کنیم.
چند مثال تکمیلی
Populating a PropertyGrid using Reflection.Emit
Dynamically adding RaisePropertyChanged to MVVM Light ViewModels using Reflection.Emit
public class Person { private string _name; public string Name { get { return _name; } } public Person(string name) { _name = name; } }
کدهای کامل و توضیحات این typeBuilder را در ادامه ملاحظه میکنید:
using System; using System.Reflection; using System.Reflection.Emit; namespace FastReflectionTests { class Program { static void Main(string[] args) { //اسمبلی محل قرارگیری کدهای پویای نهایی در اینجا تعیین میشود //حالت دسترسی به آن اجرایی درنظر گرفته شده، امکان تعیین حالتهای دیگری مانند ذخیره سازی نیز وجود دارد var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly( name: new AssemblyName("Demo"), access: AssemblyBuilderAccess.Run); // اکنون داخل این اسمبلی یک ماژول جدید را برای قرار دادن کلاس جدید خود تعریف میکنیم var moduleBuilder = assemblyBuilder.DefineDynamicModule(name: "PersonModule"); // کار ساخت نوع و کلاس جدید شخص عمومی از اینجا شروع میشود var typeBuilder = moduleBuilder.DefineType(name: "Person", attr: TypeAttributes.Public); // افزودن فیلد خصوصی نام تعریف شده در سطح کلاس شخص var nameField = typeBuilder.DefineField(fieldName: "_name", type: typeof(string), attributes: FieldAttributes.Private); // تعریف سازنده عمومی کلاس شخص که دارای یک آرگومان رشتهای است var ctor = typeBuilder.DefineConstructor( attributes: MethodAttributes.Public, callingConvention: CallingConventions.Standard, parameterTypes: new[] { typeof(string) }); // تعریف بدنه سازنده کلاس شخص // در اینجا فیلد خصوصی تعریف شده در سطح کلاس باید مقدار دهی شود var ctorIL = ctor.GetILGenerator(); // نکتهای در مورد سازندهها ctorIL.Emit(OpCodes.Ldarg_0); // اندیس صفر در سازنده کلاس به وهلهای از کلاس جاری اشاره میکند ctorIL.Emit(OpCodes.Ldarg_1); // بارگذاری آرگومان سازنده و قرار دادن آن روی پشته // مقدار دهی فیلد خصوصی نام که به وهلهای از کلاس جاری و مقدار آرگومان دریافتی نیاز دارد ctorIL.Emit(OpCodes.Stfld, nameField); ctorIL.Emit(OpCodes.Ret); // پایان کار سازنده // تعریف خاصیت رشتهای نام در کلاس شخص var nameProperty = typeBuilder.DefineProperty( name: "Name", attributes: PropertyAttributes.HasDefault, returnType: typeof(string), parameterTypes: null); // خاصیت پارامتر ورودی ندارد var namePropertyGetMethod = typeBuilder.DefineMethod( name: "get_Name", attributes: MethodAttributes.Public | //متد ویژهای است که توسط کامپایلر پردازش و تشخیص داده میشود MethodAttributes.SpecialName | MethodAttributes.HideBySig, returnType: typeof(string), parameterTypes: Type.EmptyTypes); // اتصال گت متد به خاصیت رشتهای نام که پیشتر تعریف شد nameProperty.SetGetMethod(namePropertyGetMethod); // بدنه گت متد در اینجا تعریف خواهد شد var namePropertyGetMethodIL = namePropertyGetMethod.GetILGenerator(); namePropertyGetMethodIL.Emit(OpCodes.Ldarg_0); // بارگذاری اشارهگری به وهلهای از کلاس جاری در پشته namePropertyGetMethodIL.Emit(OpCodes.Ldfld, nameField); // بارگذاری فیلد نام namePropertyGetMethodIL.Emit(OpCodes.Ret); var t = typeBuilder.CreateType(); // نهایی سازی کار ایجاد نوع جدید // ایجاد وهلهای از نوع جدید که پارامتری رشتهای به سازنده آن ارسال میشود var instance = Activator.CreateInstance(t, "Vahid"); // دسترسی به خاصیت نام var nProperty = t.GetProperty("Name"); // و دریافت مقدار آن برای نمایش var result = nProperty.GetValue(instance, null); Console.WriteLine(result); } } }
اگر به کدهای فوق دقت کرده باشید، متد get_Name به خاصیت Name انتساب داده شده است. علت را در قسمت معرفی اجمالی Reflection زمانیکه لیست متدهای کلاس Person را نمایش دادیم، ملاحظه کردهاید. تمام خواص Auto implemented در دات نت، هر چند ظاهر سادهای دارند اما در عمل به دو متد get_Name و set_Name در کدهای IL توسط کامپایلر تبدیل میشوند. به همین جهت در اینجا نیاز بود تا get_Name را نیز تعریف کنیم.
چند مثال تکمیلی
Populating a PropertyGrid using Reflection.Emit
Dynamically adding RaisePropertyChanged to MVVM Light ViewModels using Reflection.Emit
اگر در WPF سعی کنیم آیتمی را به مجموعه اعضای یک Collection مانند یک List یا ObservableCollection از طریق تردی دیگر اضافه کنیم، با خطای ذیل متوقف خواهیم شد:
راه حلی که برای آن تا دات نت 4 در اکثر سایتها توصیه میشد به نحو ذیل است:
Adding to an ObservableCollection from a background thread
مشکل!
اگر همین برنامه را که برای دات نت 4 کامپایل شدهاست، بر روی سیستمی که دات نت 4.5 بر روی آن نصب است اجرا کنیم، برنامه با خطای ذیل متوقف میشود:
مشکل از کجاست؟
در دات نت 4 و نیم، دیگر نیازی به استفاده از کلاس MTObservableCollection یاد شده نیست و به صورت توکار امکان کار با Collectionها از طریق تردی دیگر میسر است. فقط برای فعال سازی آن باید نوشت:
پس از اینکه برای نمونه، مجموعهی فرضی persons وهله سازی شد، تنها کافی است متد جدید EnableCollectionSynchronization بر روی آن فراخوانی شود.
برای برنامهی دات نت 4 ایی که قرار است در سیستمهای مختلف اجرا شود چطور؟
در اینجا باید از Reflection کمک گرفت. اگر متد EnableCollectionSynchronization بر روی کلاس BindingOperations یافت شد، یعنی برنامهی دات نت 4، در محیط جدید در حال اجرا است:
در این حالت فقط کافی است این متد جدید یافت شده را بر روی Collection مدنظر فراخوانی کنیم.
همچنین اگر بخواهیم کلاس MTObservableCollection معرفی شده را جهت سازگاری با دات نت 4 و نیم به روز کنیم، به کلاس ذیل خواهیم رسید. این کلاس با دات نت 4 و 4.5 سازگار است و جهت کار با ObservableCollectionها از طریق تردهای مختلف تهیه شدهاست:
در این کلاس، در سازندهی آن متد عمومی enableCollectionSynchronization فراخوانی میشود. اگر برنامه در محیط دات نت 4 فراخوانی شود، تاثیری نخواهد داشت چون method در حال بررسی نال است. در غیراینصورت، برنامه در حالت سازگار با دات نت 4.5 اجرا خواهد شد.
This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread
Adding to an ObservableCollection from a background thread
مشکل!
اگر همین برنامه را که برای دات نت 4 کامپایل شدهاست، بر روی سیستمی که دات نت 4.5 بر روی آن نصب است اجرا کنیم، برنامه با خطای ذیل متوقف میشود:
System.InvalidOperationException: This exception was thrown because the generator for control 'System.Windows.Controls.ListView Items.Count:62' with name '(unnamed)' has received sequence of CollectionChanged events that do not agree with the current state of the Items collection. The following differences were detected: Accumulated count 61 is different from actual count 62.
مشکل از کجاست؟
در دات نت 4 و نیم، دیگر نیازی به استفاده از کلاس MTObservableCollection یاد شده نیست و به صورت توکار امکان کار با Collectionها از طریق تردی دیگر میسر است. فقط برای فعال سازی آن باید نوشت:
private static object _lock = new object(); //... BindingOperations.EnableCollectionSynchronization(persons, _lock);
برای برنامهی دات نت 4 ایی که قرار است در سیستمهای مختلف اجرا شود چطور؟
در اینجا باید از Reflection کمک گرفت. اگر متد EnableCollectionSynchronization بر روی کلاس BindingOperations یافت شد، یعنی برنامهی دات نت 4، در محیط جدید در حال اجرا است:
public static void EnableCollectionSynchronization(IEnumerable collection, object lockObject) { MethodInfo method = typeof(BindingOperations).GetMethod("EnableCollectionSynchronization", new Type[] { typeof(IEnumerable), typeof(object) }); if (method != null) { method.Invoke(null, new object[] { collection, lockObject }); } }
همچنین اگر بخواهیم کلاس MTObservableCollection معرفی شده را جهت سازگاری با دات نت 4 و نیم به روز کنیم، به کلاس ذیل خواهیم رسید. این کلاس با دات نت 4 و 4.5 سازگار است و جهت کار با ObservableCollectionها از طریق تردهای مختلف تهیه شدهاست:
using System; using System.Collections; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; using System.Windows.Data; using System.Windows.Threading; namespace WpfAsyncCollection { public class AsyncObservableCollection<T> : ObservableCollection<T> { public override event NotifyCollectionChangedEventHandler CollectionChanged; private static object _syncLock = new object(); public AsyncObservableCollection() { enableCollectionSynchronization(this, _syncLock); } protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { using (BlockReentrancy()) { var eh = CollectionChanged; if (eh == null) return; var dispatcher = (from NotifyCollectionChangedEventHandler nh in eh.GetInvocationList() let dpo = nh.Target as DispatcherObject where dpo != null select dpo.Dispatcher).FirstOrDefault(); if (dispatcher != null && dispatcher.CheckAccess() == false) { dispatcher.Invoke(DispatcherPriority.DataBind, (Action)(() => OnCollectionChanged(e))); } else { foreach (NotifyCollectionChangedEventHandler nh in eh.GetInvocationList()) nh.Invoke(this, e); } } } private static void enableCollectionSynchronization(IEnumerable collection, object lockObject) { var method = typeof(BindingOperations).GetMethod("EnableCollectionSynchronization", new Type[] { typeof(IEnumerable), typeof(object) }); if (method != null) { // It's .NET 4.5 method.Invoke(null, new object[] { collection, lockObject }); } } } }
نظرات اشتراکها
مقایسه کارآیی ORMهای مطرح دات نت
ADO.NET شبیه به زبان اسمبلی دسترسی به اطلاعات در دات نت هست. این پایینترین سطحی است که برای کار با بانکهای اطلاعاتی در دنیای دات نت وجود دارد. تمام ORMهای موجود لایهای هستند بر روی این سطح.