اشتراکها
املای Asp.net درست است یا ASP.Net؟
اشتراکها
نکاتی در مورد کلاس CultureInfo
اشتراکها
مدیریت حافظه در دات نت
هر چند که #C به عنوان یک زبان ساده برای درک و یادگیری شناخته میشود، گاهی رفتاری غیرمنتظره را حتی برای توسعه دهندههای با تجربه خواهد داشت. در این نوشته مروری بر بعضی از این رفتارها و توضیح دلایل پشت آن خواهیم کرد.
Value
اگر مقدار null مدیریت نشود، میتواند باعث ایجاد نتایج نامطلوب، یا باعث از کار افتادن برنامه شود. شئ null به خودی خود مخرب نیست؛ اما اگر بخواهیم به یکی از متدها یا خاصیتهای آن دسترسی داشته باشیم، با استثنای معروف NullReferenceException روبرو میشویم. برای در امان ماندن، باید همیشه اطمینان داشته باشیم که پیش از استفاده از امکانات شئ، ارجاع آن null نباشد. در قطعه کد زیر برخی از رفتارهای null value آورده شده:
// Behavior 1 object obj = null; bool objValueEqual = obj.Equals(null); // Behavior 2 object obj = null; Type objType = obj.GetType(); // Behavior 3 string str = (string)null; bool strType = str is string; // Behavior 4 int num = 5; Nullable<int> nullableNum = 5; bool typeEqual = num.GetType() == nullableNum.GetType(); // Behavior 5 Type inType = typeof(int); Type nullableIntType = typeof(Nullable<int>); bool typeEqual = inType == nullableIntType;
- در رفتار اول هرچند که متد Equals از شی null در دسترس است و با مقدار null مقایسه شده اما در زمان اجرا پیغام خطای NullReferenceException را خواهیم داشت.
- در رفتار دوم هم پیغام خطا را خواهیم داشت. شئ با مقدار null، در زمان اجرا هیچ نوعی را برنمیگرداند.
- در رفتار سوم هر چند که مقدار null صریحا به رشته تبدیل شده و برای چاپ متغیر str پیام خطایی را نخواهیم داشت، اما متغیر strType در خروجی، false خواهد بود. همانطور که در رفتار دوم گفته شد، شیء با مقدار null هیچ نوعی را برنمیگرداند.
- خروجی رفتار چهارم true خواهد بود. به این صورت که هر دو از نوع System.int32 خواهند بود.
- در رفتار پنجم اگر از نوعها، خروجی جداگانه بگیریم، خواهیم دیدکه نوع int از System.int32 و <Nullable<int از نوع System.Nullable`1[System.Int32] میباشند، در نتیجه خروجی false است. اشیای nullable بعد از اینکه مقداری مشخص را دریافت کردند، به صورت یک شیء غیر nullable رفتار خواهند کرد.
مدیریت مقادیر null در سربارگذاری متدها
static void Main(string[] args) { Console.WriteLine(Method(null)); Console.ReadLine(); } private static string Method(object obj) { return "Object parameter"; } private static string Method(string str) { return "String parameter"; }
رفتارهای ()Math.Round
var rounded = Math.Round(1.5); // 2 var rounded = Math.Round(2.5); // 2 var rounded = Math.Round(2.5, MidpointRounding.ToEven); // 2 var rounded = Math.Round(2.5, MidpointRounding.AwayFromZero); // 3 var value = 1.4f; var rounded = Math.Round(value + 0.1f); // 1
متد Round از کلاس Math، ورودی را که عددی اعشاری است، گرد میکند. اگر مقدار اعشار کمتر از ۰.۵ باشد، به سمت پایین و اگر بیشتر از ۰.۵ باشد، به سمت بالا گرد میشود. اما اگر ورودی دقیقا مقدار اعشاری ۰.۵ را داشته باشد چطور؟ متد Round به صورت پیشفرض ورودی را به نزدیکترین عدد زوج گرد میکند، به این دلیل خطهای ۱ و ۲ از قطعه کد بالا، خروجی یکسان ۲ را خواهند داشت. این متد آرگومان دومی هم دارد که دو حالت MidpointRounding.ToEven و MidpointRounding.AwayFromZero را میتوان برای آن مشخص کرد. ToEven همان رفتار پیشفرض متد است که ورودی را به نزدیکترین عدد زوج گرد میکند و از حالت AwayFromZero میشود برای گرد کردن ورودی به عدد بزرگتر استفاده کرد (خط ۵).
در خط ۸ یک حالت خاص دیگر نیز داریم. انتظار میرود که خروجی، به نزدیکترین عدد زوج گرد شود و نتیجه ۲ باشد؛ مثل خط ۱، اما خروجی ۱ خواهد بود. وقتی ورودیها را از نوع float در نظر بگیریم، مقدار 0.1f کمی کمتر از ۰.۱ خواهد بود و نتیجه محاسبه کمی کمتر از ۱.۵. برای پرهیز از این مسئله بهتر است ورودی متد Round را از نوع decimal در نظر بگیریم.
مقدار دهی اولیه کلاسها
پیشنهاد میشود برای جلوگیری از وقوع استثناءها از مقدار دهی اولیه کلاسها در سازنده کلاس، بخصوص اگر سازنده استاتیک داشته باشیم، پرهیز کنیم. ترتیب مقدار دهی اولیه زمانیکه از یک کلاس یه وهله ساخته میشود، به قرار زیر است:
- فیلدهای استاتیک (زمانیکه کلاس برای اولین بار در دسترس قرار میگیرد)
- سازنده استاتیک (زمانیکه کلاس برای اولین بار در دسترس قرار میگیرد)
- فیلدهایی از کلاس که در نمونه ساخته شده در دسترس قرار میگیرند.
- سازنده کلاس که در زمان ایجاد یک نمونه از کلاس در دسترس قرار میگیرد.
در قطعه کد زیر اگر نمونهای از کلاس FailingClass ساخته شود، انتظار میرود که خطای InvalidOperationException صادر شود؛ اما برنامه با خطای TypeInitializationException متوقف میشود. در واقع در زمان اجرا به صورت خودکار خطای TypeInitializationException، خطای InvalidOperationException را پوشش میدهد. اگر بجای InvalidOperationException یک دستور ساده WriteLine داشته باشیم، سازنده کلاس FailingClass مجال کامل شدن را خواهد داشت. اما با خطایی که داخل سازنده صادر کردهایم، سازنده کلاس بدون اینکه به طور کامل به پایان برسد، متوقف خواهد شد.
public static class Config { public static bool ThrowException { get; set; } = true; } public class FailingClass { static FailingClass() { if (Config.ThrowException) { throw new InvalidOperationException(); } } }
try { var failedInstance = new FailingClass(); } catch (TypeInitializationException) { } Config.ThrowException = false; var instance = new FailingClass();
اگر قطعه کد بالا را بدون بخش try اجرا کنیم، برنامه ابتدا صدور خطا را false میکند و بدون مشکل از کلاس نمونهای ساخته میشود. اما اگر بخش try را داشته باشیم، هر چند که خطا در بخش try گرفته میشود و تنظیم صدور خطا false است، باز هم در خط آخر و در زمان ایجاد یک نمونه از کلاس، پیام خطای TypeInitializationException خواهیم داشت. علت آن است که سازنده استاتیک کلاس فقط یک بار فراخوانی میشود و اگر در این فراخوانی خطایی رخ دهد، این خطا در اثر ایجاد سایر نمونهها و یا استفاده مستقیم از کلاس، مجددا صادر خواهد شد. در نتیجه این کلاس تا زمانیکه پردازش آن در جریان است، غیرقابل استفاده خواهد بود. یک مثال دیگر از ترتیب فراخوانیها را بررسی میکنیم.
public class BaseClass { { public BaseClass() { VirtualMethod(1); } public virtual int VirtualMethod(int dividend) { return dividend / 1; } } public class DerivedClass : BaseClass { int divisor; public DerivedClass() { divisor = 1; } public override int VirtualMethod(int dividend) { return base.VirtualMethod(dividend / divisor); } }
در قطعه کد بالا هر چند که همه چیز درست به نظر میرسد، اما اگر از کلاس DerivedClass نمونهای ساخته شود، با پیام خطای DivideByZeroException مواجه میشویم. علت این مشکل ترتیب مقدار دهی اولیه در کلاسهای فرزند است. ابتدا فیلدهای کلاس فرزند مقدار دهی میشوند و بعد فیلدهای کلاس پایه، بعد سازنده کلاس پایه فراخوانی میشود و پس از آن سازنده کلاس فرزند. ترتیب فراخوانیها به همین جا محدود نمیشود.
در مثال بالا متد VirtualMethod که در سازنده کلاس پایه فراخوانی شده، پیش از این که کد داخل خود را اجرا کند، متد VirtualMethod را در کلاس فرزند، فراخوانی میکند و کلاس فرزند مجالی را برای مقدار دهی متغیر divisor، در سازنده خود نخواهد داشت. در نتیجه مقدار این متغیر در متد VirtualMethod صفر خواهد ماند و باعث صدور استثناء میشود. برای پرهیز از چنین مشکلاتی بهتر است فیلدهای یک کلاس به صورت مستقیم مقدار دهی اولیه بشوند. مقدار دهی اولیه و یا فراخوانی متدهای virtual در سازنده کلاسها میتواند باعث بروز رفتارهای پیش بینی نشدهای شوند.
چند ریختی
چند ریختی قابلیتی است برای کلاسهای متفاوت تا بتوانند یک اینترفیس مشابه را به صورتهای مختلفی پیادهسازی کنند. اما قطعه کد زیر قاعده چند ریختی را نقض میکند.
class Program { static void Main(string[] args) { var instance = new DerivedClass(); var result = instance.Method(); result = ((BaseClass)instance).Method(); Console.WriteLine(instance + " -> " + result); // Derived Class ... -> Method in BaseClass Console.ReadLine(); } } public class BaseClass { public virtual string Method() { return "Method in BaseClass"; } } public class DerivedClass : BaseClass { public override string ToString() { return "Derived Class ... "; } public new string Method() { return "Method in DerivedClass"; } }
class Program { static void Main(string[] args) { var instance = new DerivedClass(); var result = instance.Method(); // -> Method in DerivedClass result = ((IInterface)instance).Method(); // -> Method belonging to IInterface Console.WriteLine(result); Console.ReadLine(); } } public interface IInterface { string Method(); } public class DerivedClass : IInterface { public string Method() { return "Method in DerivedClass"; } string IInterface.Method() { return "Method belonging to IInterface"; } }
Iterators
Iteratorها (تکرار شوندهها) ساختارهایی هستند که برای حرکت در عناصر یک collection استفاده میشوند. عموما از دستور foreach استفاده و نوع جنریک <IEnumerable<T را نمایندگی میکنند. هر چند که استفاده از آنها ساده است، اما اگر کارکرد داخلی iteratorها را درک نکنیم ممکن است به دام استفاده نادرست از آنها گرفتار شویم. در قطعه کد زیر کلاس Test صدا زده میشود و مقادیر یک تا پنج به صورت یک IEnumerable از داخل بلوک using بازگشت داده میشود.
private IEnumerable<int> GetEnumerable(StringBuilder log) { using (var test = new Test(log)) { return Enumerable.Range(1, 5); } }
فرض کنیم کلاس Test اینترفیس IDisposable را پیادهسازی کرده و در سازنده و متد Dispose خود پیامهایی را به log اضافه کند. در مثالهای واقعی، کلاس Testمیتواند اتصالی به پایگاه داده باشد و رکوردهای خوانده شده، بازگشت داده شوند. توسط حلقه زیر مقدار خروجی تابع را چاپ میکنیم.
var log = new StringBuilder(); foreach (var number in GetEnumerable(log)) { log.AppendLine($"{number}"); }
Created Disposed 1 2 3 4 5
using (var test = new Test(log)) { foreach (var i in Enumerable.Range(1,5)) { yield return i; } }
Created 1 2 3 4 5 Disposed
آیا تا به حال مجبور به نوشتن کدی شبیه قطعه کد زیر شده اید؟
var store = GetStore(); string postCode = null; if (store != null && store.Address != null && store.Address.PostCode != null) postCode = store.Address.PostCode.ToString();
بله! من مطمئن هستم برای شما هم پیش آمده است.
هدف بازیابی و یا محاسبه یک مقدار است، اما برای انجام این کار میبایست به چندین شیء میانی دسترسی پیدا کنیم که البته ممکن است در حالت پیش فرض خود قرار داشته باشند و حاوی هیچ مقداری نباشند. بنابراین برای جلوگیری از وقوع NullException ، مجبوریم تمامی اشیائی که در مسیر قرار دارند را بررسی کنیم که null نباشند. مثال بالا کاملا گویا ست. گاهی اوقات حتی ممکن است فراخوانی یک متد، تبدیل نوع با استفاده از as و یا دسترسی به عناصر یک مجموعه وجود داشته باشد. متاسفانه مدیریت تمامی این حالات باعث حجیم شدن کدها و در نتیجه کاهش خوانایی آنها میشود. بنابراین باید به دنبال یک راه حل مناسب بود.
استفاده از یک متد الحاقی شرطی (Conditional extensions)
از نظر بسیاری از برنامه نویسها راه حل، استفاده از یک متد الحاقی شرطی است. اگر عبارت "c# deep null check" را گوگل کنید، پیاده سازیهای متنوعی را پیدا خواهید کرد. اگر چه این متدها نامهای متفاوتی دارند اما همه آنها از یک ایده کلی مشترک استفاده میکنند:
public static TResult IfNotNull<TResult, TSource>( this TSource source, Func<TSource, TResult> onNotDefault) where TSource : class { if (onNotDefault == null) throw new ArgumentNullException("onNotDefault"); return source == null ? default(TResult) : onNotDefault(source); }
همانطور که میبینید این متد الحاقی مقداری از نوع TResult را بر میگرداند. اگر source که در اینجا با توجه به الحاقی بودن متد به معنای شی جاری است، null باشد مقدار پیش فرض نوع خروجی(TResult) بازگردانده میشود و در غیر این صورت دیلیگیت onNotDefault فراخوانی میگردد.
بعد از افزودن متد الحاقی IfNotNull به پروژه میتوانیم مثال ابتدای مطلب را به صورت زیر بنویسیم :
var postCode = GetStore() .IfNotNull(x => x.Address) .IfNotNull(x => x.PostCode) .IfNotNull(x => x.ToString());
این روش مزایای بسیاری دارد اما در موارد پیچیده دچار مشکل میشویم. برای مثال در نظر بگیرید قصد داریم در طول مسیر، متدی را فراخوانی کنیم و مقدار بازگشتی را در یک متغیر موقتی ذخیره کنیم و بر اساس آن ادامه مسیر را طی کنیم. متاسفانه این کارها هم اکنون امکان پذیر نیست. پس به نظر میرسد باید کمی متد الحاقی IfNotNull را بهبود ببخشیم.
برای بهبود عملکرد متد الحاقی IfNotNull علاوه بر موارد ذکر شده حداقل دو مورد به نظر من میرسد:
- این متد فقط با انواع ارجاعی (reference types) کار میکند و میبایست برای کار با انواع مقداری (value types) اصلاح شود.
- با انواع داده ای مثل string چه باید کرد؟ در مورد این نوع دادهها تنها مطمئن شدن از null نبودن کافی نیست. برای مثال در مورد string ، گاهی اوقات ما میخواهیم از خالی نبودن آن نیز مطمئن شویم. و یا در مورد collectionها تنها null نبودن کافی نیست بلکه زمانی که نیاز به محاسبه مجموع و یا یافتن بزرگترین عضو است، باید از خالی نبودن مجموعه و وجود حداقل یک عضو در آن مطمئن باشیم.
برای حل این مشکلات میتوانیم متد الحاقی IfNotNull را به متد الحاقی IfNotDefault تبدیل کنیم:
public static TResult IfNotDefault<TResult, TSource>( this TSource source, Func<TSource, TResult> onNotDefault, Predicate<TSource> isNotDefault = null) { if (onNotDefault == null) throw new ArgumentNullException("onNotDefault"); var isDefault = isNotDefault == null ? EqualityComparer<TSource>.Default.Equals(source, default(TSource)) : !isNotDefault(source); return isDefault ? default(TResult) : onNotDefault(source); }
تعریف این متد خیلی با تعریف متد قبلی متفاوت نیست. به منظور پشتیبانی از struct ها، قید where TSource : class حذف شده است. بنابراین دیگر نمیتوان از مقایسهی ساده null با استفاده از عملگر == استفاده کرد چراکه structها nullable نیستند. پس مجبوریم از EqualityComparer<TSource>.Default بخواهیم که این کار را انجام دهد. متد الحاقی IfNotDefault همچنین شامل یک predicate اختیاری با نام isNotDefault است. در صورتی که مقایسه پیش فرض کافی نبود میتوان از این predicate استفاده کرد.
در انتها اجازه بدهید چند مثال کاربردی را مرور کنیم:
1- انجام یک سری اعمال بر روی string در صورتی که رشته خالی نباشد:
return person . IfNotDefault(x => x.Name) . IfNotDefault(SomeOperation, x => !string.IsNullOrEmpty(x));
محاسبهی مقدار میانگین. متد الحاقی IfNotDefault به زیبایی در یک زنجیرهی LINQ کار میکند:
var avg = students .Where(IsNotAGraduate) .FirstOrDefault() .IfNotDefault(s => s.Grades) .IfNotDefault(g => g.Average(), g => g != null && g.Length > 0);
برای مطالعه بیشتر
Get rid of deep null checks
Chained null checks and the Maybe monad
Maybe or IfNotNull using lambdas for deep expressions
Dynamically Check Nested Values for IsNull Values
مطالب
jQuery Tips #2
چگونگی استفاده از Cookie در jQuery
در این پست قصد دارم نحوهی کاربا Cookie را با استفاده از jQuery برسی کنم و در پست بعدی یک مثال عملی را برسی میکنیم.
همانطور که میدانید کوکی یکی از اشیاء بسیار مهم برای نگه داری دادهها در بحث وب میباشد که یک فایل متنی است که سمت Client ذخیره میشود. و ما زمانی که از کتابخانه jQuery استفاده میکنیم خیلی مهم است که بدانیم چگونه باید با Cookieها کار کرد.
برای کار با کوکیها در jQuery باید از Plugin های موجود استفاده کرد . برای ایجاد یک Cookie ابتدا فایل jQuery و سپس این کتابخانه را به صفحه مورد نظر اضافه نموده و کد زیر را برای ایجاد یک کوکی مینویسیم
در اینجا یک کوکی با نام TestCookie با مقدار Test Cookie By Mohsen Bahrzadeh ایجاد میکنیم
حال پروژه را اجرا میکنیم. و در تصویر زیر مشاهده میکنید که کوکی ما ایجاد شده است
یکی از آیتمهای بسیار مهم در کوکیها تعریف زمان انقضاء کوکی است برای ست کردن تاریخ از کد زیر استفاده میکنیم
و برای خواندن مقدار کوکی از کد زیر استفاده میکنیم
و برای حذف کوکی از کد زیر استفاده میکنیم
در این پست قصد دارم نحوهی کاربا Cookie را با استفاده از jQuery برسی کنم و در پست بعدی یک مثال عملی را برسی میکنیم.
همانطور که میدانید کوکی یکی از اشیاء بسیار مهم برای نگه داری دادهها در بحث وب میباشد که یک فایل متنی است که سمت Client ذخیره میشود. و ما زمانی که از کتابخانه jQuery استفاده میکنیم خیلی مهم است که بدانیم چگونه باید با Cookieها کار کرد.
برای کار با کوکیها در jQuery باید از Plugin های موجود استفاده کرد . برای ایجاد یک Cookie ابتدا فایل jQuery و سپس این کتابخانه را به صفحه مورد نظر اضافه نموده و کد زیر را برای ایجاد یک کوکی مینویسیم
<script src="jquery-1.7.1.min.js" type="text/javascript"></script> <script src="jquery.cookie.js" type="text/javascript"></script> <script type="text/javascript"> $(function () { $.cookie("TestCookie", "Test Cookie By Mohsen Bahrzadeh "); }); </script>
حال پروژه را اجرا میکنیم. و در تصویر زیر مشاهده میکنید که کوکی ما ایجاد شده است
یکی از آیتمهای بسیار مهم در کوکیها تعریف زمان انقضاء کوکی است برای ست کردن تاریخ از کد زیر استفاده میکنیم
$(function () { $.cookie("TestCookie", "Test Cookie By Mohsen Bahrzadeh ", { expires: 7 }); });
$(function () { alert($.cookie("TestCookie")); });
$(function () { $.cookie("TestCookie", null); });
مطالب
jQuery Tips #1
چگونگی تغییر سایز فونت صفحه با استفاده از jQuery
کد زیر را در نظر بگیرید
و کد HTML زیر را
در این کد ابتدا اندازه فونت را درون متغیر originalFontSize ذخیره و سپس 3 متد تعریف کردهایم، که اولین متد اندازه فونت را به اندازه فونت اولیه بر میگرداند و در متد دوم میخواهیم اندازه فونت تگی با ID=test را افزایش دهیم.
برای این کار ابتدا اندازه جاری تگ را گرفته و درون متغیر currentFontSize قرار داده سپس مقدار متغیر currentFontSize را به Float تبدیل کرده و 5 واحد به آن اضافه کرده ایم و سپس عدد بدست آمده را به عنوان اندازه فونت جدید در نظر گرفته ایم. برای متد سوم دقیقاً همین اتفاق میافتد فقط به جای + از - استفاده شده است
کد زیر را در نظر بگیرید
$(function () { // اندازه واقعی فونت var originalFontSize = $('#test').css('font-size'); $(".resetFont").click(function () { $('#test').css('font-size', originalFontSize); }); // افزایش اندازه فونت $(".increaseFont").click(function () { var currentFontSize = $('#test').css('font-size'); var currentFontSizeNum = parseFloat(currentFontSize); var newFontSize = currentFontSizeNum + 5; $('#test').css('font-size', newFontSize); return false; }); // کاهش اندازه فونت $(".decreaseFont").click(function () { var currentFontSize = $('#test').css('font-size'); var currentFontSizeNum = parseFloat(currentFontSize); var newFontSize = currentFontSizeNum - 5; $('#test').css('font-size', newFontSize); return false; }); });
و کد HTML زیر را
<div id="test"> jQuery Tips By Mohsen Bahrzadeh </div> <a href="#">decreaseFont</a> <a href="#">Increase</a> <a href="#">resetFont</a>
برای این کار ابتدا اندازه جاری تگ را گرفته و درون متغیر currentFontSize قرار داده سپس مقدار متغیر currentFontSize را به Float تبدیل کرده و 5 واحد به آن اضافه کرده ایم و سپس عدد بدست آمده را به عنوان اندازه فونت جدید در نظر گرفته ایم. برای متد سوم دقیقاً همین اتفاق میافتد فقط به جای + از - استفاده شده است
این باگ را در اینجا گزارش کنید (به نظر structuremap.dnx هنوز برای نگارش RTM آماده نیست).
به روز رسانی
این مساله در اینجا گزارش شده و عنوان کردهاند که یک populate اضافی دارد:
به روز رسانی
این مساله در اینجا گزارش شده و عنوان کردهاند که یک populate اضافی دارد:
private IServiceProvider IocConfig(IServiceCollection services) { var container = new Container(); container.Configure(config => { //config.Populate(services); ---> این اضافی است }); container.Populate(services); return container.GetInstance<IServiceProvider>(); }