Finalizers are interesting and dangerous because they are an environment in which everything you know is wrong.
I’ve written a lot about the perils of C# finalizers / destructors
(either name is fine) over the years, but it’s scattered in little bits
over the internet. In this series I’m going to try to get everything in
one place; here are a bunch of things that many people believe about
finalizers, all of which are wrong.
Finalizers are interesting and dangerous because they are an environment in which everything you know is wrong.
I’ve written a lot about the perils of C# finalizers / destructors
(either name is fine) over the years, but it’s scattered in little bits
over the internet. In this series I’m going to try to get everything in
one place; here are a bunch of things that many people believe about
finalizers, all of which are wrong.
در گذشته نه چندان دور، کوکیها نقش اصلی را در مدیریت کاربران ، و ذخیره اطلاعات کاربران ایفا میکردند. ولی بعد از کشف شدن باگ امنیتی ( که ناشی از اشتباه برنامه نویس بود ) در کوکی ها، برای مدتی کنار گذاشته شدند و اکثر اطلاعات کاربران در session های سمت سرور ذخیره میشد.
با کلیک بر روی لینک Write کوکی data با مقدار مشخص پر میشود.
ذخیره اطلاعات زیاد و نه چندان مهم کاربران در session های سمت سرور ، بار زیادی را به سخت افزار تحمیل میکرد. بعد از این، برنامه نویسان به سمتی استفاده متعادل از هرکدام اینها ( کوکی و سشن) رفتند.
اکثر دوستان با مدیریت سمت سرور کوکیها آشنایی دارند ، بنده قصد دارم در اینجا با استفاده از یک پلاگین جی کوئری مدیریت کوکیها را نمایش دهم.
در این برنامه ما از پلاگی jQuery.cookie استفاده میکنیم که شما میتوانید با مراجعه به صفحه این پلاگین اطلاعات کاملی از این پلاگین به دست بیاورید.
کار با این پلاگین بسیار ساده است.
ابتدا فایل پلاگین را به صفحه خودتون اضافه میکنید.
<script src="/path/to/jquery.cookie.js"></script>
حالا خیلی راحت میتوانید با این دستور یک مقدار را در کوکی قرار دهید.
$.cookie('the_cookie', 'the_value');
و برای گرفتن کوکی نوشته شده هم به این صورت عمل میکنید.
$.cookie('the_cookie'); // => "the_value"
همان طور که دیدید کار بسیار ساده ای است. ولی قدرت این پلاگین در option هایی است که در اختیار ما قرار میدهد.
مثلا شما میتوانید انتخاب کنید این کوکی برای چند روز معتبر باشد ، و یا اطلاعات را به صورت json ذخیره و بازیابی کنید، و حتی option های دیگری برای بحث امنیت کوکی شما.
برای درک بهتر از قطعه کدی که کمی پیچیدهتر از مثال منبع است، استفاده میکنیم.
به کد زیر توجه کنید :
JavaScript :
<script type="text/javascript"> $(function () { $('#write').click(function () { $.cookie('data', '{"iri":"Iran","usa":"United States"}', { expires: 365, json: true }); alert('Writed'); }); $('#show').click(function () { var obj = jQuery.parseJSON($.cookie('data')); alert(obj.iri); }); $('#remove').click(function () { $.removeCookie('data'); }); }) </script>
HTML :
<body> <a href="#" id="write">Write</a> <br /> <a href="#" id="show">Show</a> <br /> <a href="#" id="remove">Remove</a> </body>
در اینجا ما سه لینک داریم که هر کدام برای ما عملی را نمایش میدهند.
توضیحات کد :
$('#write').click(function () { $.cookie('data', '{"iri":"Iran","usa":"United States"}', { expires: 365, json: true }); alert('Writed'); });
با کلیک بر روی لینک Write کوکی data با مقدار مشخص پر میشود.
دقت داشته باشید که این مقدار از نوع json انتخاب شده است و در انتها نیز این را مشخص کرده ایم ، همچنین اعلام کرده ایم که این کوکی برای 365 روز معتبر است.
حالا مرورگر خودتان را ببندید و دوباره باز کنید.
این بار بر روی Show کلیک میکنیم :
$('#show').click(function () { var obj = jQuery.parseJSON($.cookie('data')); alert(obj.iri);
با کلیک بر روی لینک Show مقدار از کوکی خوانده میشود و نمایش داده میشود. دقت کنید ، به دلیل اینکه مقدار ذخیره شده ما از نوع json است باید دوباره این مقدار را pars کنیم تا به مقادیر property آن دسترسی داشته باشیم.
همچنین شما میتوانید خیلی راحت کوکی ساخته شده را از بین ببرید :
$('#remove').click(function () { $.removeCookie('data'); });
و یا این که کوکی را برابر null قرار دهید.
نکته ای که باید رعایت کنید و در این مثال هم نیامده است ، این است که ، هنگامی که شما میخواهید object ی که با کد تولید کرده اید در کوکی قرار بدهید ، باید از متد JSON.stringify استفاده کنید و مقدار را به این صورت در کوکی قرار دهید.
$.cookie('data', JSON.stringify(jsonobject), { expires: 365, json: true });
که در اینجا jsonobject ، ابجکتی است که شما تولید کرده اید و قصد ذخیره آن را دارید.
من از این امکان در نسخه بعدی این پروژه استفاده کرده ام ، و به کمک این پلاگین ساده اما مفید ، وب سایت هایی که کاربر نتایج آن را مشاهده کرده است در کوکی کاربر ذخیره میکنم تا در مراجعه بعدی میزان تغییرات رنکینگهای وب سایت ای در خواست شده را ، به کاربر نمایش دهم. نسخه بعد all-ranks.com تا آخر هفته آینده در سرور اختصاصی ( و نه این هاست رایگان (!)) قرار میگیرد و به مرور قسمت هایی که در این پروژه پیاده سازی شده (پلاگینهای جی کوئری و کدهای سرور ) در اینجا شرح میدهم.
امیدوارم تونسته باشم مطلب مفید و مناسبی به شما دوستان عزیزم انتقال بدم.
هر چند که #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
اشتراکها
نکاتی جهت طراحی قالبهای مشکی
اشتراکها