اولین پیش نویس HTTP 2.0 منتشر شد
«از این پس حین استفاده از انواع و اقسام لیستها، آرایهها، IEnumerableها و امثال آنها، جهت بررسی خالی بودن یا نبودن آنها تنها از متد Any فراهم شده توسط LINQ استفاده نمائید.»
اکنون پس از سالها، قصد داریم صحت این مساله را با NET 5.0. بررسی کنیم که آیا هنوز هم متد Any، بهترین متد بررسی خالی بودن مجموعهها و آرایههای NET 5.0. است یا خیر؟
نحوهی بررسی کارآیی روشهای مختلف خالی بودن مجموعهها و آرایهها در C# 9.0
در ابتدا یک لیست، یک Enumerable و یک آرایه را به صورت زیر مقدار دهی میکنیم و هر سهی اینها میتوانند نال هم باشند:
private IList<int>? _idsList; private IEnumerable<int>? _idsEnumerable; private int[]? _idsArray; [GlobalSetup] public void Setup() { _idsEnumerable = Enumerable.Range(0, 10000); _idsList = _idsEnumerable.ToList(); _idsArray = _idsEnumerable.ToArray(); }
اکنون که C# 9.0 در اختیار ما است به همراه pattern matching و همچنین Null Conditional Operator و غیره، میتوان روشهای زیر را برای بررسی خالی بودن این مجموعهها و آرایهها بکار گرفت:
1- استفاده از Null coalescing برای بررسی نال بودن مجموعه و سپس استفاده از متد Any برای بررسی خالی بودن آن:
var list = _idsList ?? new List<int>(); if (list.Any() is false) { }
2- استفاده از pattern matching برای بررسی نال بودن مجموعه و سپس استفاده از متد Any برای بررسی خالی بودن آن:
if (_idsList is null || _idsList.Any() is false) { }
3- استفاده از روش سنتی مقایسهی مستقیم با null و سپس استفاده از متد Any برای بررسی خالی بودن آن:
if (_idsList == null || _idsList.Any() is false) { }
4- استفاده از Null Conditional Operator برای بررسی نال بودن و سپس استفاده از متد Any برای بررسی خالی بودن آن:
if (_idsList?.Any() is false) { }
5- استفاده از pattern matching برای بررسی مقدار خاصیت Count یک لیست یا آرایه. البته Enumerableها به همراه این خاصیت نیستند و یا باید آنها را به لیست و یا آرایه تبدیل کرد و یا میتوان متد ()Count آنها را فراخوانی نمود:
if (_idsList is { Count: > 0 } is false) { }
6- استفاده از Null Conditional Operator برای بررسی نال بودن و سپس استفاده از مقدار خاصیت Count لیست، برای بررسی خالی بودن آن:
if (_idsList?.Count == 0) { }
7- استفاده از روش سنتی مقایسهی مستقیم با null و سپس استفاده از مقدار خاصیت Count لیست، برای بررسی خالی بودن آن:
if (_idsList == null || _idsList.Count == 0) { }
کدهای کامل این بررسی به صورت زیر هستند: AnyCountBenchmark.zip
ابتدا ارجاعی به BenchmarkDotNet به برنامه اضافه شدهاست:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net5.0</TargetFramework> <Nullable>enable</Nullable> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> </PropertyGroup> <ItemGroup> <PackageReference Include="BenchmarkDotNet" Version="0.12.1" /> </ItemGroup> </Project>
و سپس کدهای زیر، بررسی کارآیی روشهای مختلف تعیین خالی بودن مجموعهها را انجام میدهند:
using BenchmarkDotNet.Running; namespace AnyCountBenchmark { public static class Program { static void Main(string[] args) { #if DEBUG System.Console.WriteLine("Please set the project's configuration to Release mode first."); #else BenchmarkRunner.Run<Scenarios>(); #endif } } }
به همراه سناریوهای مختلف زیر:
using System; using System.Collections.Generic; using System.Linq; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Order; namespace AnyCountBenchmark { [SimpleJob(RuntimeMoniker.NetCoreApp50)] [Orderer(SummaryOrderPolicy.FastestToSlowest, MethodOrderPolicy.Declared)] [RankColumn] public class Scenarios { private IList<int>? _idsList; private IEnumerable<int>? _idsEnumerable; private int[]? _idsArray; [GlobalSetup] public void Setup() { _idsEnumerable = Enumerable.Range(0, 10000); _idsList = _idsEnumerable.ToList(); _idsArray = _idsEnumerable.ToArray(); } #region Any_With_Null_coalescing [Benchmark] public void List_Any_With_Null_coalescing() { var list = _idsList ?? new List<int>(); if (list.Any() is false) { } } [Benchmark] public void Array_Any_With_Null_coalescing() { var array = _idsArray ?? Array.Empty<int>(); if (array.Any() is false) { } } [Benchmark] public void Enumerable_Any_With_Null_coalescing() { var enumerable = _idsEnumerable ?? Enumerable.Empty<int>(); if (enumerable.Any() is false) { } } #endregion #region Any_With_Is_Null_Check [Benchmark] public void List_Any_With_Is_Null_Check() { if (_idsList is null || _idsList.Any() is false) { } } [Benchmark] public void Array_Any_With_Is_Null_Check() { if (_idsArray is null || _idsArray.Any() is false) { } } [Benchmark] public void Enumerable_Any_With_Is_Null_Check() { if (_idsEnumerable is null || _idsEnumerable.Any() is false) { } } #endregion #region Any_Any_With_Null_Equality_Check [Benchmark] public void List_Any_With_Null_Equality_Check() { if (_idsList == null || _idsList.Any() is false) { } } [Benchmark] public void Array_Any_With_Null_Equality_Check() { if (_idsArray == null || _idsArray.Any() is false) { } } [Benchmark] public void Enumerable_Any_With_Null_Equality_Check() { if (_idsEnumerable == null || _idsEnumerable.Any() is false) { } } #endregion #region Any_With_Null_Conditional_Operator [Benchmark] public void List_Any_With_Null_Conditional_Operator() { if (_idsList?.Any() is false) { } } [Benchmark] public void Array_Any_With_Null_Conditional_Operator() { if (_idsArray?.Any() is false) { } } [Benchmark] public void Enumerable_Any_With_Null_Conditional_Operator() { if (_idsEnumerable?.Any() is false) { } } #endregion #region Count_With_Pattern_Matching [Benchmark] public void List_Count_With_Pattern_Matching() { if (_idsList is { Count: > 0 } is false) { } } [Benchmark] public void Array_Length_With_Pattern_Matching() { if (_idsArray is { Length: > 0 } is false) { } } [Benchmark] public void Enumerable_Count_With_Pattern_Matching() { var list = _idsEnumerable?.ToList(); if (list is { Count: > 0 } is false) { } } #endregion #region Count_With_Null_Conditional_Operator [Benchmark] public void List_Count_With_Null_Conditional_Operator() { if (_idsList?.Count == 0) { } } [Benchmark] public void Array_Length_With_Null_Conditional_Operator() { if (_idsArray?.Length == 0) { } } [Benchmark] public void Enumerable_Count_With_Null_Conditional_Operator() { if (_idsEnumerable?.Count() == 0) { } } #endregion #region Count_With_Null_Equality_Check [Benchmark] public void List_Count_With_Null_Equality_Check() { if (_idsList == null || _idsList.Count == 0) { } } [Benchmark] public void Array_Length_With_Null_Equality_Check() { if (_idsArray == null || _idsArray.Length == 0) { } } [Benchmark] public void Enumerable_Count_With_Null_Equality_Check() { if (_idsEnumerable == null || _idsEnumerable.Count() == 0) { } } #endregion } }
نتایج حاصل:
- بررسی خالی بودن آرایهها، بسیار سریعتر از بررسی خالی بودن لیستها و این مورد نیز سریعتر از Enumerableها است.
- اگر از آرایهها و یا لیستها استفاده میکنید، بررسی خاصیت Length و یا خاصیت Count آنها، بسیار سریعتر از بکارگیری متد Any بر روی آنها است.
- اگر از Enumerableها استفاده میکنید، استفاده از متد Any بر روی آنها، بسیار سریعتر از بکارگیری متد ()Count و یا تبدیل آنها به لیست و سپس بررسی خاصیت Count آنها است.
- بررسی نال بودن با pattern matching یا همان is null، نسبت به روشی سنتی استفادهی از null ==، سریعتر است. علت آنرا در مطلب «روش ترجیح داده شدهی مقایسه مقادیر اشیاء با null از زمان C# 7.0 به بعد» میتوانید مطالعه کنید.
بنابراین برای بررسی خالی بودن آرایهها و لیستها، بهتر است از خاصیت Length و یا Count آنها استفاده کرد و برای Enumerableها از متد ()Any.
public class MyEntity { public Guid Id {get;set;} public string[] Strings { get; set; } }
modelBuilder.Entity<MyEntity>() .Id(e=> e.Id) .Property(e => e.Strings) .HasConversion( v => string.Join(',', v), v => v.Split(',', StringSplitOptions.RemoveEmptyEntries));
تبدیلگر ایران سیستم به یونیکد
string ascii = Encoding.ASCII.GetString(list.ToArray());
آشنایی با DGML
یکی از قابلیتهای جدید VS2010 ، معرفی یک markup language است به نام DGML که از آن جهت data visualization و رسم انواع و اقسام گرافها استفاده میشود.
چند دموی جالب از آن در این سایت قابل دریافت است (اگر موفق به باز کردن سایت شدید که چقدر هم خوب، اگر نه، mirror این دموها از آدرسهای زیر هم قابل دریافت است):
مقدمه :
نوع دادهها، اجزای اصلی سازندهی یک زبان برنامه نویسی و شبیه قواعد هر زبانی هستند.
مفاهیمی که در این مطلب بررسی خواهد شد :
• Data Type نوع داده
• Variables متغیرها
• Naming Convention قراردادهای نامگذاری
• Value Type/Reference Type انواع مقداری و ارجاعی
• Stack/heap memory حافظه پشته و هرم
نوع داده
متغیرها
چک کردن سایز نوع داده (Data Type)
Console.WriteLine(sizeof(int)); Console.WriteLine(sizeof(char)); Console.WriteLine(sizeof(bool)); Console.WriteLine(sizeof(decimal)); Console.WriteLine(sizeof(float));
4 2 1 16 4
نکته : متد sizeof فقط برای نمایش اندازهی نوع دادههای مقداری (value type) میتواند مورد استفاده قرار گیرد.
چک کردن نوع داده
ما میتوانیم نوع دادهها را برای بدست آوردن کلاسی که به آن تعلق دارند، چک کنیم.
مثال :
int a = 23; float b = 3.14f; Console.WriteLine(a.GetType()); Console.WriteLine(b.GetType());
System.Int32 System.Single
چک کردن نوع دادهی دو شیء
فرض کنید 2 شیء را با نامهای obj1 و obj2 داریم که هر دو از نوع long هستند. برای اینکه این مقایسه را انجام دهیم، از متد Object.RefrenceEqual میتوان استفاده کرد.
مثال :
long obj1 = 356; long obj2 = 54; float obj3 = 234; Console.WriteLine(object.ReferenceEquals(obj1.GetType(), obj2.GetType())); Console.WriteLine(object.ReferenceEquals(obj1.GetType(), obj3.GetType()));
True False
تعریف یک متغیر ومقدار دهی به آن
برای استفاده از یک متغیر ابتداباید آن را تعریف کنیم :
//<data type> <variable name>; Int a;
مقداردهی اولیه یک متغیر
مقدار دهی اولیهی یک متغیر با استفاده از عملگر = و نوشتن مقدار مورد نظر برای ذخیره کردن در متغیر، در سمت راست عملگر اتفاق خواهد افتاد.
//<data type> <variable name>=value; Int a=23; Int a;//declare تعریف a=23;//مقدار دهی اولیه initializing Int a=23;//تعریف و مقدار دهی در یک خط Int a,b,c=23;//تعریف چند متغیر و مقدار دهی در یک خط
قرار دادهای نام گذاری متغیرها :
در دنیای برنامه نویسی دو نوع قرار داد نام گذاری بسیار متداول وجود دارند:
1- camelCase : در این قرار داد، حرف اول کلمهی اول، بصورت کوچک و حرف اول از کلمهی دوم، بصورت بزرگ نوشته خواهد شد. برای مثال: firstName,lastName
2- PascalCase : در این قرار داد حروف ابتدایی دو کلمهی مجاور، بصورت بزرگ نوشته خواهند شد: FirstName,LastName
چند نکته :
• نامگذاری متغیرها را میتوانید با علامت _ و یا @ شروع کنید.
• کلمات کلیدی (key word) سی شارپ نمیتوانند به عنوان نام متغیر مورد استفاده قرار بگیرند (مگر آنکه با @ شروع شوند).
• در بین نام متغیر نباید فضای خالی وجود داشته باشد. کاراکترهای سازندهی متغیر میتوانند اعداد، حروف و زیر خط باشند.
لیستی از نام گذاریهای مجاز:
int abc; long _abcd; float @abcd; bool main_button; decimal piValue; string firstName; string first_name; bool button55_on;
long _a.5bc5d; float @ab cd; decimal pi@Value; //استفاده از کلمات کلیدی سی شارپ که کامپایلر آنها را مجاز نمیداند bool class; string namespace; string string; int static;
در ادامه کمی در مورد نوع دادهها بحث خواهیم کرد.
در سی شارپ دو مدل نوع داده وجود دارد:
• انواع مقداری Value Type
• انواع ارجاعی یا اشارهای Reference Type
انواع مقداری (Value Type) :
• انواع مقداری مستقیما حاوی دادهها هستند. اگر یک متغیر از نوع مقداری را به یک متغیر دیگر تخصیص دهید، مقدار آنها مستقیما کپی میشوند؛ برعکس نوعهای اشارهای که با نخصیص یک متغیر به یک متغیر دیگر، تنها اشارهگر به مقدار شیء کپی خواهد شد و نه خود شیء.
• کلیه نوعهای مقداری از کلاس ValueType مشتق شدهاند.
• در فضای stack به آنها حافظه تخصیص داده میشود.
• نمیتوانند مقدار null بپذیرند. البته با قابلیت nullabletype امکان تخصیص مقدار null به نوع دادههای مقداری نیز مهیا شده است.
• همه نوعهای دادههای مقداری، یک سازنده پیش فرض دارند که به صورت ضمنی کار مقدار دهی اولیه برای آنها را انجام میدهد. برای مطالعه بیشتر درباره مقادیر پیش فرض به اینجا مراجعه کنید.
انواع مقداری به دو دستهی اصلی تقسیم میشود :
• Structs
• Enumerations
طبقه بندی Structs به صورت زیر است :
• Numeric Type
* Integral Type : sbyte,short,ushort,int,uint,long,ulong,char
* Floating-Point Types : float,double
* Decimal : decimal
• Bool دو مقدار true و false
• User Defined Struct
نوع داده نال (تهی) پذیر (nullable Type) و چگونگی تعریف آن
< data type >? < variable name >= null; //syntax
int? a = null; //assigning null int? b = 55; //assigning null and a value var? c = 55 //it will give error
نکته : var نمیتواند بصورت nullable تعریف شود.
برای چک کردن مقدار در انواع تهی پذیر (nullable) دو خصوصیت وجود دارد:
• HasValue
اگر مقداری در متغیر وجود داشته باشد ارزش true بازگردانده میشود؛ در غیر اینصورت ارزش false
• Value
مقدار واقعی متغیر را باز میگرداند.
مثال :
int? a = null; int? b = 22; Console.WriteLine(a.HasValue); //------------ Console.WriteLine(b.HasValue); Console.WriteLine(b.Value);
False True 22
انواع ارجاعی Reference Type
انواع ارجاعی مستقیما حاوی اطلاعات نیستند و ارجاعی هستند به آدرسی از حافظه که حاوی اطلاعات واقعی است. به بیانی دیگر، اشارهگری به آدرسی از حافظه هستند.
• انواع ارجاعی بصورت غیر مستقیم حاوی دادهها هستند.
• در بخشی از حافظه که به آن heap میگوییم، به آنها فضا اختصاص داده میشود.
• میتوانند بصورت null (بدون مقدار) باشند.
انواع ارجاعی نیز به دو دستهی کلی تقسیم میشوند :
• انواع از پیش تعریف شده
Object,string,dynamic
• انواع تعریف شده توسط کاربر
class,interface,delegate
نکته : آدرس مکانی از حافظه که دادهها در آن قرار دارند، در بخش پشته یا Stack ذخیره میشود و دادهها در فضای heap ذخیره میشوند.
مثال :
test obj; //allocating reference on stack obj= new test(55);//allocating object on heap
نکته : دو متغیر از نوع ارجاعی میتوانند به یک آدرس از حافظه اشاره کنند. در شکل زیر این موضوع نشان داده شده است.
در شکل زیر طبقه بندی نوع دادهها در سی شارپ نشان داده شده است :
وقتی از یک متغیر مقداری را به یک متغیر دیگر تخصیص میدهیم، یک کپی جدید از آن در فضای stack ایجاد میشود. بدین معنی که محتوای دو متغیر یکسان هستند، ولی در دو بخش مجزای در حافظهی Stack قرار دارند. به همین خاطر تغییر محتوای یک متغیر، محتوای متغیر دیگر را تغییر نمیدهد.
مثال :
int a = 55;//declare a and initialize int copya = a;//copya contains the copy of value a
• عملیات کپی، در نوع دادهی ارجاعی
test obj; obj=new test(23); test objCopy; objCopy = obj;
دیاگرام حافظهی قطعه کد بالا به شکل زیر است :
تخصیص حافظه در بخش Stack و Heap به متغیرها
سیستم عامل و net CLR. حافظه را به دو بخش stack و heap تقسیم بندی میکنند.
گام سوم: افزودن یک button
... <div> <div> <input type="text" id="inputName" maxlength="15"> </div> <div> <button id="generateButton">Aye! Gimme a name!</button> </div> </div> ...
import 'dart:html'; ButtonElement genButton;
void main() { querySelector('#inputName').onInput.listen(updateBadge); genButton = querySelector('#generateButton'); genButton.onClick.listen(generateBadge); }
... void setBadgeName(String newName) { querySelector('#badgeName').text = newName; }
... void generateBadge(Event e) { setBadgeName('Meysam Khoshbakht'); }
void updateBadge(Event e) { String inputName = (e.target as InputElement).value; setBadgeName(inputName); }
void updateBadge(Event e) { String inputName = (e.target as InputElement).value; setBadgeName(inputName); if (inputName.trim().isEmpty) { // To do: add some code here. } else { // To do: add some code here. } }
void updateBadge(Event e) { String inputName = (e.target as InputElement).value; setBadgeName(inputName); if (inputName.trim().isEmpty) { genButton..disabled = false ..text = 'Aye! Gimme a name!'; } else { genButton..disabled = true ..text = 'Arrr! Write yer name!'; } }
گام چهارم: ایجاد کلاس PirateName
در این مرحله فقط کد مربوط به فایل dart را تغییر میدهیم. ابتدا کلاس PirateName را ایجاد میکنیم. با ایجاد نمونه ای از این کلاس، یک نام بصورت تصادفی انتخاب میشود و یا نامی بصورت اختیاری از طریق سازنده انتخاب میگردد.
نخست کتابخانه dart:math را به ابتدای فایل dart اضافه کنید
import 'dart:html'; import 'dart:math' show Random;
توضیحات
- با استفاده از کلمه کلیدی show، شما میتوانید فقط کلاسها، توابع و یا ویژگیهای مورد نیازتان را import کنید.
- کلاس Random یک عدد تصادفی را تولید میکند
در انتهای فایل کلاس زیر را تعریف کنید
... class PirateName { }
در داخل کلاس یک شی از کلاس Random ایجاد کنید
class PirateName { static final Random indexGen = new Random(); }
توضیحات
- با استفاده از static یک فیلد را در سطح کلاس تعریف میکنیم که بین تمامی نمونههای ایجاد شده از کلاس مشترک میباشد
- متغیرهای final فقط خواندنی میباشند و غیر قابل تغییر هستند.
- با استفاده از new میتوانیم سازنده ای را فراخوانی نموده و نمونه ای را از کلاس ایجاد کنیم
دو فیلد دیگر از نوع String و با نامهای _firstName و _appelation به کلاس اضافه میکنیم
class PirateName { static final Random indexGen = new Random(); String _firstName; String _appellation; }
متغیرهای خصوصی با (_) تعریف میشوند. Dart کلمه کلیدی private را ندارد.
دو لیست static به کلاس فوق اضافه میکنیم که شامل لیستی از name و appelation میباشد که میخواهیم آیتمی را بصورت تصادفی از آنها انتخاب کنیم.
class PirateName { ... static final List names = [ 'Anne', 'Mary', 'Jack', 'Morgan', 'Roger', 'Bill', 'Ragnar', 'Ed', 'John', 'Jane' ]; static final List appellations = [ 'Jackal', 'King', 'Red', 'Stalwart', 'Axe', 'Young', 'Brave', 'Eager', 'Wily', 'Zesty']; }
کلاس List میتواند شامل مجموعه ای از آیتمها میباشد که در Dart تعریف شده است.
سازنده ای را بصورت زیر به کلاس اضافه میکنیم
class PirateName { ... PirateName({String firstName, String appellation}) { if (firstName == null) { _firstName = names[indexGen.nextInt(names.length)]; } else { _firstName = firstName; } if (appellation == null) { _appellation = appellations[indexGen.nextInt(appellations.length)]; } else { _appellation = appellation; } } }
توضیحات
- سازنده تابعی همنام کلاس میباشد
- پارامترهایی که در {} تعریف میشوند اختیاری و Named Parameter میباشند. Named Parameterها پارمترهایی هستند که جهت مقداردهی به آنها در زمان فراخوانی، از نام آنها استفاده میشود.
- تابع nextInt() یک عدد صحیح تصادفی جدید را تولید میکند.
- جهت دسترسی به عناصر لیست از [] و شمارهی خانهی لیست استفاده میکنیم.
- ویژگی length تعداد آیتمهای موجود در لیست را بر میگرداند.
در این مرحله یک getter برای دسترسی به pirate name ایجاد میکنیم
class PirateName { ... String get pirateName => _firstName.isEmpty ? '' : '$_firstName the $_appellation'; }
توضیحات
- Getterها متدهای خاصی جهت دسترسی به یک ویژگی به منظور خواندن مقدار آنها میباشند.
- عملگر سه گانه :? دستور میانبر عبارت شرطی if-else میباشد
- $ یک کاراکتر ویژه برای رشتههای موجود در Dart میباشد و میتواند محتوای یک متغیر یا ویژگی را در رشته قرار دهد. در رشته '$_firstName the $_appellation' محتوای دو ویژگی _firstName و _appellation در رشته قرار گرفته و نمایش مییابند.
- عبارت (=> expr;) یک دستور میانبر برای { return expr; } میباشد.
تابع setBadgeName را بصورت زیر تغییر دهید تا یک پارامتر از نوع کلاس PirateName را به عنوان پارامتر ورودی دریافت نموده و با استفاده از Getter مربوط به ویژگی pirateName، مقدار آن را در badge name نمایش دهد.
void setBadgeName(PirateName newName) { querySelector('#badgeName').text = newName.pirateName; }
تابع updateBadge را بصورت زیر تغییر دهید تا یک نمونه از کلاس PirateName را با توجه به مقدار ورودی کاربر در فیلد input تولید نموده و تابع setBadgeName رافراخوانی نماید. همانطور که در کد مشاهده میکنید پارامتر ورودی اختیاری firstName در زمان فراخوانی با ذکر نام پارامتر قبل از مقدار ارسالی نوشته شده است. این همان قابلیت Named Parameter میباشد.
void updateBadge(Event e) { String inputName = (e.target as InputElement).value; setBadgeName(new PirateName(firstName: inputName)); ... }
تابع generateBadge را بصورت زیر تغییر دهید تا به جای نام ثابت Meysam Khoshbakht، از کلاس PirateName به منظور ایجاد نام استفاده کند. همانطور که در کد میبینید، سازندهی بدون پارامتر کلاس PirateName فراخوانی شده است.
void generateBadge(Event e) { setBadgeName(new PirateName()); }
همانند گام سوم برنامه را اجرا کنید و نتیجه را مشاهده نمایید.