مطالب
Generators در ES 6
Generators در حقیقت نوعی Iterator هستند. آن‌ها نوع خاصی از توابع هستند که قابلیت تعلیق و از سرگیری مجدد را دارند. برای رسیدن به این هدف، اینبار تعریف function به صورت *function خواهد بود و در آن برای بازگشت مقادیر، از واژه‌ی کلیدی yield استفاده می‌شود.
یک نمونه مثال ابتدایی از Generators را در کدهای زیر مشاهده می‌کنید:
function* generator () {
   yield 1;
   // pause
   yield 2;
   // pause
   yield 3;
   // pause
   yield 'done?';
   // done
}
این متد خاص با یک ستاره پس از نام function مشخص شده‌است و همچنین برای بازگشت مقادیر از واژه‌ی کلیدی yield استفاده می‌کند. روش فراخوانی دستی آن نیز به صورت زیر است:
 let gen = generator(); // [object Generator]
console.log(gen.next()); // Object {value: 1, done: false}
console.log(gen.next()); // Object {value: 2, done: false}
console.log(gen.next()); // Object {value: 3, done: false}
console.log(gen.next()); // Object {value: 'done?', done: false}
console.log(gen.next()); // Object {value: undefined, done: true}
console.log(gen.next()); // Object {value: undefined, done: true}
همانطور که مشاهده می‌کنید، همانند Iterators، هر بار که متد next آن‌ها فراخوانی می‌شود، شیءایی را با خواص value و done بازگشت می‌دهند. هر زمانیکه done مساوی true شد، یعنی کار آن به پایان رسیده‌است.
و یا می‌توان بجای فراخوانی دستی متد next، از حلقه‌ی جدید for of نیز برای کار با آن‌ها استفاده کرد:
for (let val of generator()) {
   console.log(val); // 1
   // 2
   // 3
   // 'done?'
}
امکان ترکیب Generators نیز وجود دارد:
function* random (max) {
   yield Math.floor(Math.random() * max) + 1;
}

function* random1_20 () {
   while (true) {
      yield* random(20);
   }
}

let rand = random1_20();
console.log(rand.next());
console.log(rand.next());
در اینجا یک متد Generator به نام random1_20، به تعداد نامتناهی اعداد اتفاقی بین 1 تا 20 را بازگشت می‌دهد و در این بین، yield آن خود نیز یک Generator دیگر است.
فقط در این حالت بجای yield معمولی از *yield استفاده می‌شود. از *yield برای کار با هر نوع Iterator ایی می‌توان استفاده کرد:
function* multiplier (value) {
  yield value * 2;
  yield value * 3;
  yield value * 4;
  yield value * 5;
}
function* trailmix () {
  yield 0;
  yield* [1, 2];
  yield* [...multiplier(2)];
  yield* multiplier(3);
}
در این مثال از yield معمولی برای بازگشت اعداد و از *yiled برای کار با انواع و اقسام Iterators از آرایه‌ها گرفته تا spread operator، استفاده شده‌است.


کاهش مصرف حافظه‌ی برنامه با استفاده از Generators

در مثال زیر، قرار است لیستی از rows بازگشت داده شود. در اینجا یک آرایه تشکیل شده و هربار اطلاعاتی به آن push می‌شود و در نهایت این آرایه بازگشت داده خواهد شد:
function splitIntoRows(icons, rowLength) {
   var rows = [];
   for (var i = 0; i < icons.length; i += rowLength) {
       rows.push(icons.slice(i, i + rowLength));
   }
   return rows;
}
اما با استفاده از Generators دیگر نیازی نیست تا یک آرایه برای جمع آوری این لیست تشکیل شود و به این ترتیب مصرف حافظه‌ی برنامه کاهش خواهد یافت و همچنین اینبار این خروجی می‌تواند نامتنهاهی باشد (کاری که با استفاده از آرایه‌های معمولی قابل انجام نیست):
 function* splitIntoRows(icons, rowLength) {
   for (var i = 0; i < icons.length; i += rowLength) {
      yield icons.slice(i, i + rowLength);
  }
}

روش‌هایی برای خاموش کردن Generators

