First, the decoupling of .NET from Windows
Second, the newfound focus on CLR performance
Third, moving .NET’s tooling to a cross-platform model
Fourth, the .NET user base is embracing the OSS ecosystem as a whole
Fifth, in general the direction on .NET development is pushing users further down into the details of the stack
Sixth, Microsoft’s platform work being done out in the open
اشتراکها
نوع Span به همراه NET Core 2.1. ارائه شد. یکی از مهمترین مزایای آن امکان دسترسی به قسمتی از حافظه (توسط متد Split آن)، بدون ایجاد سربار کپی یا تخصیص مجدد حافظهای برای دسترسی به آن است. قدم بعدی، بسط این قابلیت به امکانات ذاتی زبان #C است؛ تحت عنوان ویژگی Ranges که امکان دسترسی مستقیم به بازهای/قسمتی از آرایهها، رشتهها و یا Spanها را میسر میکند.
معرفی عملگر Hat
برای دسترسی به آخرین عضو یک آرایه عموما از روش زیر استفاده میشود:
یعنی از آخر شروع به شمارش کرده و 1 واحد از آن کم میکنیم (این عدد 1 را بهخاطر داشته باشید) و یا اگر بخواهیم از LINQ استفاده کنیم میتوان از متد Last آن استفاده کرد:
همچنین اگر بخواهیم دومین عنصر از آخر آنرا دریافت کنیم:
نیز مجددا از آخر شروع به شمارش کرده و 2 واحد، از آن کم میکنیم (این عدد 2 را نیز بهخاطر داشته باشید).
این شمردنهای از آخر در C# 8.0 توسط ارائهی عملگر hat یا همان ^ که پیشتر کار xor را انجام میداد (و البته هنوز هم در جای خودش همین مفهوم را دارد)، میسر شدهاست:
این قطعه کد یعنی به دنبال ایندکس X، از آخر آرایه هستیم.
نکتهی مهم: کسانیکه شروع به آموزش برنامه نویسی میکنند، مدتی طول میکشد تا عادت کنند که اولین ایندکس یک آرایه از صفر شروع میشود. در اینجا باید درنظر داشت که با بکارگیری «عملگر کلاه»، آخرین ایندکس یک آرایه از «یک» شروع میشود و نه از صفر. برای نمونه در مثال زیر به خوبی تفاوت بین ایندکس از ابتدا و ایندکس از انتها را میتوانید مشاهده کنید:
آرایهی فوق، 9 عضو دارد. در این حالت اولین عنصر آن با ایندکس صفر قابل دسترسی است. در همین حالت همین ایندکس را اگر از آخر محاسبه کنیم، 9 خواهد بود و همینطور برای مابقی.
در حالت کلی ایندکس n^ معادل sequence.Length - n است. بنابراین sequence[^0] به معنای sequence[sequence.Length] است و هر دو مورد یک index out of range exception را صادر میکنند.
IDE نیز با فعال سازی C# 8.0، زمانیکه به قطعه کد زیر میرسد، زیر words.Length - 1 خط کشیده و پیشنهاد میدهد که بهتر است از 1^ استفاده کنید:
معرفی نوع جدید Index
در C# 8.0 زمانیکه مینویسم 1^، در حقیقت قطعه کد زیر را ایجاد کردهایم:
Index یک struct و نوع جدید در C# 8.0 میباشد که در فضای نام System قرار گرفتهاست. سه سطر فوق دقیقا به یک معنا هستند و هر کدام خلاصه شده و ساده شدهی سطر قبلی است.
در سطر اول، پارامتر fromEnd نیز قابل تعریف است. این fromEnd با مقدار true، همان عملگر ^ در اینجا است و عدم ذکر این عملگر به معنای false بودن آن است:
در این مثال دو نمونه کاربرد fromEnd با مقدار false و سپس true را ملاحظه میکنید. در حالتیکه Index i5 = 5 تعریف شدهاست، دسترسی به عناصر آرایه همانند قبل از ایندکس صفر و از آغاز شروع میشود و نه از ایندکس یک.
روش دسترسی به بازهای از اعضای یک آرایه تا پیش از C# 8.0
فرض کنید آرایهای از اعداد بین 1 تا 10 را به صورت زیر ایجاد کردهاید:
اکنون اگر بخواهیم به بازهی مشخصی درون این آرایه دسترسی پیدا کنیم، میتوان حداقل به یکی از دو روش زیر عمل کرد:
یا میتوان برای مثال توسط LINQ، از متدهای Skip و Take آن برای جدا کردن بازهای از آرایهی numbers استفاده کرد و یا حتی میتوان از روش کپی کردن آرایهها به آرایهای جدید نیز کمک گرفت که در هر دو حالت، مراحلی که باید طی شوند قابل توجه است. با ارائهی C# 8.0، این نوع دسترسیها جزئی از قابلیتهای زبان شدهاند.
روش دسترسی به بازهای از اعضای یک آرایه در C# 8.0
در C# 8.0 برای دسترسی به بازهای از عناصر یک آرایه میتوان از range expression که به صورت x..y نوشته میشود، استفاده کرد. در ادامه، مثالهایی را از کاربردهای عبارت .. ملاحظه میکنید:
3..1 به معنای انتخاب بازهای از المان 2 تا المان 3 است. در اینجا به واژهی «المان» دقت کنید که معادل ایندکس آن در آرایه نیست. یعنی عدد ابتدای یک بازه دقیقا به ایندکس آن در آرایه اشاره میکند و عدد انتهای بازه، به ایندکس ماقبل آن (از این جهت که بتوان توسط 0^، انتهای بازه را مشخص کرد؛ بدون دریافت استثنای index out of range). به همین جهت به ابتدای بازه میگویند inclusive و به انتهای آن exclusive:
1^..1 به معنای انتخاب بازهای از المان 2، تا المان یکی مانده به آخر است:
همچنین [0^..0] نیز به معنای کل بازه است.
مثالی دیگر: بازنویسی یک حلقهی for با foreach
حلقهی for زیر را
توسط range expression میتوان به صورت زیر بازنویسی کرد:
بنابراین همانطور که مشاهده میکنید، ذکر بازهی 4..1 به صورت حلقهی for (int i = 1; i < 4; i++) تفسیر میشود و نه حلقهی for (int i = 1; i <= 4; i++)
یعنی ابتدای آن inclusive است و انتهای آن exclusive
چند مثال کاربردی و متداول از بازهها
معرفی نوع جدید Range
در C# 8.0 زمانیکه مینویسم 4..1، در حقیقت قطعه کد زیر را ایجاد کردهایم:
Range نیز یک struct و نوع جدید در C# 8.0 میباشد که در فضای نام System قرار گرفتهاست. سه سطر فوق دقیقا به یک معنا هستند و هر کدام خلاصه شده و ساده شدهی سطر قبلی است.
یک مثال: استفاده از نوع جدید Range به عنوان پارامتر یک متد
همانطور که ملاحظه میکنید، Range را میتوان به عنوان پارامتر متدها نیز استفاده و بر روی آرایهها اعمال کرد؛ اما با <List<T سازگار نیست.
مثالی دیگر: استفاده از Range به عنوان جایگزینی برای متد String.Substring
از Range میتوان برای کار بر روی رشتهها و انتخاب قسمتی از آنها نیز استفاده کرد:
چند مثال دیگر:
سؤال: زمانیکه بازهای از یک آرایه را انتخاب میکنیم، آیا یک آرایهی جدید ایجاد میشود، یا هنوز به همان آرایهی قبلی اشاره میکند؟
پاسخ: یک آرایهی جدید ایجاد میشود؛ اما میتوان با فراخوانی متد ()array.AsSpan پیش از انتخاب یک بازه، بازهای را تولید کرد که دقیقا به همان آرایهی اصلی اشاره میکند و یک کپی جدید نیست:
در این مثال، آرایهی اصلی را ابتدا تبدیل به یک Span کردهایم و سپس بازهای از روی آن انتخاب شدهاست. به همین جهت است که زمانیکه از ref locals برای تغییر عضوی از این بازه استفاده میشود، این تغییر بر روی آرایهی اصلی نیز تاثیر میگذارد.
معرفی عملگر Hat
برای دسترسی به آخرین عضو یک آرایه عموما از روش زیر استفاده میشود:
var integerArray = new int[3]; var lastItem = integerArray[integerArray.Length - 1];
var integerList = integerArray.ToList(); integerList.Last();
var secondToLast = integerArray[integerArray.Length - 2];
این شمردنهای از آخر در C# 8.0 توسط ارائهی عملگر hat یا همان ^ که پیشتر کار xor را انجام میداد (و البته هنوز هم در جای خودش همین مفهوم را دارد)، میسر شدهاست:
var lastItem = integerArray[^1];
نکتهی مهم: کسانیکه شروع به آموزش برنامه نویسی میکنند، مدتی طول میکشد تا عادت کنند که اولین ایندکس یک آرایه از صفر شروع میشود. در اینجا باید درنظر داشت که با بکارگیری «عملگر کلاه»، آخرین ایندکس یک آرایه از «یک» شروع میشود و نه از صفر. برای نمونه در مثال زیر به خوبی تفاوت بین ایندکس از ابتدا و ایندکس از انتها را میتوانید مشاهده کنید:
var words = new string[] { // index from start index from end "The", // 0 ^9 "quick", // 1 ^8 "brown", // 2 ^7 "fox", // 3 ^6 "jumped", // 4 ^5 "over", // 5 ^4 "the", // 6 ^3 "lazy", // 7 ^2 "dog" // 8 ^1 }; // 9 (or words.Length) ^0
در حالت کلی ایندکس n^ معادل sequence.Length - n است. بنابراین sequence[^0] به معنای sequence[sequence.Length] است و هر دو مورد یک index out of range exception را صادر میکنند.
IDE نیز با فعال سازی C# 8.0، زمانیکه به قطعه کد زیر میرسد، زیر words.Length - 1 خط کشیده و پیشنهاد میدهد که بهتر است از 1^ استفاده کنید:
Console.WriteLine($"The last word is {words[words.Length - 1]}");
معرفی نوع جدید Index
در C# 8.0 زمانیکه مینویسم 1^، در حقیقت قطعه کد زیر را ایجاد کردهایم:
var index = new Index(value: 1, fromEnd: true); Index indexStruct = ^1; var indexShortHand = ^1;
در سطر اول، پارامتر fromEnd نیز قابل تعریف است. این fromEnd با مقدار true، همان عملگر ^ در اینجا است و عدم ذکر این عملگر به معنای false بودن آن است:
int[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; Console.WriteLine(a[a.Length – 2]); // will write 8 on console. Console.WriteLine(a[^2]); // will write 8 on console. Index i5 = 5; Console.WriteLine(a[i5]); //will write 5 on console. Index i2fromEnd = ^2; Console.WriteLine(a[i2fromEnd]); // will write 8 on console.
روش دسترسی به بازهای از اعضای یک آرایه تا پیش از C# 8.0
فرض کنید آرایهای از اعداد بین 1 تا 10 را به صورت زیر ایجاد کردهاید:
var numbers = Enumerable.Range(1, 10).ToArray();
var (start, end) = (1, 7); var length = end - start; // Using LINQ var subset1 = numbers.Skip(start).Take(length); // Or using Array.Copy var subset2 = new int[length]; Array.Copy(numbers, start, subset2, 0, length);
روش دسترسی به بازهای از اعضای یک آرایه در C# 8.0
در C# 8.0 برای دسترسی به بازهای از عناصر یک آرایه میتوان از range expression که به صورت x..y نوشته میشود، استفاده کرد. در ادامه، مثالهایی را از کاربردهای عبارت .. ملاحظه میکنید:
var myArray = new string[] { "Item1", "Item2", "Item3", "Item4", "Item5" };
var fromIndexToX = myArray[1..3]; // = [Item2, Item3]
var fromIndexToXFromTheEnd = myArray[1..^1]; // = [ "Item2", "Item3", "Item4" ]
ذکر انتهای بازه اجباری نیست و اگر ذکر نشود به معنای 0^ است. برای مثال ..1 به معنای انتخاب بازهای از المان 2، تا آخرین المان است:
var fromAnIndexToTheEnd = myArray[1..]; // = [ "Item2", "Item3", "Item4", "Item5" ]
ذکر ابتدای بازه نیز اجباری نیست و اگر ذکر نشود به معنای عدد صفر است. برای مثال 3.. به معنای انتخاب بازهای از اولین المان، تا سومین المان است:
var fromTheStartToAnIndex = myArray[..3]; // = [ "Item1", "Item2", "Item3" ]
اگر ابتدا و انتهای بازه ذکر نشوند، کل بازه و تمام عناصر آن بازگشت داده میشوند:
var entireRange = myArray[..]; // = [ "Item1", "Item2", "Item3", "Item4", "Item5" ]
مثالی دیگر: بازنویسی یک حلقهی for با foreach
حلقهی for زیر را
var myArray = new string[] { "Item1", "Item2", "Item3", "Item4", "Item5" }; for (int i = 1; i <= 3; i++) { Console.WriteLine(myArray[i]); }
foreach (var item in myArray[1..4]) // = [ "Item2", "Item3", "Item4" ] { Console.WriteLine(item); }
یعنی ابتدای آن inclusive است و انتهای آن exclusive
چند مثال کاربردی و متداول از بازهها
using System; using System.Linq; namespace ConsoleApp { class Program { private static readonly int[] _numbers = Enumerable.Range(1, 10).ToArray(); static void Main() { var skip2CharactersAndTake2Characters = _numbers[2..4]; // صرفنظر کردن از دو عنصر اول و سپس انتخاب دو عنصر var skipFirstAndLastCharacter = _numbers[1..^1]; // صرفنظر کردن از دو عنصر اول و آخر var last3Characters = _numbers[^3..]; // انتخاب بازهای شامل سه عنصر آخر var first4Characters = _numbers[0..4]; // دریافت بازهای از 4 عنصر اول var rangeStartFrom2 = _numbers[2..]; // دریافت بازهای شروع شده از المان دوم تا آخر var skipLast3Characters = _numbers[..^3]; // صرفنظر کردن از سه المان آخر var rangeAll = _numbers[..]; // انتخاب کل بازه } } }
معرفی نوع جدید Range
در C# 8.0 زمانیکه مینویسم 4..1، در حقیقت قطعه کد زیر را ایجاد کردهایم:
var range = new Range(1, 4); Range rangeStruct = 1..4; var rangeShortHand = 1..4;
یک مثال: استفاده از نوع جدید Range به عنوان پارامتر یک متد
using System; using System.Linq; namespace ConsoleApp { class Program { private static readonly int[] _numbers = Enumerable.Range(1, 10).ToArray(); static void Print(Range range) => Console.WriteLine($"{range} => {string.Join(", ", _numbers[range])}"); static void Main() { Print(1..3); // 1..3 => 2, 3 Print(..3); // 0..3 => 1, 2, 3 Print(3..); // 3..^0 => 4, 5, 6, 7, 8, 9, 10 Print(1..^1); // 1..^1 => 2, 3, 4, 5, 6, 7, 8, 9 Print(^2..^1); // ^2..^1 => 9 } } }
مثالی دیگر: استفاده از Range به عنوان جایگزینی برای متد String.Substring
از Range میتوان برای کار بر روی رشتهها و انتخاب قسمتی از آنها نیز استفاده کرد:
Console.WriteLine("123456789"[1..4]); // Would output 234
var helloWorldStr = "Hello, World!"; var hello = helloWorldStr[..5]; Console.WriteLine(hello); // Output: Hello var world = helloWorldStr[7..]; Console.WriteLine(world); // Output: World! var world2 = helloWorldStr[^6..]; // Take the last 6 characters Console.WriteLine(world); // Output: World!
سؤال: زمانیکه بازهای از یک آرایه را انتخاب میکنیم، آیا یک آرایهی جدید ایجاد میشود، یا هنوز به همان آرایهی قبلی اشاره میکند؟
پاسخ: یک آرایهی جدید ایجاد میشود؛ اما میتوان با فراخوانی متد ()array.AsSpan پیش از انتخاب یک بازه، بازهای را تولید کرد که دقیقا به همان آرایهی اصلی اشاره میکند و یک کپی جدید نیست:
var arr = (new[] { 1, 4, 8, 11, 19, 31 }).AsSpan(); var range = arr[2..5]; ref int elt1 = ref range[1]; elt1 = -1; int copiedElement = range[2]; copiedElement = -2; Console.WriteLine($"range[1]: {range[1]}, range[2]: {range[2]}"); // output: range[1]: -1, range[2]: 19 Console.WriteLine($"arr[3]: {arr[3]}, arr[4]: {arr[4]}"); // output: arr[3]: -1, arr[4]: 19
اشتراکها
چرا از سی پلاس پلاس نا امید نشدم؟
مقدمه
با اجرای این مثال نتیجه زیر حاصل میشود:
همانطور که میبنید عملیات replace برای سایر توکنها انجام نمیشود.
در این روش عملیات replace تا زمانیکه تغییری در رشته جاری ایجاد نشود ادامه مییابد. با استفاده از این متد، خروجی مثال قبل درست و به صورت زیر خواهد بود:
استفاده از این متد هم نتیجه درستی برای مثال آخر ارائه میدهد.
این متد نیز نتیجه مشابهی ارائه میکند. حال به مثال زیر توجه کنید:
خروجی صحیح مثال فوق باید به صورت زیر باشد:
درصورتیکه رشتهای که دو متد از سه متد آخر (3 و 4) به عنوان خروجی ارائه میدهند بهصورت زیر است:
برای آخرین متد که ازحلقه while (درواقع با اندیس معکوس) استفاده میکند (5) مثالی که خطای مورد بحث را نشان میدهد به صورت زیر است:
که خروجی اشتباه زیر را برمیگرداند:
درصورتیکه باید مقدار زیر را برگشت دهد:
دلیل رخدادن این خطا اجرای عملیات replace به صورت جداگانه و کامل برای هر توکن، از اول تا آخر برای رشتههای replace شده جاری است که کار را خراب میکند.
خروجی این مثال بهصورت زیر است:
پیادهسازی زیر برای حل این مشکل استفاده میشود.
با استفاده از این متد جدید خروجی مثالهای قبل درست خواهد بود.
که خروجی زیر را ارائه میدهد:
.
در اینجا با استفاده از یک عبارت RegExp پیچیدهتر و کنترل تکرار کاراکترهای { و } در متد سفارشی جایگزینی در دستور replace، پیادهسازی اولیه این ویژگی ارائه شده است..
.
برای حالت prototype نیز داریم:
دقت کنید قسمت ابتدایی این متد که برای بررسی اعتبار آرگومانهای ورودی است، برای سادگی عملیات کامنت شده است. همانطور که میبینید این متد پیادهسازی نسبتا مفصلی دارد و امکانات بیشتری نیز در اختیار برنامه نویسان قرار میدهد. البته سایر متدهای مربوطه بدلیل طولانی بودن در اینجا آورده نشده است. برای مثال امکانات پیشرفتهتری مثل زیر با استفاده از این کتابخانه در دسترس هستند:
همانطور که میبینید این کتابخانه امکانات کاملتری نیز دارد. مثالهای مربوط به این کتابخانه به صورت زیر هستند که تواناییهای نسبتا کامل آنرا نشان میدهد:.
با اینکه زبان برنامه نویسی جاوا اسکریپت زبانی بسیار قدرتمند و با امکانات زیاد است، اما فقدان برخی متدهای کمکی پرمصرف در آن در برخی موارد باعث دردسرهایی میشود. امکانی برای فرمتبندی رشتهها یکی از این نیازهای نسبتا پرکاربرد است.
متدی که در این مطلب قصد توضیح پیادهسازی آنرا داریم، String.format نام دارد که فرایندی مشابه متد متناظر در دات نت را انجام میدهد. همچنین سعی شده است تا نحوه پیادهسازی این متد کمکی از ابتداییترین نمونهها تا نسخههای پیشرفتهتر برای درک بهتر مطلب نشان داده شود.
.
پیادهسازی متد String.format
1. در این پیادهسازی از اولین فرایندی که ممکن است به ذهن یک برنامهنویس خطور کند استفاده شده است. این پیادهسازی بسیار ساده به صورت زیر است:
String.format = function () { var s = arguments[0]; for (var i = 0; i < arguments.length - 1; i++) { s = s.replace("{" + i + "}", arguments[i + 1]); } return s; };
.
2. پیادهسازی مشابهی هم با استفاده از نوع دیگری از حلقه for که تقریبا! مشابه با حلقه foreach در #C است به صورت زیر میتوان درنظر گرفت:
String.format = function () { var s = arguments[0]; for (var arg in arguments) { var i = parseInt(arg); s = s.replace("{" + i + "}", arguments[i + 1]); } return s; };
در این متدها ابتدا فرمت واردشده توسط کاربر از لیست آرگومانهای متد خوانده شده و در متغیر s ذخیره میشود. سپس درون یک حلقه به ازای هر توکن موجود در رشته فرمت، یک عملیات replace با مقدار متناظر در لیست آرگومانهای متد انجام میشود. نحوه استفاده از این متد نیز به صورت زیر است:
console.log(String.format("{0} is nice!", "donettips.info"));
هر دو متد خروجی یکسانی دارند، به صورت زیر:
donettips.info is nice!
تا اینجا به نظر میرسد که عملیات بهدرستی پیش میرود. اما اولین و بزرگترین مشکل در این دو متد نحوه کارکردن متد replace در جاوا اسکریپت است. این متد با این نحوه فراخوانی تنها اولین توکن موجود را یافته و عملیات جایگزینی را برای آن انجام میدهد. برای روشنتر شدن موضوع به مثال زیر توجه کنید:
console.log(String.format("{0} is {1} nice! {0} is {1} nice!", "donettips.info", "very"));
donettips.info is very nice! {0} is {1} nice!
.
3. برای حل مشکل فوق میتوان از روش ساده زیر استفاده کرد:
String.format = function () { var original = arguments[0], replaced; for (var i = 0; i < arguments.length - 1; i++) { replaced = ''; while (replaced != original) { original = replaced || original; replaced = original.replace("{" + i + "}", arguments[i + 1]); } } return replaced; };
donettips.info is very nice! donettips.info is very nice!
.
4. راه حل دیگر استفاده از امکانات شی RegExp در دستور replace است. نکته مهم استفاده از modifier کلی یا global (که با حرف g مشخص میشود) در شی تولیدی از RegExp است (^ و ^ و ^، برای جلوگیری از دورشدن از بحث اصلی، جستجو برای کسب اطلاعات بیشتر در این زمینه به خوانندگان واگذار میشود). برای استفاده از این شی متد ما به صورت زیر تغییر میکند:
String.format = function () { var s = arguments[0]; for (var i = 0; i < arguments.length - 1; i++) { s = s.replace(new RegExp("\\{" + i + "\\}", "g"), arguments[i + 1]); } return s; };
.
5. روش دیگری که کمی از دو متد قبلی سریعتر اجرا میشود (به دلیل استفاده از حلقه while) به صورت زیر است:
String.format = function () { var s = arguments[0], i = arguments.length - 1; while (i--) { s = s.replace(new RegExp('\\{' + i + '\\}', 'g'), arguments[i + 1]); } return s; };
console.log(String.format("{0}:0 {1}:1 {2}:2", "zero", "{2}", "two"));
zero:0 {2}:1 two:2
zero:0 two:1 two:2
console.log(String.format("{0}:0 {1}:1 {2}:2", "zero", "one", "{1}"));
zero:0 one:1 one:2
zero:0 one:1 {1}:2
.
6. برای حل مشکل بالا نیز میتوان از یکی دیگر از امکانات دستور replace استفاده کرد که به صورت زیر است:
String.format = function () { var args = arguments; return args[0].replace(/{(\d+)}/g, function (match, number) { return args[parseInt(number) + 1]; }); };
در اینجا از قابلیت سفارشیسازی عملیات جایگزینی در دستور replace استفاده شده است. با استفاده از این ویژگی عملیات replace برای هر توکن جداگانه انجام میشود و بنابراین تغییرات اعمالی در حین عملیات تاثیر مستقیمی برای ادامه روند نخواهد گذاشت.
دقت کنید که برای بکاربردن RegExp درون دستور replace به جای تولید یک نمونه از شی RegExp میتوان عبارت مربوطه را نیز مستقیما بکار برد. در اینجا از عبارتی کلی برای دریافت تمامی توکنهای با فرمتی به صورت {عدد} استفاده شده است.
متد سفارشی مربوطه نیز شماره ردیف توکن یافتهشده به همراه خود عبارت یافتهشده را به عنوان آرگومان ورودی دریافت کرده و مقدار متناظر را از لیست آرگومانهای متد اصلی پس از تبدیل شماره ردیف توکن به یک عدد، برگشت میدهد (در اینجا نیز برای جلوگیری از دورشدن از بحث اصلی، جستجو برای کسب اطلاعات بیشتر در این زمینه به خوانندگان واگذار میشود).
برای جلوگیری از تداخل بین آرگومانهای متد اصلی و متد تهیهشده برای سفارشیسازی عملیات جایگزینی، در ایتدای متد اصلی، لیست آرگومانهای آن درون متغیر جداگانهای (args) ذخیره شده است.
با استفاده از این متد خروجی درست نشان داده میشود. حال مثال زیر را درنظر بگیرید:
console.log(String.format("{0} is {1} nice!", "donettips.info"));
donettips.info is undefined nice!
.
7. برای کنترل بیشتر و رفع خطاهای احتمالی در متد بالا، میتوان ابتدا از وجود آرگومان مربوطه در متغیر args اطمینان حاصل کرد تا از جایگزینی مقدار undefined در رشته نهایی جلوگیری کرد. مانند نمونه زیر:
String.format = function () { var s = arguments[0], args = arguments; return s.replace(/{(\d+)}/g, function (match, number) { var i = parseInt(number); return typeof args[i + 1] != 'undefined' ? args[i + 1] : match; }); };
در فرمت بندی رشتهها برای نمایش خود کاراکتر { یا } از تکرار آنها (یعنی {{ یا }}) استفاده میشود. اما متد ما تا این لحظه این امکان را ندارد. برای مثال:
console.log(String.format("{0}:0 {1}:1 {2}:2, {{0}} {{{1}}} {{{{2}}}} {2}", "zero", "{2}", "two"));
zero:0 {2}:1 two:2, {zero} {{{2}}} {{{two}}} two
8. برای پیادهسازی امکان اشارهشده در بالا میتوان از کد زیر استفاده کرد:
String.format = function () { var s = arguments[0], args = arguments; return s.replace(/\{\{|\}\}|\{(\d+)\}/g, function (match, number) { if (match == "{{") { return "{"; } if (match == "}}") { return "}"; } var i = parseInt(number); return typeof args[i + 1] != 'undefined' ? args[i + 1] : match; }); };
این متد خروجی صحیح زیر را برای مثال آخر ارائه میدهد:
zero:0 {2}:1 two:2, {0} {{2}} {{2}} two
پیادهسازی بهصورت یک خاصیت prototype
تمامی متدهای نشان دادهشده تا اینجا بهصورت مستقیم از طریق String.format در دسترس خواهند بود (تعریفی شبیه به متدهای استاتیک در دات نت). درصورتیکه بخواهیم از این متدها به صورت یک خاصیت prototype شی string استفاده کنیم (چیزی شبیه به متدهای instance در اشیای دات نت) میتوانیم از تعریف زیر استفاده کنیم:
String.prototype.format = function () { ... }
تنها فرق مهم این پیادهسازی این است که رشته مربوط به فرمت وارده در این متد از طریق شی this در دسترس است و بنابراین شماره اندیس آرگومانهای متد یکی کمتر از متدهای قبلی است که باید مدنظر قرار گیرد. مثلا برای متد آخر خواهیم داشت:
String.prototype.format = function () { var s = this.toString(), args = arguments; return s.replace(/\{\{|\}\}|\{(\d+)\}/g, function (match, number) { if (match == "{{") { return "{"; } if (match == "}}") { return "}"; } return typeof args[number] != 'undefined' ? args[number] : match; }); };
نکته: در تمامی خواص prototype هر شی در جاوا اسکریپت، متغیر this از نوع object است. بنابراین برای جلوگیری از وقوع هر خطا بهتر است ابتدا آنرا به نوع مناسب تبدیل کرد. مثل استفاده از متد toString در متد فوق که موجب تبدیل آن به رشته میشود.
.
ازآنجاکه نیاز به تغییر اندیس در متد سفارشی عملیات replace وجود ندارد، بنابراین خط مربوط به تبدیل آرگومان number به یک مقدار عددی (با دستور parseInt) حذف شده است و از این متغیر به صورت مستقیم استفاده شده است. در این حالت عملیات تبدیل توسط خود جاوا اسکریپت مدیریت میشود که کار را راحتتر میسازد.
بنابراین متد ما به صورت زیر قابل استفاده است:console.log("{0}:0 {1}:1 {2}:2, {{0}} {{{1}}} {{{{2}}}} {2}".format("zero", "{2}", "two"));
.
پیادهسازی با استفاده از توکنهای غیرعددی
برای استفاده از توکنهای غیرعددی میتوانیم به صورت زیر عمل کنیم:
String.format = function () { var s = arguments[0], args = arguments[1]; for (var arg in args) { s = s.replace(new RegExp("{" + arg + "}", "g"), args[arg]); } return s; };
String.prototype.format = function () { var s = this.toString(), args = arguments[0]; for (var arg in args) { s = s.replace(new RegExp("{" + arg + "}", "g"), args[arg]); } return s; };
با استفاده از این دو متد داریم:
console.log(String.format("{site} is {adj}! {site} is {adj}!", { site: "donettips.info", adj: "nice" })); console.log("{site} is {adj}! {site} is {adj}!".format({ site: "donettips.info", adj: "nice" }));
.
تا اینجا متدهایی نسبتا کامل برای نیازهای عادی برنامهنویسی تهیه شده است. البته کار توسعه این متد برای پشتیبانی از امکانات پیشرفتهتر فرمتبندی رشتهها میتواند ادامه پیدا کند.
.
کتابخانههای موجود
یکی از کاملترین کتابخانههای کار با رشتهها همان کتابخانه معروف Microsoft Ajax Client Libray است که بیشتر امکانات موجود کار با رشتهها در دات نت را در خود دارد. صرفا جهت آشنایی، پیادهسازی متد String.format در این کتابخانه در زیر آورده شده است:
String.format = function String$format(format, args) { /// <summary locid="M:J#String.format" /> /// <param name="format" type="String"></param> /// <param name="args" parameterArray="true" mayBeNull="true"></param> /// <returns type="String"></returns> // var e = Function._validateParams(arguments, [ // { name: "format", type: String }, // { name: "args", mayBeNull: true, parameterArray: true } // ]); // if (e) throw e; return String._toFormattedString(false, arguments); }; String._toFormattedString = function String$_toFormattedString(useLocale, args) { var result = ''; var format = args[0]; for (var i = 0; ; ) { var open = format.indexOf('{', i); var close = format.indexOf('}', i); if ((open < 0) && (close < 0)) { result += format.slice(i); break; } if ((close > 0) && ((close < open) || (open < 0))) { if (format.charAt(close + 1) !== '}') { throw Error.argument('format', Sys.Res.stringFormatBraceMismatch); } result += format.slice(i, close + 1); i = close + 2; continue; } result += format.slice(i, open); i = open + 1; if (format.charAt(i) === '{') { result += '{'; i++; continue; } if (close < 0) throw Error.argument('format', Sys.Res.stringFormatBraceMismatch); var brace = format.substring(i, close); var colonIndex = brace.indexOf(':'); var argNumber = parseInt((colonIndex < 0) ? brace : brace.substring(0, colonIndex), 10) + 1; if (isNaN(argNumber)) throw Error.argument('format', Sys.Res.stringFormatInvalid); var argFormat = (colonIndex < 0) ? '' : brace.substring(colonIndex + 1); var arg = args[argNumber]; if (typeof (arg) === "undefined" || arg === null) { arg = ''; } if (arg.toFormattedString) { result += arg.toFormattedString(argFormat); } else if (useLocale && arg.localeFormat) { result += arg.localeFormat(argFormat); } else if (arg.format) { result += arg.format(argFormat); } else result += arg.toString(); i = close + 1; } return result; }
console.log(String.format("{0:n}, {0:c}, {0:p}, {0:d}", 100.0001)); // result: 100.00, ¤100.00, 10,000.01 %, 100.0001 console.log(String.format("{0:d}, {0:t}", new Date(2015, 1, 1, 10, 45))); // result: 02/01/2015, 10:45
آخرین نسخه این کتابخانه از اینجا قابل دریافت است (این متدها درون فایل MicrosoftAjax.debug.js قرار دارند). این کتابخانه دیگر به این صورت و با این نام توسعه داده نمیشود و چند سالی است که تصمیم به توسعه ویژگیهای جدید آن به صورت پلاگینهای jQuery گرفته شده است.
.
کتابخانه دیگری که میتوان برای عملیات فرمتبندی رشتهها در جاوا اسکریپت از آن استفاده کرد، کتابخانه معروف jQuery Validation است. این کتابخانه یک متد نسبتا خوب با نام format برای فرمت کردن رشتهها دارد. نحوه استفاده از این متد به صورت زیر است:
var template = jQuery.validator.format("{0} is not a valid value"); console.log(template("abc")); // result: 'abc is not a valid value'
.
کتابخانه نسبتا کامل دیگری که وجود دارد، با عنوان Stringformat از اینجا قابل دریافت است. برای استفاده از این کتابخانه باید به صورت زیر عمل کرد:
String.format([full format string], [arguments...]); // or: [date|number].format([partial format string]);
// Object path String.format("Welcome back, {username}!", { id: 3, username: "JohnDoe" }); // Result: "Welcome back, JohnDoe!" // Date/time formatting String.format("The time is now {0:t}.", new Date(2009, 5, 1, 13, 22)); // Result: "The time is now 01:22 PM." // Date/time formatting (without using a full format string) var d = new Date(); d.format("hh:mm:ss tt"); // Result: "02:28:06 PM" // Custom number format string String.format("Please call me at {0:+##0 (0) 000-00 00}.", 4601111111); // Result: "Please call me at +46 (0) 111-11 11." // Another custom number format string String.format("The last year result was {0:+$#,0.00;-$#,0.00;0}.", -5543.346); // Result: "The last year result was -$5,543.35." // Alignment String.format("|{0,10:PI=0.00}|", Math.PI); // Result: "| PI=3.14|" // Rounding String.format("1/3 ~ {0:0.00}", 1/3); // Result: "1/3 ~ 0.33" // Boolean values String.format("{0:true;;false}", 0); // Result: "false" // Explicitly specified localization // (note that you have to include the .js file for used cultures) msf.setCulture("en-US"); String.format("{0:#,0.0}", 3641.667); // Result: "3,641.7" msf.setCulture("sv-SE"); String.format("{0:#,0.0}", 3641.667); // Result: "3 641,7"
.
یک کتابخانه دیگر نیز از این آدرس قابل دریافت است. این کتابخانه با عنوان String.format نامگذاری شده است. نحوه استفاده از این کتابخانه نیز به صورت زیر است:
//inline arguments String.format("some string with {0} and {1} injected using argument {{number}}", 'first value', 'second value'); //returns: 'some string with first value and second value injected argument {number}' //single array String.format("some string with {0} and {1} injected using array {{number}}", [ 'first value', 'second value' ]); //returns: 'some string with first value and second value injected using array {number}' //single object String.format("some string with {first} and {second} value injected using {{propertyName}}",{first:'first value',second:'second value'}); //returns: 'some string with first value and second value injected using {propertyName}'
کتابخانه نسبتا معروف و کامل sprintf نیز در اینجا وجود دارد. این کتابخانه امکانات بسیاری همچون متدهای متناظر در زبان C دارد.
.
منابع
اشتراکها
5 قانون ساده برای عملیات CRUD
مطالب
سری آموزشی PRISM
PRISM یا Composite Application Guidance الگوهایی را برای تولید برنامههای WPF ویا Silverlight ماژولار با قابلیت تست پذیری بالا ارائه میدهند. شعار این مجموعه built for change و built to last است که به معنای تهیه سیستمهایی با قابلیت تغییر بالا و همچنین سهولت نگهداری آنها در دراز مدت میباشد.
جناب Mike Taulty را احتمالا با ویدیوهای آموزش WCF به خاطر دارید. ایشان مجموعه جدیدی را به نام Video Series on PRISM for Silverlight 3 تهیه کردهاند که از لینکهای زیر قابل دریافت است:
Part 1: Taking Sketched Code Towards Unity
Part 2: Dependency Injection with Unity
Part 3: Modularity with Prism
Part 4: The Unity Bootstrapper
Part 5: Moving to a Modular Silverlight Project
Part 6: Shells, Regions, Views
Part 7: Commands
Part 8: Loosely Coupled Events with Event Aggregation
Part 9: Sharing Data via Region Contexts
Part 10: A Larger Example: "Email Client"
Part 2: Dependency Injection with Unity
Part 3: Modularity with Prism
Part 4: The Unity Bootstrapper
Part 5: Moving to a Modular Silverlight Project
Part 6: Shells, Regions, Views
Part 7: Commands
Part 8: Loosely Coupled Events with Event Aggregation
Part 9: Sharing Data via Region Contexts
Part 10: A Larger Example: "Email Client"
The first version of ASP.NET was released 17 years ago and during these years, it is fascinating to see how the ASP.NET team constructively reacted through these years to the major shifts happening on the web. Initially a platform that was closed and tried to hide and abstract the web; ASP.NET has metamorphized into an open source and cross platform - one that fully embraces the nature of the web. This is the first part of a series of 3 articles that will cover the history of ASP.NET from its launch to the latest ASP.NET Core releases.
نظرات اشتراکها
#C در بین زبانهای برتر سال 2014
خیر. این رقم اتفاقا بسیار قابل توجه است؛ با در نظر گرفتن، سابقهی کم آن مثلا در مقایسه با JavaScript یا ++C ، تعداد پلتفرمهای محدودی که از آن پشتیبانی میکنند (بیشتر ویندوز است). همچنین پیش فرضهای سنتی و تاثیر گذار محیطهای دانشگاهی که عموما C و ++C است.
در قسمت پنجم در مورد ابزار Ngen کمی صحبت کردیم و در این قسمت هم در مورد آن صحبت هایی خواهیم کرد. گفتیم که این ابزار در زمان نصب، اسمبلیها را کامپایل میکند تا در زمان اجرا JIT وقتی برای آن نگذارد. این کار دو مزیت به همراه دارد:
- بهینه سازی زمان آغاز به کار برنامه
- کاهش صفحات کاری برنامه: از آنجا که برنامه از قبل کامپایل شده، فراهم کردن صفحه بندی از ابتدای کار امر چندان دشواری نخواهد بود؛ لذا در این حالت صفحه بندی حافظه به صورت پویاتری انجام میگردد. شیوهی کار به این صورت است که اسمبلیها به چندین پروسهی کاری کوچکتر تبدیل شده تا صفحه بندی هر کدام جدا صورت گیرد و محدودهی صفحه بندی کوچکتر میشود. در نتیجه کمتر نقصی در صفحه بندی دیده شده یا کلا دیده نخواهد شد. نتیجهی کار هم در یک فایل ذخیره میگردد که این فایل میتواند نگاشت به حافظه شود تا این قسمت از حافظه به طور اشتراکی مورد استفاده قرار گیرد و بدین صورت نیست که هر پروسهای برای خودش قسمتی را گرفته باشد.
%SystemRoot%\Assembly\NativeImages_v4.0.#####_64
نام دایرکتوری اطلاعاتی شامل نسخه CLR و اطلاعاتی مثل اینکه برنامه بر اساس چه نسخهای 32 یا 64 بیت کامپایل شده است.
معایب
احتمالا شما پیش خود میگویید این مورد فوق العاده امکان جالبی هست. کدها از قبل تبدیل شدهاند و دیگر فرآیند جیت صورت نمیگیرد. در صورتیکه ما تمامی امکانات یک CLR مثل مدیریت استثناءها و GC و ... را داریم، ولی غیر از این یک مشکلاتی هم به کارمان اضافه میشود که در زیر به آنها اشاره میکنیم:
عدم محافظت از کد در برابر بیگانگان: بعضیها تصور میکنند که این کد را میتوانند روی ماشین شخصی خود کامپایل کرده و فایل ngen را همراه با آن ارسال کنند. در این صورت کد IL نخواهد بود ولی موضوع این هست اینکار غیر ممکن است و هنوز استفاده از اطلاعات متادیتاها پابرجاست به خصوص در مورد اطلاعات چون reflection و serializationها . پس کد IL کماکان همراهش هست. نکتهی بعدی اینکه انتقال هم ممکن نیست؛ بنا به شرایطی که در مورد بعدی دلیل آن را متوجه خواهید شد.
از سینک با سیستم خارج میشوند: موقعیکه CLR، اسمبلیها را به داخل حافظه بار میکند، یک سری خصوصیات محیط فعلی را با زمانیکه عملیات تبدیل IL به کد ماشین صورت گرفته است، چک میکند. اگر این خصوصیات هیچ تطابقی نداشته باشند، عملیات JIT همانند سابق انجام میگردد. خصوصیات و ویژگیهایی که چک میشوند به شرح زیر هستند:
معایب
احتمالا شما پیش خود میگویید این مورد فوق العاده امکان جالبی هست. کدها از قبل تبدیل شدهاند و دیگر فرآیند جیت صورت نمیگیرد. در صورتیکه ما تمامی امکانات یک CLR مثل مدیریت استثناءها و GC و ... را داریم، ولی غیر از این یک مشکلاتی هم به کارمان اضافه میشود که در زیر به آنها اشاره میکنیم:
عدم محافظت از کد در برابر بیگانگان: بعضیها تصور میکنند که این کد را میتوانند روی ماشین شخصی خود کامپایل کرده و فایل ngen را همراه با آن ارسال کنند. در این صورت کد IL نخواهد بود ولی موضوع این هست اینکار غیر ممکن است و هنوز استفاده از اطلاعات متادیتاها پابرجاست به خصوص در مورد اطلاعات چون reflection و serializationها . پس کد IL کماکان همراهش هست. نکتهی بعدی اینکه انتقال هم ممکن نیست؛ بنا به شرایطی که در مورد بعدی دلیل آن را متوجه خواهید شد.
از سینک با سیستم خارج میشوند: موقعیکه CLR، اسمبلیها را به داخل حافظه بار میکند، یک سری خصوصیات محیط فعلی را با زمانیکه عملیات تبدیل IL به کد ماشین صورت گرفته است، چک میکند. اگر این خصوصیات هیچ تطابقی نداشته باشند، عملیات JIT همانند سابق انجام میگردد. خصوصیات و ویژگیهایی که چک میشوند به شرح زیر هستند:
- ورژن CLR: در صورت تغییر، حتی با پچها و سرویس پک ها.
- نوع پردازنده: در صورت تغییر پردازنده یا ارتقا سخت افزاری.
- نسخه سیستم عامل : ارتقاء با سرویس پک ها.
- MVID یا Assemblies Identity module Version Id: در صورت کامپایل مجدد تغییر میکند.
- Referenced Assembly's version ID: در صورت کامپایل مجدد اسمبلی ارجاع شده.
- تغییر مجوزها: در صورتی که تغییری نسبت به اولین بار رخ دهد؛ مثلا در قسمت قبلی در مورد اجازه نامه اجرای کدهای ناامن صحبت کردیم. برای نمونه اگر در همین اجازه نامه تغییری رخ دهد، یا هر نوع اجازه نامه دیگری، برنامه مثل سابق (جیت) اجرا خواهد شد.
پی نوشت: در آپدیتهای دات نت فریم ورک به طور خودکار ابزار ngen صدا زده شده و اسمبلیها مجددا کمپایل و دخیره میشوند و برنامه سینک و آپدیت باقی خواهد ماند.
کارایی پایین کد در زمان اجرا: استفاده از ngen از ابتدا قرار بود کارآیی را با حذف جیت بالا ببرد، ولی گاهی اوقات در بعضی شرایط ممکن نیست. کدهایی که ngen تولید میکند به اندازهی جیت بهینه نیستند. برای مثال ngen نمیتواند بسیاری از دستورات خاص پردازنده را جز در زمان runtime مشخص کند. همچنین فیلدهایی چون static را از آنجا که نیاز است آدرس واقعی آنها در زمان اجرا به دست بیاید، مجبور به تکنیک و ترفند میشود و موارد دیگری از این قبیل.
پس حتما نسخهی ngen شده و غیر ngen را بررسی کنید و کارآیی هر دو را با هم مقایسه کنید. برای بسیاری از برنامهها کاهش صفحه بندی یک مزیت و باعث بهبود کارآیی میشود. در نتیجه در این قسمت ngen برنده اعلام میشود.
توجه کنید برای سیستمهایی که در سمت سرور به فعالیت میپردازند، از آنجا که تنها اولین درخواست برای اولین کاربر کمی زمان میبرد و برای باقی کاربران درخواست با سرعت بالاتری اجرا میگردد و اینکه برای بیشتر برنامههای تحت سرور از آنجا که تنها یک نسخه در حال اجراست، هیچ مزیت صفحه بندی را ngen ایجاد نمیکند.
برای بسیاری از برنامههای کلاینت که تجربهی startup طولانی دارند، مایکروسافت ابزاری را به نام Managed Profile Guided Optimization Tool یا MPGO .exe دارد. این ابزار به تحلیل اجرای برنامه شما پرداخته و بررسی میکند که در زمان آغازین برنامه چه چیزهایی نیاز است. اطلاعات به دست آمده از تحلیل به سمت ngen فرستاده شده تا کد بومی بهینهتری تولید گردد. موقعیکه شما آماده ارائه برنامه خود هستید، برنامه را از طریق این تحلیل و اجرا کرده و با قسمتهای اساسی برنامه کار کنید. با این کار اطلاعاتی در مورد اجرای برنامه در داخل یک پروفایل embed شده در اسمبلی، قرار گرفته و ngen موقع تولید کد، این پروفایل را جهت تولید کد بهینه مطالعه خواهد کرد.
در مقالهی بعدی در مورد FCL صحبتهایی خواهیم کرد.
پس حتما نسخهی ngen شده و غیر ngen را بررسی کنید و کارآیی هر دو را با هم مقایسه کنید. برای بسیاری از برنامهها کاهش صفحه بندی یک مزیت و باعث بهبود کارآیی میشود. در نتیجه در این قسمت ngen برنده اعلام میشود.
توجه کنید برای سیستمهایی که در سمت سرور به فعالیت میپردازند، از آنجا که تنها اولین درخواست برای اولین کاربر کمی زمان میبرد و برای باقی کاربران درخواست با سرعت بالاتری اجرا میگردد و اینکه برای بیشتر برنامههای تحت سرور از آنجا که تنها یک نسخه در حال اجراست، هیچ مزیت صفحه بندی را ngen ایجاد نمیکند.
برای بسیاری از برنامههای کلاینت که تجربهی startup طولانی دارند، مایکروسافت ابزاری را به نام Managed Profile Guided Optimization Tool یا MPGO .exe دارد. این ابزار به تحلیل اجرای برنامه شما پرداخته و بررسی میکند که در زمان آغازین برنامه چه چیزهایی نیاز است. اطلاعات به دست آمده از تحلیل به سمت ngen فرستاده شده تا کد بومی بهینهتری تولید گردد. موقعیکه شما آماده ارائه برنامه خود هستید، برنامه را از طریق این تحلیل و اجرا کرده و با قسمتهای اساسی برنامه کار کنید. با این کار اطلاعاتی در مورد اجرای برنامه در داخل یک پروفایل embed شده در اسمبلی، قرار گرفته و ngen موقع تولید کد، این پروفایل را جهت تولید کد بهینه مطالعه خواهد کرد.
در مقالهی بعدی در مورد FCL صحبتهایی خواهیم کرد.
Microsoft's Ryan Molden said : that disallowing non-Microsoft extensions is deliberate.
We don't prevent you from INSTALLING a package to Express ,since that just means 'writing some bits into the registry ', but it will never successfully load. As I mentioned in the other post, this is not a technological limitation, it is a business decision, one I disagree with, but I don't make these decisions
We don't prevent you from INSTALLING a package to Express ,since that just means 'writing some bits into the registry ', but it will never successfully load. As I mentioned in the other post, this is not a technological limitation, it is a business decision, one I disagree with, but I don't make these decisions