Generators علاوه بر متد next، دارای متدهای return و throw نیز هستند.
فراخوانی (generator.throw(error همانند این است که در بین کار، به متدی برخورده‌ایم که استثنایی را صادر کرده‌است و سبب خاتمه‌ی کار Generator شده‌است.
اگر متد return آن‌ها فراخوانی شود، کار Generator پایان می‌یابد (خاصیت done شیء بازگشتی بلافاصله true می‌شود) و فراخوانی next، پس از آن، دیگر اثری نخواهد داشت:
function* numbers () {
  yield 1
  yield 2
  yield 3
}

var g = numbers()
console.log(g.next())
// <- { done: false, value: 1 }
console.log(g.return())
// <- { done: true }
console.log(g.next())
// <- { done: true }, as we know
مشابه آن حالتی است که در بین yieldها یک return وجود داشته باشد:
function* numbers () {
  yield 1
  yield 2
  return 3 
  yield 4
}

console.log([...numbers()])
// <- [1, 2]
در این حالت نیز return سبب خاتمه‌ی Generator شده‌است.

اگر به متد return خود Generator، پارامتری ارسال شود، این مقدار، مقدار نهایی بازگشت داده شده خواهد بود:
function* numbers () {
  yield 1
  yield 2
  return 3
  yield 4
}

var g = numbers()
console.log(g.next())
// <- { done: false, value: 1 }
console.log(g.return(5))
// <- { done: true, value: 5 }
console.log(g.next())
// <- { done: true }
در این مثال (g.return(5 سبب خاتمه‌ی Generator شده‌است و همچنین مقدار نهایی آن‌را نیز تعیین کرده‌است (عدد 5 بجای عدد 2).
در حالتیکه به متد return پارامتری ارسال می‌شود، قسمت finally بدنه‌ی try/finally نوشته شده حتما اجرا خواهد شد و سپس کار خاتمه پیدا می‌کند:
function* numbers () {
  yield 1
  try {
       yield 2
  } finally {
       yield 3
       yield 4
  }
  yield 5
}

var g = numbers()
console.log(g.next())
// <- { done: false, value: 1 }
console.log(g.next())
// <- { done: false, value: 2 }
console.log(g.return(6))
// <- { done: false, value: 3 }
console.log(g.next())
// <- { done: false, value: 4 }
console.log(g.next())
// <- { done: true, value: 6 }
در این مثال (g.return(6 پس از yield 2 فراخوانی شده‌است. اما همانطور که مشاهده می‌کنید، بدنه‌ی finally کاملا اجرا شده و سپس Generator با عدد 6 خاتمه یافته‌است.
مطالب
تنظیم رشته اتصالی Entity Framework به بانک اطلاعاتی به وسیله کد
در زمان ساخت مدل از بانک اطلاعاتی در روش Database First به صورت پیش فرض تنظیمات مربوط به اتصال (Connection String) مدل به بانک اطلاعاتی در فایل config برنامه ذخیره می‌شود. مشکل این روش آن است که در سیستم‌های مختلف، بسته به بستری که نرم افزار قرار است بر روی آن اجرا شود، باید تنظیمات مربوط به بانک اطلاعاتی صورت گیرد.
مثلا فرض کنید شما در زمان توسعه نرم افزار، SQL Server را به صورت Local بر روی سیستم خود نصب کرده اید و Connection String ساخته شده توسط ویزارد Entity Framework بر همین اساس ساخته و ذخیره شده‌است. حال بعد از انتشار برنامه، شخصی تصمیم دارد برنامه را بر روی سیستمی نصب کند که بانک اطلاعاتی Local نداشته و تصمیم به اتصال به یک بانک اطلاعاتی بر روی سرور دیگر یا با مشخصات (Login و Password و ...) دیگر را دارد. برای این مواقع نیاز به پیاده سازی روشی است تا کاربر نهایی بتواند تنظیمات مربوط به اتصال به بانک اطلاعاتی را تغییر دهد.
روش‌های مختلفی مثل تغییر فایل app.config به صورت Runtime یا ... در سایت‌های مختلف ارائه شده که اکثرا روش‌های غیر اصولی و زمانبری جهت پیاده سازی هستند.
ساده‌ترین روش جهت انجام این کار، اعمال تغییری کوچک در Constructor کلاس مدل مشتق شده از DBContext می‌باشد. فرض کنید مدلی از بانک اطلاعاتی Personnely با نام PersonallyEntities ساخته اید که حاصل آن کلاس زیر خواهد بود:
    public partial class PersonallyEntities : DbContext
    {
        public PersonallyEntities()
            : base("name=PersonallyEntities")
        {
        }
    }
همانطور که مشاهده می‌کنید، در Constructor این کلاس، نام Connection String مورد استفاده جهت اتصال به بانک اطلاعاتی به صورت زیر آورده شده که به Connection String ذخیره شده در فایل Config اشاره می‌کند:
"name=PersonallyEntities"
اگر به Connection String ذخیره شده در فایل Config دقت کنید متوجه می‌شوید که Connection String ذخیره شده، دارای فرمتی خاص و متفاوتی نسبت به Connection String معمولی ADO.NET است. متن ذخیره شده شامل تنظیمات و Metadata مدل ساخته شده جهت ارتباط با بانک اطلاعاتی نیز می‌باشد:
 metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;provider connection string="data source=.;initial catalog=Personally;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"
جهت تولید پویای Connection String، بسته به تنظیمات کاربر، نیاز است تا در آخر Connection String ی با فرمت بالا در اختیار Entity Framework قرار دهیم تا امکان اتصال به بانک فراهم شود. جهت تبدیل Connection String معمول ADO.NET به Connection String قابل فهم EF میتوان از کلاس EntityConnectionStringBuilder به صورت زیر استفاده کرد:
        public static string BuildEntityConnection(string connectionString)
        {
            var entityConnection = new EntityConnectionStringBuilder
            {
                Provider = "System.Data.SqlClient",
                ProviderConnectionString = connectionString,
                Metadata = "res://*"
            };

            return entityConnection.ToString();
        }
همانطور که مشاهده می‌کنید، متد بالا با دریافت یک connectionString که همان ADO.NET ConnectionString ما می‌باشد، تنظیمات و Metadata مورد نیاز Entity Framework را به آن اضافه کرده و یک EF ConnectionString برمی‌گرداند.
برای اینکه بتوان EF ConnectionString تولید شده را در هنگام اجرای برنامه به صورت Runtime اعمال کرد، نیاز است تا تغییر کوچکی در Constructor کلاس مدل تولید شده توسط Entity Framework ایجاد کرد. کلاس PersonnelyEntities به صورت زیر تغییر پیدا می‌کند:

    public partial class PersonallyEntities : DbContext
    {
        public PersonallyEntities(string connectionString)
            : base(connectionString)
        {

        }
    }
با اضافه شدن پارامتر connectionString به سازنده کلاس PersonnelyEntities برای ساخت یک نمونه از مدل ساخته شده در کد نیاز است تا Connection String مورد نظر جهت برقراری ارتباط با بانک را به عنوان پارامتر، به متد سازنده پاس دهیم. سپس مقدار این پارامتر به کلاس والد ( DbContext ) جهت برقراری ارتباط با بانک اطلاعاتی ارجاع داده شده: 
: base(connectionString)
در آخر به صورت زیر میتوان توسط EF به بانک اطلاعاتی مورد نظر متصل شد :
var entityConnectionString = BuildeEntityConnection("Data Source=localhost;Initial Catalog=Personally; Integrated Security=True");
var PersonallyDb = new PersonallyEntities(entityConnectionString);
با این روش میتوان ADO Connection String مربوط به اتصال بانک اطلاعاتی را به راحتی به صورت داینامیک به وسیله اطلاعات وارد شده توسط کاربر و کلاس‌های تولید Connection String نظیر SQLConnectionStringBuilder تولید کرد و بدون تغییر در کد‌های برنامه، به بانک‌های مختلفی متصل شد. همچنین با داینامیک کردن متد Provider کلاس EntityConnectionStringBuilder که در کد بالا با "System.Data.SqlClient" مقدار دهی شده، می‌توان وابستگی برنامه بانک اطلاعی خاص را از بین برد و بسته به تنظیمات مورد نظر کاربر، به موتورهای مختلف بانک اطلاعاتی متصل شد که البته لازمه این کار رعایت یکسری نکات فنی در پیاده سازی پروژه است که از حوصله این مقاله خارج است.
موفق باشید
نظرات مطالب
C# 6 - Null-conditional operators
با سلام
برای چک کردن مقادیر نال پی در پی واقعا کاربردی است
البته موردی که ابتدای مطلب اومده اشکال کوچکی دارد :
string data = null;
var result = data ?? "value";
و
if (data == null)
{
    data = "value";
}
var result = data;
یکی نیستند چون در کد دوم مقدار data تغییر می‌کند(در صورتیکه برابر نال باشد).
مطالب
MVC Scaffolding #2
از آنجائیکه اصل کار با MVC Scaffolding از طریق خط فرمان پاورشل انجام می‌شود، بنابراین بهتر است در ادامه با گزینه‌ها و سوئیچ‌های مرتبط با آن بیشتر آشنا شویم.
دو نوع پارامتر حین کار با MVC Scaffolding مهیا هستند:

الف) سوئیچ‌ها
مانند پارامترهای boolean عمل کرده و شامل موارد ذیل می‌باشند. تمام این پارامترها به صورت پیش فرض دارای مقدار false بوده و ذکر هرکدام در دستور نهایی سبب true شدن مقدار آن‌ها می‌گردد:
Repository: برای تولید کدها بر اساس الگوی مخزن
Force: برای بازنویسی فایل‌های موجود.
ReferenceScriptLibraries: ارجاعاتی را به اسکریپت‌های موجود در پوشه Scripts، اضافه می‌کند.
NoChildItems: در این حالت فقط کلاس کنترلر تولید می‌شود و از سایر ملحقات مانند تولید Viewها، DbContext و غیره صرفنظر خواهد شد.

ب) رشته‌ها
این نوع پارامترها، رشته‌ای را به عنوان ورودی خود دریافت می‌کنند و شامل موارد ذیل هستند:
ControllerName: جهت مشخص سازی نام کنترلر مورد نظر
ModelType: برای ذکر صریح کلاس مورد استفاده در تشکیل کنترلر بکار می‌رود. اگر ذکر نشود، از نام کنترلر حدس زده خواهد شد.
DbContext: نام کلاس DbContext تولیدی را مشخص می‌کند. اگر ذکر نشود از نامی مانند ProjectNameContex استفاده خواهد کرد.
Project: پیش فرض آن پروژه جاری است یا اینکه می‌توان پروژه دیگری را برای قرار دادن فایل‌های تولیدی مشخص کرد. (برای مثال هربار یک سری کد مقدماتی را در یک پروژه جانبی تولید کرد و سپس موارد مورد نیاز را از آن به پروژه اصلی افزود)
CodeLanguage: می‌تواند cs یا vb باشد. پیش فرض آن زبان جاری پروژه است.
Area: اگر می‌خواهید کدهای تولیدی در یک ASP.NET MVC area مشخص قرار گیرند، نام Area مشخصی را در اینجا ذکر کنید.
Layout: در حالت پیش فرض از فایل layout اصلی استفاده خواهد شد. اما اگر نیاز است از layout دیگری استفاده شود، مسیر نسبی کامل آن‌را در اینجا قید نمائید.

یک نکته:
نیازی به حفظ کردن هیچکدام از موارد فوق نیست. برای مثال در خط فرمان پاورشل، دستور Scaffold را نوشته و پس از یک فاصله، دکمه Tab را فشار دهید. لیست پارامترهای قابل اجرای در این حالت ظاهر خواهند شد. اگر در اینجا برای نمونه Controller انتخاب شود، مجددا با ورود یک فاصله و خط تیره و سپس فشردن دکمه Tab، لیست پارامترهای مجاز و همراه با سوئیچ کنترلر ظاهر می‌گردند.


MVC Scaffolding و مدیریت روابط بین کلاس‌ها

مثال قسمت قبلی بسیار ساده و شامل یک کلاس بود. اگر آن‌را کمی پیچیده‌تر کرده و برای مثال روابط one-to-many و many-to-many را اضافه کنیم چطور؟
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcApplication1.Models
{
    public class Task
    {
        public int Id { set; get; }

        [Required]
        public string Name { set; get; }

        [DisplayName("Due Date")]
        public DateTime? DueDate { set; get; }

        [ForeignKey("StatusId")]
        public virtual Status Status { set; get; } // one-to-many
        public int StatusId { set; get; }

        [StringLength(450)]
        public string Description { set; get; }

        public virtual ICollection<Tag> Tags { set; get; } // many-to-many
    }

    public class Tag
    {
        public int Id { set; get; }

        [Required]
        public string Name { set; get; }

        public virtual ICollection<Task> Tasks { set; get; } // many-to-many
    }

    public class Status
    {
        public int Id { set; get; }

        [Required]
        public string Name { set; get; }
    }
}
کلاس Task تعریف شده اینبار دارای رابطه many-to-many با برچسب‌های مرتبط با آن است. همچنین یک رابطه one-to-many با کلاس وضعیت هر Task نیز تعریف شده است. به علاوه نکته تعریف «کار با کلیدهای اصلی و خارجی در EF Code first» نیز در اینجا لحاظ گردیده است.
در ادامه دستور تولید کنترلر‌های Task، Tag و Status ساخته شده با الگوی مخزن را در خط فرمان پاورشل ویژوال استودیو صادر می‌کنیم:
PM> Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository -Force
PM> Scaffold Controller -ModelType Tag -ControllerName TagsController -DbContextType TasksDbContext -Repository -Force
PM> Scaffold Controller -ModelType Status -ControllerName StatusController -DbContextType TasksDbContext -Repository -Force
اگر به کارهایی که در اینجا انجام می‌شود دقت کنیم، می‌توان صرفه جویی زمانی قابل توجهی را شاهد بود؛ خصوصا در برنامه‌هایی که از ده‌ها فرم ورود اطلاعات تشکیل شده‌اند. فرض کنید قصد استفاده از ابزار فوق را نداشته باشیم. باید به ازای هر عملیات CRUD دو متد را ایجاد کنیم. یکی برای نمایش و دیگری برای ثبت. بعد بر روی هر متد کلیک راست کرده و Viewهای متناظری را ایجاد کنیم. سپس مجددا یک سری پیاده سازی «مقدماتی» تکراری را به ازای هر متد جهت ثبت یا ذخیره اطلاعات تدارک ببینیم. اما در اینجا پس از طراحی کلاس‌های برنامه، با یک دستور، حجم قابل توجهی از کدهای «مقدماتی» که بعدها مطابق نیاز ما سفارشی سازی و غنی‌تر خواهند شد، تولید می‌گردند.

چند نکته:
- با توجه به اینکه مدل‌ها تغییر کرده‌اند، نیاز است بانک اطلاعاتی متناظر نیز به روز گردد. مطالب مرتبط با آن‌را در مباحث Migrations می‌توانید مطالعه نمائید.
- View تولیدی رابطه many-to-many را پشتیبانی نمی‌کند. این مورد را باید دستی اضافه و طراحی کنید: (^ و ^)
- رابطه one-to-many به خوبی با View متناظری دارای یک drop down list تولید خواهد شد. در اینجا لیست تولیدی به صورت خودکار با مقادیر خاصیت Name کلاس Status پر می‌شود. اگر این نام دقیقا Name نباشد نیاز است توسط ویژگی به نام DisplayColumn که بر روی نام کلاس قرار می‌گیرد، مشخص کنید از کدام خاصیت باید استفاده شود.
@Html.DropDownListFor(model => model.StatusId,
((IEnumerable<Status>)ViewBag.PossibleStatus).Select(option => new SelectListItem {
  Text = (option == null ? "None" : option.Name),
  Value = option.Id.ToString(),
  Selected = (Model != null) && (option.Id == Model.StatusId)
}), "Choose...")
@Html.ValidationMessageFor(model => model.StatusId)


تولید آزمون‌های واحد به کمک MVC Scaffolding

MVC Scaffolding امکان تولید خودکار کلاس‌ها و متدهای آزمون واحد را نیز دارد. برای این منظور دستور زیر را در خط فرمان پاورشل وارد نمائید:
 PM> Scaffold MvcScaffolding.ActionWithUnitTest -Controller TasksController -Action ArchiveTask -ViewModel Task
دستوری که در اینجا صادر شده است نسبت به حالت‌های کلی قبلی، اندکی اختصاصی‌تر است. این دستور بر روی کنترلری به نام TasksController، جهت ایجاد اکشن متدی به نام ArchiveTask با استفاده از کلاس ViewModel ایی به نام Task اجرا می‌شود. حاصل آن ایجاد اکشن متد یاد شده به همراه کلاس TasksControllerTest است؛ البته اگر حین ایجاد پروژه جدید در ابتدای کار، گزینه ایجاد پروژه آزمون‌های واحد را نیز انتخاب کرده باشید. نام پروژه پیش فرضی که جستجوی می‌شود YourMvcProjectName.Test/Tests است.
 نکته مهم آن، عدم حذف یا بازنویسی کامل کنترلر یاد شده است. کاری هم که در تولید متد آزمون واحد متناظر انجام می‌شود، تولید بدنه متد آزمون واحد به همراه تولید کدهای اولیه الگوی Arrange/Act/Assert است. پر کردن جزئیات بیشتر آن با برنامه نویس است.
و یا به صورت خلاصه‌تر:
 PM> Scaffold UnitTest Tasks Delete
در اینجا متد آزمون واحد کنترلر Tasks و اکشن متد Delete آن، تولید می‌شود.

کار مقدماتی با MVC Scaffolding و امکانات مهیای در آن همینجا به پایان می‌رسد. در قسمت‌های بعد به سفارشی سازی این مجموعه خواهیم پرداخت.
مطالب
یک نکته از ASP.NET 4.5 GridView
تا قبل از ASP.NET 4.5 ، هنگام کار با GridView رسم بر این بوده که به خاصیت DataSource ، یک منبع داده (مانند SqlDataSource و ...) را  Bind کرده و متد DataBind را صدا نموده و نتیجه نمایش داده می‌شد.
اما با استفاده از ویژگی‌های جدید اضافه شده(هر چند با تأخیر نسبت به Grid‌های پیشرفته دیگر ) کار با این کنترل راحت‌تر و خواناتر شده است. یکی از این ویژگی‌ها را با هم بررسی می‌کنیم:
با استفاده از ویژگی SelectMethod میتوان متدی را به GridView معرفی کرد که وظیفه منبع داده را انجام داده و هنگام Bind فراخوانی شده و گرید را پر کند:
مثال:
    <asp:GridView ID="gvCities"
                runat="server"
                AutoGenerateColumns="False"
                ItemType="WebApplication3.City"
                SelectMethod="GetAllCities">
                <Columns>
                    <asp:TemplateField HeaderText="نام">
                        <ItemTemplate><%#: Item.Name %></ItemTemplate>
                    </asp:TemplateField>
                </Columns>
     </asp:GridView>
نکته مهم در این کد ItemType است. با استفاده از این خاصیت به جای اینکه مانند قبل نام فیلدهایی که قرار است در گرید نمایش  داده شود را بصورت string معرفی کنیم (مثلا در اینجا ("Eval("Name ، اگر نام فیلد را غلط بنویسیم هنگام کامپایل خطایی صادر نمی‌شود)، آنرا بصورت  Strongly Type نوشته و از اشتباه جلوگیری می‌کنیم.( + )
کد متد:
 public IQueryable<City> GetAllCities()
        {
            var context = new EFContext();
            var q = from c in context.City
                    orderby c.Name
                    select c;
            return q;
        }
و سپس دستور زیر  را فراخوانی می‌کنیم:
 gvCities.DataBind();
اگر بخواهیم در گرید Paging داشته باشیم بصورت زیر عمل می‌کنیم:
   <asp:GridView ID="gvCities" 
                runat="server" 
                AutoGenerateColumns="False"
                AllowPaging="True" 
                PageSize="10" 
                ItemType="WebApplication3.City" 
                SelectMethod="GetAllCities">
                 <Columns>
                    <asp:TemplateField HeaderText="نام">
                        <ItemTemplate><%#: Item.Name %></ItemTemplate>
                    </asp:TemplateField>
                </Columns>
  </asp:GridView>
که در اینجا دو خصوصیت AllowPaging و PageSize را مقدار دهی کرده ایم. این خصوصیت‌ها اجازه صفحه بندی را به گرید می‌دهند.حال برای اینکه متد نیز برای صفحه بندی آماده شود باید سه آرگومان به آن اضافه کنیم:(نام پارامترها باید دقیقا موارد زیر باشد)
1- startRowIndex: نقطه شروع صفحه بندی را مشخص می‌کند.
2- maximumRows: تعداد سطرهایی که گرید باید نمایش دهد را مشخص می‌کند.
3- totalRowCount: این پارامتر  باید در تابع مقدار دهی شود (مانند مثال) تا مشخص شود نتیجه Query چند رکورد است و در نهایت گرید تعداد صفحات را بر این اساس نمایش می‌دهد.

 و برای اینکه صفحه بندی را در Query هم لحاظ کنیم از دو تا بع Skip و Take استفاده شده است.
 public IQueryable<City> GetAllCities(int startRowIndex, int maximumRows, out int totalRowCount)
 {
        var context = new EFContext();
        var q = from c in context.City
                    select c;

        totalRowCount = q.Count();

        return q.OrderBy(x=>x.Name).Skip(startRowIndex).Take(maximumRows);
 }

نکته مهم در این متد  IQueryable  بودن آن است که باعث واکشی داده‌ها بصورت صفحه به صفحه میشود.
دستورات SQL تولید شده در پروفایلر:

همانطور که مشاهده می‌کنید دو دستور SQL  تولید شده ، یکی برای بازگرداندن تعداد رکوردها و یکی هم برای واکشی داده‌ها به اندازه تعداد رکوردهای مجاز در هر صفحه.


مطالب
کامپوننت‌های جنریک در Blazor
طرح مشکل! نیاز به دریافت انواع و اقسام مقادیر یک جنس (مانند اعداد و یا تاریخ) در کامپوننت‌های Blazor

فرض کنید می‌خواهید عددی را در کامپوننتی، دریافت کنید. می‌توان اینکار را با تعریف یک پارامتر عمومی به صورت زیر انجام داد:
[Parameter] public int Value { get; set; }
و ... مشکل از همینجا شروع می‌شود! خوب، برای نوع‌های double ، decimal ، float ، long و غیره چه باید کرد؟ آیا باید به ازای هر کدام، یک پارامتر مخصوص را تعریف کنیم؟ و یا اگر خواستیم زمان و تاریخ را از کاربر دریافت کنیم، آیا باید او را مجبور به ارائه‌ی فقط DateTime کنیم و یا ... شاید بهتر باشد به او بگوییم اگر «رشته‌ای» را در اختیار ما قرار دهید، ما یکبار دیگر خودمان کار تبدیل آن‌را به نوع تاریخ انجام خواهیم داد!
برای حل این نوع مشکلات، می‌توان در Blazor پارامترها را جنریک تعریف کرد. برای نمونه اگر به طراحی یک سری کامپوننت‌های پایه‌ای Blazor دقت کنیم، امکان دریافت مستقیم انواع و اقسام اعداد و یا انواع و اقسام نوع‌های مرتبط با زمان را دارند؛ بدون اینکه این اطلاعات را به صورت رشته‌ای دریافت کنند و یا به ازای هر نوع عددی، یک پارامتر جداگانه را تعریف و یا اینکه استفاده کننده را محدود به ورود تنها یک نوع عددی و یا زمانی خاص کرده باشند.


نحوه‌ی جنریک تعریف کردن یک کامپوننت در Blazor

Blazor فایل‌های razor. را در حین پروسه‌ی build، به cs. تبدیل می‌کند و از آنجائیکه فایل‌های razor. الزامی به تعریف نام کلاس مرتبط را ندارند و این نام به صورت خودکار دقیقا از نام خود فایل، دریافت می‌شود، نیاز است راهی را یافت تا بتوان کلاس مرتبط را به صورت Generic تعریف کرد. برای این منظور، دایرکتیو ویژه‌ای به نام typeparam@ پیش‌ بینی شده‌است که توسط آن می‌توان یک یا چند نوع/پارامتر جنریک جدا شده‌ی توسط کاما را تعریف کنیم (تعریف شده‌ی در فایل فرضی MyGenericComponent.razor):
@typeparam T

@code {
    [Parameter] public T Value { get; set; }
}
در این مثال کامپوننتی را مشاهده می‌کنید که مقدار دریافتی خود را به صورت جنریک از نوع T دریافت می‌کند. این T باید توسط دایرکتیو typeparam@ دقیقا قید شود تا در حین ساخت خودکار کلاس معادل این فایل، مورد استفاده قرار گیرد.


نحوه‌ی جنریک تعریف کردن کامپوننت‌های دارای code-behind

اگر قسمت code@ فایل‌های razor. را به فایل‌های code-benind منتقل می‌کنید و داخل فایل razor.، کد #C نمی‌نویسید، روش جنریک تعریف کردن یک کامپوننت، به صورت زیر خواهد بود (برای مثال دو فایل MyGenericComponentWithCodeBehind.razor و MyGenericComponentWithCodeBehind.razor.cs تعریف شده‌اند):
using Microsoft.AspNetCore.Components;

namespace BlazorGenericComponents.Pages;

public partial class MyGenericComponentWithCodeBehind<T>
{
    [Parameter] public T Value { get; set; }
}
در اینجا ذکر دایرکتیو typeparam@ در فایل razor. متناظر هم باید صورت گیرد:
@typeparam T
یعنی هر دو کلاس نهایی حاصل از این فایل‌ها که به صورت partial تعریف شده‌اند (و در آخر یکی می‌شوند)، باید به صورت جنریک تعریف شوند.


روش مقید کردن پارامترهای جنریک در کامپوننت‌ها

از زمان دات نت 6 به بعد، امکان محدود کردن بازه‌ی نوع‌های قابل پذیرش T نیز میسر شده‌است:
@typeparam T where T : IMyInterface


روش مشخص کردن صریح نوع پارامترهای جنریک، در حین استفاده‌ی از آن‌ها

عموما نیازی به مشخص کردن نوع پارامترهای جنریک، در حین استفاده‌ی از آن‌ها نیست و کامپایلر بر اساس نوع مقدار ورودی، سعی خواهد کرد این نوع را به صورت خودکار تشخیص دهد؛ اما اگر به هر دلیلی چنین امری ممکن نبود و خطایی را دریافت کردید، می‌توان نوع پارامتر جنریک را به صورت زیر مشخص کرد:
<MyGenericComponent Value="10" T="int"/>
در اینجا نام پارامتر جنریک، ذکر شده و سپس نوعی به آن انتساب داده می‌شود.


روش پردازش پارامترهای جنریک در کامپوننت‌ها

تا اینجا امکان پذیرش نوع‌های جنریک را توسط استفاده کننده میسر کردیم؛ اما قسمت دیگر آن، نحوه‌ی کار با نوع T، درون یک کامپوننت است:
using Microsoft.AspNetCore.Components;

namespace BlazorGenericComponents.Pages;

public partial class MyGenericComponentWithCodeBehind<T>
{
    private string FormattedValue { set; get; }

    [Parameter] public T Value { get; set; }

    [Parameter] public string Format { get; set; }

    protected override void OnParametersSet()
    {
        FormattedValue = ConvertNumberToText();
    }

    private string ConvertNumberToText()
    {
        return Value switch
        {
            short shortValue => shortValue.ToString(Format),
            ushort ushortValue => ushortValue.ToString(Format),
            int intValue => intValue.ToString(Format),
            uint uintValue => uintValue.ToString(Format),
            byte byteValue => byteValue.ToString(Format),
            sbyte sbyteValue => sbyteValue.ToString(Format),
            decimal decimalValue => decimalValue.ToString(Format),
            double doubleValue => doubleValue.ToString(Format),
            float floatValue => floatValue.ToString(Format),
            long longValue => longValue.ToString(Format),
            ulong ulongValue => ulongValue.ToString(Format),
            _ => string.Empty
        };
    }
}
برای مثال فرض کنید کامپوننت جنریک ما قرار است انواع و اقسام اعداد را بپذیرد و سپس بر اساس Format مشخص شده، آن‌ها را نمایش دهد. در اینجا جهت واکنش نشان دادن به تغییرات Value، می‌توان از روال رخ‌داد گردان OnParametersSet استفاده کرد. سپس در متد ConvertNumberToText، بر اساس هر نوع میسر عددی، باید منطق ویژه‌ای را تهیه کرد که نمونه‌ای از آن‌را در اینجا مشاهده می‌کنید.

و در آخر نمایش نهایی آن هم در فایل razor. متناظر، به این صورت خواهد بود:
@typeparam T

@FormattedValue
بازخوردهای پروژه‌ها
ایراد در نمایش جزییات نمایش مطالب و TreeView Extension
زمانی که روی جزییات نمایش  مطلبی کلیک میکنیم خطای Helper مربوط به  TreeView  رو میگیره بعد از بررسی متوجه شدم کد TreeView ربطی به پروژه نداره و اصلا داخل پوشه  Helper  تعریف نشده.

خطای دریافتی بعد از زدن روی دکمه مشاهده
( به ازای کامنت هر مطلب  از TreeView  استفاده شده، خطا میدهد)


HttpCompileException: d:\IrisCms-master\Iris.Web\Views\Comment\_PostComments.cshtml(7): error CS1061: 'System.Web.Mvc.HtmlHelper<System.Collections.Generic.IEnumerable<Iris.DomainClasses.Entities.Comment>>' does not contain a definition for 'TreeView' and no extension method 'TreeView' accepting a first argument of type 'System.Web.Mvc.HtmlHelper<System.Collections.Generic.IEnumerable<Iris.DomainClasses.Entities.Comment>>' could be found (are you missing a using directive or an assembly reference?) 


کد TreeView
using System.Collections.Generic;
using System.Web.Mvc;
using Iris.Servicelayer.Interfaces;

namespace Iris.Web.Controllers
{
    public partial class TreeViewController : Controller
    {
        private readonly IPageService _pageService;

        public TreeViewController(IPageService pageService)
        {
            _pageService = pageService;
        }

        public virtual ActionResult Index()
        {
            List<TreeViewLocation> locations = GetLocations();

            return View(_pageService.GetNavBarData(x => x.Body.Equals("dsad")));
        }

        public static List<TreeViewLocation> GetLocations()
        {
            var locations = new List<TreeViewLocation>
            {
                new TreeViewLocation
                {
                    Name = "United States",
                    ChildLocations =
                    {
                        new TreeViewLocation
                        {
                            Name = "Chicago",
                            ChildLocations =
                            {
                                new TreeViewLocation {Name = "Rack 1"},
                                new TreeViewLocation {Name = "Rack 2"},
                                new TreeViewLocation {Name = "Rack 3"},
                                new TreeViewLocation {Name = "Rack 3"},
                            }
                        },
                        new TreeViewLocation
                        {
                            Name = "Dallas",
                            ChildLocations =
                            {
                                new TreeViewLocation
                                {
                                    Name = "Rack 1",
                                    ChildLocations =
                                    {
                                        new TreeViewLocation
                                        {
                                            Name = "Rack 1",
                                            ChildLocations =
                                            {
                                                new TreeViewLocation {Name = "Rack 1"},
                                                new TreeViewLocation {Name = "Rack 2"},
                                                new TreeViewLocation {Name = "Rack 3"},
                                                new TreeViewLocation {Name = "Rack 3"},
                                            }
                                        },
                                        new TreeViewLocation {Name = "Rack 2"},
                                        new TreeViewLocation {Name = "Rack 3"},
                                        new TreeViewLocation {Name = "Rack 3"},
                                    }
                                },
                                new TreeViewLocation {Name = "Rack 2"},
                                new TreeViewLocation {Name = "Rack 3"},
                                new TreeViewLocation {Name = "Rack 3"},
                            }
                        },
                        new TreeViewLocation {Name = "Dallas"},
                        new TreeViewLocation {Name = "Dallas"},
                        new TreeViewLocation {Name = "Dallas"},
                        new TreeViewLocation {Name = "Dallas"},
                        new TreeViewLocation {Name = "Dallas"},
                        new TreeViewLocation {Name = "Dallas"},
                    }
                },
                new TreeViewLocation
                {
                    Name = "Canada",
                    ChildLocations =
                    {
                        new TreeViewLocation {Name = "Ontario"},
                        new TreeViewLocation {Name = "Windsor"}
                    }
                }
            };
            return locations;
        }
    }

    public class TreeViewLocation
    {
        public TreeViewLocation()
        {
            ChildLocations = new HashSet<TreeViewLocation>();
        }

        public int Id { get; set; }
        public string Name { get; set; }
        public ICollection<TreeViewLocation> ChildLocations { get; set; }
    }
}

نظرات مطالب
روش ذخیره‌ی لاگ‌های ILogger در پایگاه داده در Blazor
یک نکته‌ی تکمیلی: نیاز به استفاده‌ی از یک Context مجزای از Context درخواست جاری، در حین لاگ کردن

اگر استثنای حاصله به علت وجود مشکلی در اعمال خود EF-Core باشد، از این لحظه به بعد نمی‌توان از Context موجود، برای ثبت کردن اطلاعاتی در بانک اطلاعاتی برنامه استفاده کرد. یعنی زمانیکه Context ای به سازنده‌ی لاگر تزریق شد:
public class DbLoggerProvider:ILoggerProvider
{
     public DbLoggerProvider(ApplicationDbContext dbContext)
این Context، دقیقا همان Context درخواست جاری برنامه است (الگوی واحد کار) و وضعیت داخلی آن به علت بروز استثناء، برای انجام اعمال بیشتری بر روی آن مناسب نیست.
برای اینکه بتوان یک Context مجزای از Context درخواست جاری را در لاگر مورد استفاده قرار داد، ابتدا باید IServiceProvider را بجای Context، به سازنده‌ی لاگر تزریق کرد و سپس با استفاده از آن، یک scoped context جدید را ایجاد کرد (مانند کاری که در اینجا شده):
var scopeFactory = _serviceProvider.GetRequiredService<IServiceScopeFactory>();
using (var scope = scopeFactory.CreateScope())
{
  var scopedProvider = scope.ServiceProvider;
  using (var newDbContext = scopedProvider.GetRequiredService<ApplicationDbContext>())
  {
       // ...
  }
}
اطلاعات بیشتر
مطالب
استفاده از MVVM زمانیکه امکان Binding وجود ندارد

ساده‌ترین تعریف MVVM، نهایت استفاده از امکانات Binding موجود در WPF و Silverlight است. اما خوب، همیشه همه چیز بر وفق مراد نیست. مثلا کنترل WebBrowser را در WPF در نظر بگیرید. فرض کنید که می‌خواهیم خاصیت Source آن‌را در ViewModel مقدار دهی کنیم تا صفحه‌ای را نمایش دهد. بلافاصله با خطای زیر متوقف خواهیم شد:

A 'Binding' cannot be set on the 'Source' property of type 'WebBrowser'.
A 'Binding' can only be set on a DependencyProperty of a DependencyObject.

بله؛ این خاصیت از نوع DependencyProperty نیست و نمی‌توان چیزی را به آن Bind کرد. بنابراین این نکته مهم را توسعه دهنده‌های کنترل‌های WPF و Silverlight همیشه باید بخاطر داشته باشند که اگر قرار است کنترل‌های شما MVVM friendly باشند باید کمی بیشتر زحمت کشیده و بجای تعریف خواص ساده دات نتی، خواص مورد نظر را از نوع DependencyProperty تعریف کنید.
الان که تعریف نشده چه باید کرد؟
پاسخ متداول آن این است: مهم نیست؛ خودمان می‌توانیم این‌کار را انجام دهیم! یک Attached property یا به عبارتی یک Behavior را تعریف و سپس به کمک آن عملیات Binding را میسر خواهیم ساخت. برای مثال:
در این Attached property قصد داریم یک خاصیت جدید به نام BindableSource را جهت کنترل WebBrowser تعریف کنیم:

using System;
using System.Windows;
using System.Windows.Controls;

namespace WebBrowserSample.Behaviors
{
public static class WebBrowserBehaviors
{
public static readonly DependencyProperty BindableSourceProperty =
DependencyProperty.RegisterAttached("BindableSource",
typeof(object),
typeof(WebBrowserBehaviors),
new UIPropertyMetadata(null, BindableSourcePropertyChanged));

public static object GetBindableSource(DependencyObject obj)
{
return (string)obj.GetValue(BindableSourceProperty);
}

public static void SetBindableSource(DependencyObject obj, object value)
{
obj.SetValue(BindableSourceProperty, value);
}

public static void BindableSourcePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
WebBrowser browser = o as WebBrowser;
if (browser == null) return;

Uri uri = null;

if (e.NewValue is string)
{
var uriString = e.NewValue as string;
uri = string.IsNullOrWhiteSpace(uriString) ? null : new Uri(uriString);
}
else if (e.NewValue is Uri)
{
uri = e.NewValue as Uri;
}

if (uri != null) browser.Source = uri;
}
}
}


یک مثال ساده از استفاده‌ی آن هم به صورت زیر می‌تواند باشد:
ابتدا ViewModel مرتبط با فرم برنامه را تهیه خواهیم کرد. اینجا چون یک خاصیت را قرار است Bind کنیم، همینجا داخل ViewModel آن‌را تعریف کرده‌ایم. اگر تعداد آن‌ها بیشتر بود بهتر است به یک کلاس مجزا مثلا GuiModel منتقل شوند.

using System;
using System.ComponentModel;

namespace WebBrowserSample.ViewModels
{
public class MainWindowViewModel : INotifyPropertyChanged
{
Uri _sourceUri;
public Uri SourceUri
{
get { return _sourceUri; }
set
{
_sourceUri = value;
raisePropertyChanged("SourceUri");
}
}

public MainWindowViewModel()
{
SourceUri = new Uri(@"C:\path\arrow.png");
}

#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
void raisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler == null) return;
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}

در ادامه بجای استفاده از خاصیت Source که قابلیت Binding ندارد، از Behavior سفارشی تعریف شده استفاده خواهیم کرد. ابتدا باید فضای نام آن تعریف شود، سپس BindableSource مرتبط آن در دسترس خواهد بود:

<Window x:Class="WebBrowserSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:VM="clr-namespace:WebBrowserSample.ViewModels"
xmlns:B="clr-namespace:WebBrowserSample.Behaviors"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<VM:MainWindowViewModel x:Key="vmMainWindowViewModel" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource vmMainWindowViewModel}}">
<WebBrowser B:WebBrowserBehaviors.BindableSource="{Binding SourceUri}" />
</Grid>
</Window>



نمونه مشابه این مورد را در مثال «استفاده از کنترل‌های Active-X در WPF» پیشتر در این سایت دیده‌اید.

مطالب
Span در C# 7.2
C# 7.2 به همراه تعداد کوچکی از بهبودهای کامپایلر است و با Visual Studio 2017 نگارش 15.5 ارائه شده و روش فعالسازی آن با نگارش 7.1 آن یکی است (انتخاب گزینه‌ی «C# latest minor version (latest)» در تنظیمات پیشرفته‌ی Build خواص پروژه). همچنین اگر از VSCode استفاده می‌کنید، نگارش 1.14 افزونه‌ی #C آن، پشتیبانی کاملی را از C# 7.2 به همراه دارد؛ در اینجا، افزودن خاصیت <LangVersion>latest</LangVersion> به فایل csproj برنامه برای استفاده‌ی از آخرین نگارش کامپایلر نصب شده، کفایت می‌کند. البته باید دقت داشت کامپایلر C# 7.2 به همراه NET Core SDK 2.1.2. ارائه شده‌است. بنابراین تنها نصب آخرین نگارش افزونه‌ی #C مخصوص VSCode برای کامپایل آن کافی نیست و باید حداقل SDK یاد شده (یا نگارش جدیدتر آن) را هم نصب کنید.
 

نوع‌های جدید <Span<T و  <ReadOnlySpan<T در C# 7.2

نوع‌های جدید <Span<T و <ReadOnlySpan<T جهت ارائه‌ی ناحیه‌های اختیاری پیوسته‌ای از حافظه، شبیه به آرایه‌ها تدارک دیده شده‌اند و هدف استفاده‌ی از آن‌ها، تولید برنامه‌های سمت سرور با کارآیی بالا است.
برای کار با این نوع‌ها، هم نیاز به کامپایلر C# 7.2 است و هم نصب بسته‌ی نیوگت System.Memory:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    <LangVersion>latest</LangVersion>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="System.Memory" Version="4.4.0-preview1-25305-02" />
  </ItemGroup>
</Project>
این بسته از .NETStandard 1.0. به بعد را پشتیبانی می‌کند؛ یعنی با +NET 4.5+ ،Mono 4.6.  و +NET Core 1.0. سازگار است.


Spanها و امکان دسترسی به انواع حافظه

Spanها می‌توانند به حافظه‌ی مدیریت شده، حافظه‌ی بومی (native) و حافظه‌ی اختصاص داده شده‌ی در Stack اشاره کنند. به عبارتی Spanها یک لایه انتزاعی، برفراز تمام انواع و اقسام حافظه‌هایی هستند که می‌توانند در اختیار توسعه دهندگان NET. باشند.
- البته اکثر توسعه دهندگان دات نت از حافظه‌ی مدیریت شده استفاده می‌کنند. برای مثال Stack memory تنها از طریق کدهای unsafe و واژه‌ی کلیدی stackalloc قابل تخصیص است. این نوع حافظه بسیار سریع است و همچنین بسیار کوچک؛ کمتر از یک مگابایت که به خوبی در CPU cache جا می‌شود. اما اگر در این بین حجم حافظه‌ی تخصیصی بیشتر از یک مگابایت شود، بلافاصله استثنای StackOverflowException غیرقابل مدیریتی را به همراه خاتمه‌ی فوری برنامه به همراه خواهد داشت. برای نمونه از این نوع حافظه در جهت مدیریت رخ‌دادهای داخلی corefx زیاد استفاده می‌شود.
- حافظه‌ی مدیریت شده، همان حافظه‌ای است که توسط واژه‌ی کلیدی new در برنامه، جهت ایجاد اشیاء، تخصیص داده می‌شود و طول عمر آن تحت مدیریت GC است.
- حافظه‌ی مدیریت نشده یا بومی از دید GC مخفی است و توسط متدهایی مانند Marshal.AllocHGlobal و Marshal.AllocCoTaskMem در اختیار برنامه قرار می‌گیرند. این حافظه باید به صورت صریحی توسط توسعه دهنده به کمک متدهایی مانند Marshal.FreeHGlobal و Marshal.FreeCoTaskMem آزاد شود. وب سرور Kestrel مخصوص ASP.NET Core، از این روش جهت کار با آرایه‌های حجیم، جهت کاهش بار GC استفاده می‌کند.

مزیت کار با Spanها این است که دسترسی امن و type safeایی را به انواع حافظه‌های مهیا، جهت توسعه دهندگانی که عموما کدهای unsafe ایی را نمی‌نویسند و با اشاره‌گرها به صورت مستقیم کار نمی‌کنند، میسر می‌کند. برای مثال تا پیش از معرفی Spanها، برای دسترسی به 1000 عنصر یک آرایه‌ی 10 هزار عنصری و ارسال آن به یک متد، نیاز بود تا ابتدا یک کپی از این 1000 عنصر را تهیه کرد. این عملیات از لحاظ میزان مصرف حافظه و همچنین زمان انجام آن، بسیار هزینه‌بر است. با استفاده از <Span<T می‌توان یک دید مجازی از آن آرایه را بدون اختصاص آرایه‌ای و یا آرایه‌هایی جدید، ارائه کرد.


مثالی از کاربرد Spanها جهت کاهش تعداد بار تخصیص‌های حافظه

برای نمونه، متد IsValidName زیر، بررسی می‌کند که طول رشته‌ی دریافتی حداقل 2 باشد و حتما با یک حرف شروع شده باشد:
    static class NameValidatorUsingString
    {
        public static bool IsValidName(string name)
        {
            if (name.Length < 2)
                return false;

            if (char.IsLetter(name[0]))
                return true;

            return false;
        }
    }
در این حالت یک نمونه مثال از استفاده‌ی آن می‌تواند به صورت زیر باشد:
string fullName = "User 1";
string firstName = fullName.Substring(0, 4);
NameValidatorUsingString.IsValidName(firstName);
در اینجا زمانیکه از متد Substring استفاده می‌شود، در حقیقت تخصیص حافظه‌ی دومی جهت تولید firstName رخ می‌دهد.

همچنین اگر این اطلاعات را از طریق شبکه دریافت کرده باشیم، ممکن است به صورت آرایه‌ای از حروف دریافت شوند:
char[] anotherFullName = { 'A', 'B' };
که به صورت مستقیم در متد IsValidName قابل استفاده نیست و خطای عدم امکان تبدیل []char به string، از طرف کامپایلر صادر می‌شود:
NameValidatorUsingString.IsValidName(anotherFullName);
در این حالت برای استفاده‌ی از این آرایه، نیاز است یک تخصیص حافظه‌ی دیگر نیز صورت گیرد:
NameValidatorUsingString.IsValidName(new string(anotherFullName));

اکنون در C# 7.2، بازنویسی این متد توسط ReadOnlySpan، به صورت ذیل است:
    static class NameValidatorUsingSpan
    {
        public static bool IsValidName(ReadOnlySpan<char> name)
        {
            if (name.Length < 2)
                return false;

            if (char.IsLetter(name[0]))
                return true;

            return false;

        }
    }
که این مزایا را به همراه دارد:
ReadOnlySpan<char> fullName = "User 1".AsSpan();
ReadOnlySpan<char> firstName = fullName.Slice(0, 4);
NameValidatorUsingSpan.IsValidName(firstName);
کار با API مربوط به Spanها به همراه تخصیص حافظه‌ی جدیدی نیست. برای نمونه در اینجا متد Slice این API، سبب تخصیص حافظه‌ی جدیدی نمی‌شود (برخلاف متد Substring) و فقط به قسمتی از حافظه‌ی موجود اشاره می‌کند (بدون نیاز به کار مستقیم با اشاره‌گرها و کدهای unsafe).

و یا اینبار امکان استفاده‌ی از آرایه‌ای از کاراکترها، بدون نیاز به تخصیص حافظه‌ای جدید، برای بررسی اعتبار مقادیر دریافتی میسر است:
char[] anotherFullName = { 'A', 'B' };
NameValidatorUsingSpan.IsValidName(anotherFullName);

برای نمونه از یک چنین APIایی در پشت صحنه‌ی کتابخانه‌هایی مانند SignalR و یا Roslyn، برای بالا بردن کارآیی برنامه، با کاهش تعداد بار تخصیص‌های حافظه‌ی مورد نیاز، بسیار استفاده شده‌است. برای نمونه در NET Core 2.1.، حجم رشته‌های تخصیص داده شده‌ی در فریم ورک‌های وابسته، به این ترتیب به شدت کاهش یافته‌است.


مثال‌هایی از کار با API نوع Span

امکان ایجاد یک Span از یک array
var arr = new byte[10];
Span<byte> bytes = arr; // Implicit cast from T[] to Span<T>
پس از آن کار با این span همانند کار با آرایه‌های معمولی است؛ با این تفاوت که این span تنها یک دید مجازی از قسمتی از این آرایه را ارائه می‌دهد؛ بدون سربار تخصیص حافظه‌ی اضافی و کپی اطلاعات:
Span<byte> slicedBytes = bytes.Slice(start: 5, length: 2);
slicedBytes[0] = 42;
slicedBytes[1] = 43;
slicedBytes[2] = 44; // Throws IndexOutOfRangeException
bytes[2] = 45; // OK
در اینجا slicedBytes یک دید مجازی از ایندکس 5 تا 7 آرایه‌ی arr را ارائه می‌دهد. کار کردن با آن نیز همانند آرایه‌ها، توسط ایندکس‌ها میسر است.
همچنین تغییرات بر روی Span (غیر read only) بر روی آرایه‌ی اصلی نیز تاثیر گذار است. برای مثال در اینجا با تغییر bytes[2]، مقدار arr[2] نیز تغییر می‌کند.

و یا روش دیگر ایجاد Span استفاده از متد AsSpan است:
var array = new byte[100];
Span<byte> interiorRef1 = array.AsSpan().Slice(start: 20);
همین عملیات را توسط new Span نیز می‌توان به صورت ساده‌تری ارائه داد:
Span<byte> interiorRef2 = new Span<byte>(array: array, start: 20, length: array.Length - 20);


محدودیت‌های کار با Spanها

- Span تنها یک نوع stack-only است.
- Spanها در بین تردها به اشتراک گذاشته نمی‌شوند. هر استک در یک زمان تنها توسط یک ترد قابل دسترسی است. بنابراین Spanها thread-safe هستند.
- طول عمر Spanها کوتاه است و قابلیت قرارگیری بر روی heap با طول عمر بیشتر را ندارند؛ یعنی:
  • به صورت فیلد در یک نوع non-stackonly قابل تعریف نیستند:
class Impossible
{
   Span<byte> field;
}
فیلدهای یک کلاس در heap ذخیره می‌شوند. بنابراین محل ذخیره سازی spanها نیستند.
  • به عنوان پارامترهای متدهای async قابل استفاده نیستند. چون در این بین در پشت صحنه یک AsyncMethodBuilder تشکیل می‌شود که در قسمتی از آن، پارامترها بر روی heap قرار می‌گیرند.
  • هرجائیکه عملیات boxing صورت گیرد، نتیجه‌ی عملیات بر روی heap قرار می‌گیرد. بنابراین در یک چنین مواردی نمی‌توان از Spanها استفاده کرد. برای مثال تعریف Func<T> valueProvider و سپس فراخوانی ()valueProvider.Invoke به همراه یک boxing است. بنابراین نمی‌توان از spanها به عنوان نوع آرگومان جنریک استفاده کرد. این مورد هرچند کامپایل می‌شود، اما در زمان اجرا سبب خاتمه‌ی برنامه خواهد شد و یا نمونه‌ی دیگر، عدم امکان دسترسی به آن‌ها توسط reflection invoke APIs است که سبب boxing می‌شود.


معرفی نوع <Memory<T

با توجه به محدودیت‌های Span و خصوصا اینکه به عنوان پارامتر متدهای async قابل استفاده نیست (چون بر روی stack ذخیره می‌شوند)، نوع دیگری به نام <Memory<T نیز به همراه C# 7.2 ارائه شده‌است. البته این نوع هنوز به بسته‌ی نیوگت فوق اضافه نشده‌است و به همراه ارائه نهایی NET Core 2.1. ارائه خواهد شد.
این نوع، محدودیت <Span<T را نداشته و قابلیت ذخیره سازی بر روی heap را دارا است.
static async Task<int> ChecksumReadAsync(Memory<byte> buffer, Stream stream)
{
   int bytesRead = await stream.ReadAsync(buffer);
   return Checksum(buffer.Span.Slice(0, bytesRead));
   // Or buffer.Slice(0, bytesRead).Span
}
در اینجا نیز می‌توان از یک آرایه، یک <Memory<T را ایجاد و سپس یک <Span<T را از آن دریافت و با Sliceهای آن کار کرد.