مطالب
استفاده از EF در اپلیکیشن های N-Tier : قسمت سوم

در قسمت قبلی بروز رسانی موجودیت‌های منفصل با WCF را بررسی کردیم. در این قسمت خواهیم دید چگونه می‌توان تغییرات موجودیت‌ها را تشخیص داد و عملیات CRUD را روی یک Object Graph اجرا کرد.

تشخیص تغییرات با Web API

فرض کنید می‌خواهیم از سرویس‌های Web API برای انجام عملیات CRUD استفاده کنیم، اما بدون آنکه برای هر موجودیت متدهایی مجزا تعریف کنیم. به بیان دیگر می‌خواهیم عملیات مذکور را روی یک Object Graph انجام دهیم. مدیریت داده‌ها هم با مدل Code-First پیاده سازی می‌شود. در مثال جاری یک اپلیکیشن کنسول خواهیم داشت که بعنوان یک کلاینت سرویس را فراخوانی می‌کند. هر پروژه نیز در Solution مجزایی قرار دارد، تا یک محیط n-Tier را شبیه سازی کنیم.

مدل زیر را در نظر بگیرید.

همانطور که می‌بینید مدل ما آژانس‌های مسافرتی و رزرواسیون آنها را ارائه می‌کند. می‌خواهیم مدل و کد دسترسی داده‌ها را در یک سرویس Web API پیاده سازی کنیم تا هر کلاینتی که به HTTP دسترسی دارد بتواند عملیات CRUD را انجام دهد. برای ساختن سرویس مورد نظر مراحل زیر را دنبال کنید:

  • در ویژوال استودیو پروژه جدیدی از نوع ASP.NET Web Application بسازید و قالب پروژه را Web API انتخاب کنید. نام پروژه را به Recipe3.Service تغییر دهید.
  • کنترلر جدیدی بنام TravelAgentController به پروژه اضافه کنید.
  • دو کلاس جدید با نام‌های TravelAgent و Booking بسازید و کد آنها را مطابق لیست زیر تغییر دهید.
public class TravelAgent
{
    public TravelAgent()
    {
        this.Bookings = new HashSet<Booking>();
    }

    public int AgentId { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Booking> Bookings { get; set; }
}

public class Booking
{
    public int BookingId { get; set; }
    public int AgentId { get; set; }
    public string Customer { get; set; }
    public DateTime BookingDate { get; set; }
    public bool Paid { get; set; }
    public virtual TravelAgent TravelAgent { get; set; }
}
  • با استفاده از NuGet Package Manager کتابخانه Entity Framework 6 را به پروژه اضافه کنید.
  • کلاس جدیدی بنام Recipe3Context بسازید و کد آن را مطابق لیست زیر تغییر دهید.
public class Recipe3Context : DbContext
{
    public Recipe3Context() : base("Recipe3ConnectionString") { }
    public DbSet<TravelAgent> TravelAgents { get; set; }
    public DbSet<Booking> Bookings { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<TravelAgent>().HasKey(x => x.AgentId);
        modelBuilder.Entity<TravelAgent>().ToTable("TravelAgents");
        modelBuilder.Entity<Booking>().ToTable("Bookings");
    }
}

  • فایل Web.config پروژه را باز کنید و رشته اتصال زیر را به قسمت ConnectionStrings اضافه کنید.
<connectionStrings>
  <add name="Recipe3ConnectionString"
    connectionString="Data Source=.;
    Initial Catalog=EFRecipes;
    Integrated Security=True;
    MultipleActiveResultSets=True"
    providerName="System.Data.SqlClient" />
</connectionStrings>
  • فایل Global.asax را باز کنید و کد زیر را به متد Application_Start اضافه نمایید. این کد بررسی Model Compatibility در EF را غیرفعال می‌کند. همچنین به JSON serializer می‌گوییم که self-referencing loop خاصیت‌های پیمایشی را نادیده بگیرد. این حلقه بدلیل ارتباط bidirectional بین موجودیت‌ها بوجود می‌آید.
protected void Application_Start()
{
    // Disable Entity Framework Model Compatibilty
    Database.SetInitializer<Recipe1Context>(null);

    // The bidirectional navigation properties between related entities
    // create a self-referencing loop that breaks Web API's effort to
    // serialize the objects as JSON. By default, Json.NET is configured
    // to error when a reference loop is detected. To resolve problem,
    // simply configure JSON serializer to ignore self-referencing loops.
    GlobalConfiguration.Configuration.Formatters.JsonFormatter
        .SerializerSettings.ReferenceLoopHandling =
        Newtonsoft.Json.ReferenceLoopHandling.Ignore;
    ...
}
  • فایل RouteConfig.cs را باز کنید و قوانین مسیریابی را مانند لیست زیر تغییر دهید.
public static void Register(HttpConfiguration config)
{
    config.Routes.MapHttpRoute(
      name: "ActionMethodSave",
      routeTemplate: "api/{controller}/{action}/{id}",
      defaults: new { id = RouteParameter.Optional });
}
  • در آخر کنترلر TravelAgent را باز کنید و کد آن را مطابق لیست زیر بروز رسانی کنید.
public class TravelAgentController : ApiController
{
    // GET api/travelagent
    [HttpGet]
    public IEnumerable<TravelAgent> Retrieve()
    {
        using (var context = new Recipe3Context())
        {
            return context.TravelAgents.Include(x => x.Bookings).ToList();
        }
    }

    /// <summary>
    /// Update changes to TravelAgent, implementing Action-Based Routing in Web API
    /// </summary>
    public HttpResponseMessage Update(TravelAgent travelAgent)
    {
        using (var context = new Recipe3Context())
        {
            var newParentEntity = true;
            // adding the object graph makes the context aware of entire
            // object graph (parent and child entities) and assigns a state
            // of added to each entity.
            context.TravelAgents.Add(travelAgent);
            if (travelAgent.AgentId > 0)
            {
                // as the Id property has a value greater than 0, we assume
                // that travel agent already exists and set entity state to
                // be updated.
                context.Entry(travelAgent).State = EntityState.Modified;
                newParentEntity = false;
            }

            // iterate through child entities, assigning correct state.
            foreach (var booking in travelAgent.Bookings)
            {
                if (booking.BookingId > 0)
                    // assume booking already exists if ID is greater than zero.
                    // set entity to be updated.
                    context.Entry(booking).State = EntityState.Modified;
            }

            context.SaveChanges();
            HttpResponseMessage response;
            // set Http Status code based on operation type
            response = Request.CreateResponse(newParentEntity ? HttpStatusCode.Created : HttpStatusCode.OK, travelAgent);
            return response;
        }
    }

    [HttpDelete]
    public HttpResponseMessage Cleanup()
    {
        using (var context = new Recipe3Context())
        {
            context.Database.ExecuteSqlCommand("delete from [bookings]");
            context.Database.ExecuteSqlCommand("delete from [travelagents]");
        }
        return Request.CreateResponse(HttpStatusCode.OK);
    }
}

در قدم بعدی کلاینت پروژه را می‌سازیم که از سرویس Web API مان استفاده می‌کند.

  • در ویژوال استودیو پروژه جدیدی از نوع Console application بسازید و نام آن را به Recipe3.Client تغییر دهید.
  • فایل program.cs را باز کنید و کد آن را مطابق لیست زیر بروز رسانی کنید.
internal class Program
{
    private HttpClient _client;
    private TravelAgent _agent1, _agent2;
    private Booking _booking1, _booking2, _booking3;
    private HttpResponseMessage _response;

    private static void Main()
    {
        Task t = Run();
        t.Wait();
        Console.WriteLine("\nPress <enter> to continue...");
        Console.ReadLine();
    }

    private static async Task Run()
    {
        var program = new Program();
        program.ServiceSetup();
        // do not proceed until clean-up is completed
        await program.CleanupAsync();
        program.CreateFirstAgent();
        // do not proceed until agent is created
        await program.AddAgentAsync();
        program.CreateSecondAgent();
        // do not proceed until agent is created
        await program.AddSecondAgentAsync();
        program.ModifyAgent();
        // do not proceed until agent is updated
        await program.UpdateAgentAsync();
        // do not proceed until agents are fetched
        await program.FetchAgentsAsync();
    }

    private void ServiceSetup()
    {
        // set up infrastructure for Web API call
        _client = new HttpClient {BaseAddress = new Uri("http://localhost:6687/")};
        // add Accept Header to request Web API content negotiation to return resource in JSON format
        _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    }

    private async Task CleanupAsync()
    {
        // call cleanup method in service
        _response = await _client.DeleteAsync("api/travelagent/cleanup/");
    }

    private void CreateFirstAgent()
    {
        // create new Travel Agent and booking
        _agent1 = new TravelAgent {Name = "John Tate"};
        _booking1 = new Booking
        {
            Customer = "Karen Stevens",
            Paid = false,
            BookingDate = DateTime.Parse("2/2/2010")
        };

        _booking2 = new Booking
        {
            Customer = "Dolly Parton",
            Paid = true,
            BookingDate = DateTime.Parse("3/10/2010")
        };
  
        _agent1.Bookings.Add(_booking1);
        _agent1.Bookings.Add(_booking2);
    }

    private async Task AddAgentAsync()
    {
        // call generic update method in Web API service to add agent and bookings
        _response = await _client.PostAsync("api/travelagent/update/",
            _agent1, new JsonMediaTypeFormatter());

        if (_response.IsSuccessStatusCode)
        {
            // capture newly created travel agent from service, which will include
            // database-generated Ids for each entity
            _agent1 = await _response.Content.ReadAsAsync<TravelAgent>();
            _booking1 = _agent1.Bookings.FirstOrDefault(x => x.Customer == "Karen Stevens");
            _booking2 = _agent1.Bookings.FirstOrDefault(x => x.Customer == "Dolly Parton");

            Console.WriteLine("Successfully created Travel Agent {0} and {1} Booking(s)",
            _agent1.Name, _agent1.Bookings.Count);
        }
        else
            Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase);
    }

    private void CreateSecondAgent()
    {
        // add new agent and booking
        _agent2 = new TravelAgent {Name = "Perry Como"};
        _booking3 = new Booking {
            Customer = "Loretta Lynn",
            Paid = true,
            BookingDate = DateTime.Parse("3/15/2010")};
        _agent2.Bookings.Add(_booking3);
    }

    private async Task AddSecondAgentAsync()
    {
        // call generic update method in Web API service to add agent and booking
        _response = await _client.PostAsync("api/travelagent/update/", _agent2, new JsonMediaTypeFormatter());

        if (_response.IsSuccessStatusCode)
        {
            // capture newly created travel agent from service
            _agent2 = await _response.Content.ReadAsAsync<TravelAgent>();
            _booking3 = _agent2.Bookings.FirstOrDefault(x => x.Customer == "Loretta Lynn");
            Console.WriteLine("Successfully created Travel Agent {0} and {1} Booking(s)",
                _agent2.Name, _agent2.Bookings.Count);
        }
        else
            Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase);
    }

    private void ModifyAgent()
    {
        // modify agent 2 by changing agent name and assigning booking 1 to him from agent 1
        _agent2.Name = "Perry Como, Jr.";
        _agent2.Bookings.Add(_booking1);
    }

    private async Task UpdateAgentAsync()
    {
        // call generic update method in Web API service to update agent 2
        _response = await _client.PostAsync("api/travelagent/update/", _agent2, new JsonMediaTypeFormatter());
        if (_response.IsSuccessStatusCode)
        {
            // capture newly created travel agent from service, which will include Ids
            _agent1 = _response.Content.ReadAsAsync<TravelAgent>().Result;
            Console.WriteLine("Successfully updated Travel Agent {0} and {1} Booking(s)", _agent1.Name, _agent1.Bookings.Count);
        }
        else
            Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase);
    }

    private async Task FetchAgentsAsync()
    {
        // call Get method on service to fetch all Travel Agents and Bookings
        _response = _client.GetAsync("api/travelagent/retrieve").Result;
        if (_response.IsSuccessStatusCode)
        {
            // capture newly created travel agent from service, which will include Ids
            var agents = await _response.Content.ReadAsAsync<IEnumerable<TravelAgent>>();

            foreach (var agent in agents)
            {
                Console.WriteLine("Travel Agent {0} has {1} Booking(s)", agent.Name, agent.Bookings.Count());
            }
        }
        else
            Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase);
    }
}
  • در آخر کلاس‌های TravelAgent و Booking را به پروژه کلاینت اضافه کنید. اینگونه کدها بهتر است در لایه مجزایی قرار گیرند و بین پروژه‌ها به اشتراک گذاشته شوند.

اگر اپلیکیشن کنسول (کلاینت) را اجرا کنید با خروجی زیر مواجه خواهید شد.

(Successfully created Travel Agent John Tate and 2 Booking(s
(Successfully created Travel Agent Perry Como and 1 Booking(s
(Successfully updated Travel Agent Perry Como, Jr. and 2 Booking(s
(Travel Agent John Tate has 1 Booking(s
(Travel Agent Perry Como, Jr. has 2 Booking(s


شرح مثال جاری

با اجرای اپلیکیشن Web API شروع کنید. این اپلیکیشن یک کنترلر MVC Web Controller دارد که پس از اجرا شما را به صفحه خانه هدایت می‌کند. در این مرحله سایت در حال اجرا است و سرویس‌ها قابل دسترسی هستند.

سپس اپلیکیشن کنسول را باز کنید، روی خط اول کد فایل program.cs یک breakpoint قرار دهید و آن را اجرا کنید. ابتدا آدرس سرویس Web API را نگاشت می‌کنیم و با تنظیم مقدار خاصیت Accept Header از سرویس درخواست می‌کنیم که اطلاعات را با فرمت JSON بازگرداند.

بعد از آن با استفاده از آبجکت HttpClient متد DeleteAsync را فراخوانی می‌کنیم که روی کنترلر TravelAgent تعریف شده است. این متد تمام داده‌های پیشین را حذف میکند.

در قدم بعدی سه آبجکت جدید می‌سازیم: یک آژانس مسافرتی و دو رزرواسیون. سپس این آبجکت‌ها را با فراخوانی متد PostAsync روی آبجکت HttpClient به سرویس ارسال می‌کنیم. اگر به متد Update در کنترلر TravelAgent یک breakpoint اضافه کنید، خواهید دید که این متد آبجکت آژانس مسافرتی را بعنوان یک پارامتر دریافت می‌کند و آن را به موجودیت TravelAgents در Context جاری اضافه می‌نماید. این کار آبجکت آژانس مسافرتی و تمام آبجکت‌های فرزند آن را در حالت Added اضافه می‌کند و باعث می‌شود که context جاری شروع به ردیابی (tracking) آنها کند.

نکته: قابل ذکر است که اگر موجودیت‌های متعددی با مقداری یکسان در خاصیت کلید اصلی (Primary-key value) دارید باید مجموعه آبجکت‌های خود را Add کنید و نه Attach. در مثال جاری چند آبجکت Booking داریم که مقدار کلید اصلی آنها صفر است (Bookings with Id = 0). اگر از Attach استفاده کنید EF پیغام خطایی صادر می‌کند چرا که چند موجودیت با مقادیر کلید اصلی یکسان به context جاری اضافه کرده اید.

بعد از آن بر اساس مقدار خاصیت Id مشخص می‌کنیم که موجودیت‌ها باید بروز رسانی شوند یا خیر. اگر مقدار این فیلد بزرگتر از صفر باشد، فرض بر این است که این موجودیت در دیتابیس وجود دارد بنابراین خاصیت EntityState را به Modified تغییر می‌دهیم. علاوه بر این فیلدی هم با نام newParentEntity تعریف کرده ایم که توسط آن بتوانیم کد وضعیت مناسبی به کلاینت بازگردانیم. در صورتی که مقدار فیلد Id در موجودیت TravelAgent برابر با یک باشد، مقدار خاصیت EntityState را به همان Added رها می‌کنیم.

سپس تمام آبجکت‌های فرزند آژانس مسافرتی (رزرواسیون ها) را بررسی میکنیم و همین منطق را روی آنها اعمال می‌کنیم. یعنی در صورتی که مقدار فیلد Id آنها بزرگتر از 0 باشد وضعیت EntityState را به Modified تغییر می‌دهیم. در نهایت متد SaveChanges را فراخوانی می‌کنیم. در این مرحله برای موجودیت‌های جدید اسکریپت‌های Insert و برای موجودیت‌های تغییر کرده اسکریپت‌های Update تولید می‌شود. سپس کد وضعیت مناسب را به کلاینت بر می‌گردانیم. برای موجودیت‌های اضافه شده کد وضعیت 201 (Created) و برای موجودیت‌های بروز رسانی شده کد وضعیت 200 (OK) باز می‌گردد. کد 201 به کلاینت اطلاع می‌دهد که رکورد جدید با موفقیت ثبت شده است، و کد 200 از بروز رسانی موفقیت آمیز خبر می‌دهد. هنگام تولید سرویس‌های REST-based بهتر است همیشه کد وضعیت مناسبی تولید کنید.

پس از این مراحل، آژانس مسافرتی و رزرواسیون جدیدی می‌سازیم و آنها را به سرویس ارسال می‌کنیم. سپس نام آژانس مسافرتی دوم را تغییر می‌دهیم، و یکی از رزرواسیون‌ها را از آژانس اولی به آژانس دومی منتقل می‌کنیم. اینبار هنگام فراخوانی متد Update تمام موجودیت‌ها شناسه ای بزرگتر از 1 دارند، بنابراین وضعیت EntityState آنها را به Modified تغییر می‌دهیم تا هنگام ثبت تغییرات دستورات بروز رسانی مناسب تولید و اجرا شوند.

در آخر کلاینت ما متد Retreive را روی سرویس فراخوانی می‌کند. این فراخوانی با کمک متد GetAsync انجام می‌شود که روی آبجکت HttpClient تعریف شده است. فراخوانی این متد تمام آژانس‌های مسافرتی بهمراه رزرواسیون‌های متناظرشان را دریافت می‌کند. در اینجا با استفاده از متد Include تمام رکوردهای فرزند را بهمراه تمام خاصیت هایشان (properties) بارگذاری می‌کنیم.

دقت کنید که مرتب کننده JSON تمام خواص عمومی (public properties) را باز می‌گرداند، حتی اگر در کد خود تعداد مشخصی از آنها را انتخاب کرده باشید.

نکته دیگر آنکه در مثال جاری از قرارداد‌های توکار Web API برای نگاشت درخواست‌های HTTP به اکشن متدها استفاده نکرده ایم. مثلا بصورت پیش فرض درخواست‌های POST به متدهایی نگاشت می‌شوند که نام آنها با "Post" شروع می‌شود. در مثال جاری قواعد مسیریابی را تغییر داده ایم و رویکرد مسیریابی RPC-based را در پیش گرفته ایم. در اپلیکیشن‌های واقعی بهتر است از قواعد پیش فرض استفاده کنید چرا که هدف Web API ارائه سرویس‌های REST-based است. بنابراین بعنوان یک قاعده کلی بهتر است متدهای سرویس شما به درخواست‌های متناظر HTTP نگاشت شوند. و در آخر آنکه بهتر است لایه مجزایی برای میزبانی کدهای دسترسی داده ایجاد کنید و آنها را از سرویس Web API تفکیک نمایید.

نظرات مطالب
Blazor 5x - قسمت هشتم - مبانی Blazor - بخش 5 - تامین محتوای نمایشی کامپوننت‌های فرزند توسط کامپوننت والد
یک نکته‌ی تکمیلی: روش ارسال داده‌ها به RenderFragmentها

در مباحث اعتبارسنجی و احراز هویت Blazor در قسمت‌های بعدی، به قطعه کد context@ داری در داخل یک RenderFragment خواهیم رسید:
<AuthorizeView>
    <Authorized>
                Hello, @context.User.Identity.Name!
    </Authorized>
</AuthorizeView>
اکنون این سؤال مطرح است که این context@ از کجا آمده‌است و چه مفهومی را دارد؟
برای پاسخ به این سؤال نیاز است با مفهوم «Templated components» در برنامه‌های Blazor آشنا شد. تا اینجا از RenderFragmentها صرفا جهت فراهم آوردن قسمت ثابتی از قالب کامپوننت جاری، توسط استفاده کننده‌ی از آن، کمک گرفتیم. اما در همان سمت استفاده کننده، امکان دسترسی به اطلاعات مهیای داخل آن فرگمنت نیز وجود دارد. برای نمونه به کدهای کامپوننت TableTemplate.razor دقت کنید:
@typeparam Titem

<table class="table">
    <thead>
        <tr>@TableHeader</tr>
    </thead>
    <tbody>
        @foreach (var item in Items)
        {
            <tr>@RowTemplate(item)</tr>
        }
    </tbody>
</table>

@code {
    [Parameter]
    public RenderFragment TableHeader { get; set; }

    [Parameter]
    public RenderFragment<TItem> RowTemplate { get; set; }

    [Parameter]
    public IReadOnlyList<TItem> Items { get; set; }
}
- این کامپوننت، قالب سفارشی ثابت هدر جدول را توسط یک RenderFragment، از بکارگیرنده‌ی خود دریافت می‌کند.
- همچنین یک RenderFragment جنریک را نیز مشاهده می‌کنید که قالب ردیف‌های جدول را تامین می‌کند. نوع جنریک قابل دسترسی در این کامپوننت، توسط دایرکتیو typeparam Titem@ تعریف شده‌ی در ابتدای آن، مشخص شده‌است.
- بنابراین هربار که ردیفی از بدنه‌ی جدول در حال رندر است، یک شیء item از نوع TItem را به قالب سفارشی تامین شده‌ی توسط بکارگیرنده‌ی خود ارسال می‌کند.
اکنون این سؤال مطرح می‌شود که چگونه می‌توان به شیء item، در سمت والد یا بکارگیرنده‌ی کامپوننت TableTemplate فوق دسترسی یافت؟
برای اینکار می‌توان از پارامتر ویژه‌ای به نام context که یک implicit parameter است و به صورت پیش‌فرض تعریف شده و مهیا است، در سمت کدهای بکارگیرند‌ه‌ی کامپوننت، استفاده کرد:
@page "/pets"
<h1>Pets</h1>
<TableTemplate Items="pets">
    <TableHeader>
        <th>ID</th>
        <th>Name</th>
    </TableHeader>
    <RowTemplate>
        <td>@context.PetId</td>
        <td>@context.Name</td>
    </RowTemplate>
</TableTemplate>

@code {
    private List<Pet> pets = new()
    {
        new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
        new Pet { PetId = 4, Name = "Salem Saberhagen" },
        new Pet { PetId = 7, Name = "K-9" }
    };

    private class Pet
    {
        public int PetId { get; set; }
        public string Name { get; set; }
    }
}
- در اینجا روش بکارگیری کامپوننت TableTemplate را در کامپوننتی دیگر مشاهده می‌کنید. توسط فرگمنت TableHeader، قالب ثابت هدر این جدول تامین شده‌است. توسط فرگمنت RowTemplate قالب پویای ردیف‌های جدول ارائه شده‌است.
- همچنین در اینجا پارامتر ویژه‌ای به نام context@ را نیز مشاهده می‌کنید. این پارامتر همان شیء item ای است که در حین رندر هر ردیف جدول، به فرگمنت RowTemplate ارسال می‌شود. به این ترتیب کامپوننت والد می‌تواند از اطلاعات در حال رندر توسط کامپوننت فرگمنت دار، مطلع شود و از آن استفاده کند. در این مثال، نوع context@، از نوع کلاس Pet است که سعی شده بر اساس نوع پارامتر Items ارسالی به آن، توسط کامپایلر تشخیص داده شود. حتی می‌توان این نوع را به صورت صریحی نیز مشخص کرد:
<TableTemplate Items="pets" TItem="Pet">
این مورد زمانی مفید است که چندین پارامتر جنریک را در کامپوننت تعریف کرده باشیم:
<SomeGenericComponent TParam1="Person" TParam2="Supplier" TItem=etc/>
و یا می‌توان نام پارامتر ویژه‌ی context@ را در تمام RenderFragmentهای جنریک، با ذکر ویژگی Context برای مثال به pet تغییر داد:
<TableTemplate Items="pets" Context="pet">
    <TableHeader>
        <th>ID</th>
        <th>Name</th>
    </TableHeader>
    <RowTemplate>
        <td>@pet.PetId</td>
        <td>@pet.Name</td>
    </RowTemplate>
</TableTemplate>
و یا حتی می‌توان این نام را به صورت اختصاصی به ازای هر RenderFragment جنریک، مشخص کرد. برای اینکار ویژگی Context را دقیقا بر روی RenderFragment مدنظر قرار می‌دهیم:
<TableTemplate Items="pets">
    <TableHeader>
        <th>ID</th>
        <th>Name</th>
    </TableHeader>
    <RowTemplate Context="pet">
        <td>@pet.PetId</td>
        <td>@pet.Name</td>
    </RowTemplate>
</TableTemplate>
این تغییرنام بهتر است زمانی صورت گیرد که نام پیش‌فرض context، با نام مشابه دیگری در کامپوننت جاری، تداخل پیدا کرده‌است.
مطالب
غلط یاب فارسی در دات نت با استفاده از امکانات open office

مدتی است که محصور کننده‌ای سورس باز برای امکانات غلط‌ یاب مجموعه‌ی open office در سایت code project ارائه شده است:
NHunspell - Hunspell for the .NET platform
دریافت آخرین نسخه‌ی آن از source forge

خوشبختانه کتابخانه‌ی واژه‌های فارسی هم برای اپن آفیس مهیا است.
دریافت

پس از دریافت کتابخانه‌ی فوق و همچنین فایل‌های مربوط به زبان فارسی، فقط کافی است ارجاعی به اسمبلی NHunspell.dll در برنامه اضافه شود و سپس یک مثال ساده در مورد استفاده از آن به صورت زیر خواهد بود:

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using NHunspell;

namespace testWinForms87
{
class CSpellCheck
{
public static void Test()
{
using (Hunspell hunspell = new Hunspell(@"fa_ir.aff", @"fa_ir.dic"))
{
bool correct = hunspell.Spell("دباق");
if (correct)
MessageBox.Show("مشکلی نیست!");
else
{
List<string> suggestions = hunspell.Suggest("دباق");
string result = string.Empty;
foreach (string suggestion in suggestions)
{
result += suggestion + Environment.NewLine;
}

if (result != string.Empty)
MessageBox.Show(result,"لیست پیشنهادها");
}
}
}
}
}




مطالب
چقدر سی‌شارپ را می‌شناسیم؟!
هر چند که #C به عنوان یک زبان ساده برای درک و یادگیری شناخته میشود، گاهی رفتاری غیرمنتظره را حتی برای توسعه دهنده‌های با تجربه خواهد داشت. در این نوشته مروری بر بعضی از این رفتارها و توضیح دلایل پشت آن خواهیم کرد.

Value 

اگر مقدار null مدیریت نشود، میتواند باعث ایجاد نتایج نامطلوب، یا باعث از کار افتادن برنامه شود. شئ null به خودی خود مخرب نیست؛ اما اگر بخواهیم به یکی از متدها یا خاصیت‌های آن دسترسی داشته باشیم، با استثنای معروف NullReferenceException روبرو می‌شویم. برای در امان ماندن، باید همیشه اطمینان داشته باشیم که پیش از استفاده از امکانات شئ، ارجاع آن null نباشد. در قطعه کد زیر برخی از رفتارهای null value آورده شده:
// Behavior 1 
object obj = null;
bool objValueEqual = obj.Equals(null);

// Behavior 2 
object obj = null;
Type objType = obj.GetType();

// Behavior 3
string str = (string)null;
bool strType = str is string;

// Behavior 4
int num = 5;
Nullable<int> nullableNum = 5;
bool typeEqual = num.GetType() == nullableNum.GetType();

// Behavior 5
Type inType = typeof(int);
Type nullableIntType = typeof(Nullable<int>);
bool typeEqual = inType == nullableIntType;
  • در رفتار اول هرچند که متد Equals از شی null در دسترس است و با مقدار null مقایسه شده اما در زمان اجرا پیغام خطای NullReferenceException را خواهیم داشت. 
  • در رفتار دوم هم پیغام خطا را خواهیم داشت. شئ با مقدار null، در زمان اجرا هیچ نوعی را برنمیگرداند. 
  • در رفتار سوم هر چند که مقدار null صریحا به رشته تبدیل شده و برای چاپ متغیر str پیام خطایی را نخواهیم داشت، اما متغیر strType در خروجی، false خواهد بود. همانطور که در رفتار دوم گفته شد، شیء با مقدار null هیچ نوعی را برنمیگرداند. 
  • خروجی رفتار چهارم true خواهد بود. به این صورت که هر دو از نوع System.int32 خواهند بود.
  • در رفتار پنجم اگر از نوع‌ها، خروجی جداگانه بگیریم، خواهیم دیدکه نوع int از System.int32 و <Nullable<int از نوع System.Nullable`1[System.Int32] میباشند، در نتیجه خروجی false است. اشیای nullable بعد از اینکه مقداری مشخص را دریافت کردند، به صورت یک شیء غیر nullable رفتار خواهند کرد.

مدیریت مقادیر null در سربارگذاری متدها   

        static void Main(string[] args)
        {
            Console.WriteLine(Method(null));
            Console.ReadLine();
        }
        private static string Method(object obj)
        {
            return "Object parameter";
        }
        private static string Method(string str)
        {
            return "String parameter";
        }
در قطعه کد بالا، فراخوانی متد سربارگذاری شده با مقدار ورودی null، باعث اجرای متدی میشود که پارامتر ورودی آن از نوع رشته است. تا زمانیکه یکی از پارامترها بتواند به دیگری تبدیل شود، برنامه بدون خطا کامپایل خواهد شد. اما اگر هیچ تبدیل نوعی بین پارامترها وجود نداشته باشد، کد کامپایل نخواهد شد. بین متدهای سربارگذاری شده، متدی که نوع پارامتر آن مشخص‌تر است، فراخوانی میشود. برای اینکه متد خاصی را مجبور به اجرا کنیم، باید مقدار null را پیش از ارسال، به نوع پارامتر آن متد تبدیل کنید.(object)null

رفتارهای ()Math.Round

var rounded = Math.Round(1.5); // 2
var rounded = Math.Round(2.5); // 2

var rounded = Math.Round(2.5, MidpointRounding.ToEven); // 2
var rounded = Math.Round(2.5, MidpointRounding.AwayFromZero); // 3

var value = 1.4f;
var rounded = Math.Round(value + 0.1f); // 1
متد Round از کلاس Math، ورودی را که عددی اعشاری است، گرد میکند. اگر مقدار اعشار کمتر از ۰.۵ باشد، به سمت پایین و اگر بیشتر از ۰.۵ باشد، به سمت بالا گرد میشود. اما اگر ورودی دقیقا مقدار اعشاری ۰.۵ را داشته باشد چطور؟ متد Round به صورت پیش‌فرض ورودی  را به نزدیکترین عدد زوج گرد میکند، به این دلیل خط‌های ۱ و ۲ از قطعه کد بالا، خروجی یکسان ۲ را خواهند داشت. این متد آرگومان دومی هم دارد که دو حالت MidpointRounding.ToEven و MidpointRounding.AwayFromZero را می‌توان برای آن مشخص کرد. ToEven همان رفتار پیش‌فرض متد است که ورودی را به نزدیکترین عدد زوج گرد میکند و از حالت AwayFromZero میشود برای گرد کردن ورودی به عدد بزرگتر استفاده کرد (خط ۵). 
در خط ۸ یک حالت خاص دیگر نیز داریم. انتظار میرود که خروجی، به نزدیکترین عدد زوج گرد شود و نتیجه ۲ باشد؛ مثل خط ۱، اما خروجی ۱ خواهد بود. وقتی ورودی‌ها را از نوع float در نظر بگیریم، مقدار 0.1f کمی کمتر از ۰.۱ خواهد بود و نتیجه محاسبه کمی کمتر از ۱.۵. برای پرهیز از این مسئله بهتر است ورودی متد Round را از نوع decimal در نظر بگیریم.
 

مقدار دهی اولیه کلاسها 

پیشنهاد میشود برای جلوگیری از وقوع استثناءها از مقدار دهی اولیه کلاسها در سازنده کلاس، بخصوص اگر سازنده استاتیک داشته باشیم، پرهیز کنیم. ترتیب مقدار دهی اولیه زمانیکه از یک کلاس یه وهله ساخته میشود، به قرار زیر است:
  • فیلدهای استاتیک (زمانیکه کلاس برای اولین بار در دسترس قرار میگیرد)
  • سازنده استاتیک (زمانیکه کلاس برای اولین بار در دسترس قرار میگیرد)
  • فیلدهایی از کلاس که در نمونه ساخته شده در دسترس قرار میگیرند.
  • سازنده کلاس که در زمان ایجاد یک نمونه از کلاس در دسترس قرار میگیرد.
در قطعه کد زیر اگر نمونه‌ای از کلاس FailingClass ساخته شود، انتظار میرود که خطای InvalidOperationException صادر شود؛ اما برنامه با خطای TypeInitializationException متوقف میشود. در واقع در زمان اجرا به صورت خودکار خطای TypeInitializationException، خطای InvalidOperationException را پوشش میدهد. اگر بجای  InvalidOperationException یک دستور ساده WriteLine داشته باشیم، سازنده کلاس FailingClass مجال کامل شدن را خواهد داشت. اما با خطایی که داخل سازنده صادر کرده‌ایم، سازنده کلاس بدون اینکه به طور کامل به پایان برسد، متوقف خواهد شد. 
    public static class Config
    {
        public static bool ThrowException { get; set; } = true;
    }

    public class FailingClass
    {
        static FailingClass()
        {
            if (Config.ThrowException)
            {
                throw new InvalidOperationException();
            }
        }
    }
حال که میدانیم خطای اصلی که در این مواقع صادر میشود چیست، شاید بخواهیم به روش زیر آن را مدیریت کنیم.
try
{
   var failedInstance = new FailingClass();
}
catch (TypeInitializationException) { }

Config.ThrowException = false;
var instance = new FailingClass();
اگر قطعه کد بالا را بدون بخش try  اجرا کنیم، برنامه ابتدا صدور خطا را false میکند و بدون مشکل از کلاس نمونه‌ای ساخته میشود. اما اگر بخش try را داشته باشیم، هر چند که خطا در بخش try گرفته میشود و تنظیم صدور خطا false است، باز هم در خط آخر و در زمان ایجاد یک نمونه از کلاس، پیام خطای TypeInitializationException خواهیم داشت. علت آن است که سازنده استاتیک کلاس فقط یک بار فراخوانی میشود و اگر در این فراخوانی خطایی رخ دهد، این خطا در اثر ایجاد سایر نمونه‌ها و یا استفاده مستقیم از کلاس، مجددا صادر خواهد شد. در نتیجه این کلاس تا زمانیکه پردازش آن در جریان است، غیرقابل استفاده خواهد بود. یک مثال دیگر از ترتیب فراخوانی‌ها را بررسی میکنیم.
public class BaseClass
{
    {
        public BaseClass()
        {
            VirtualMethod(1);
        }
        public virtual int VirtualMethod(int dividend)
        {
            return dividend / 1;
        }
    }

    public class DerivedClass : BaseClass
    {
        int divisor;
        public DerivedClass()
        {
            divisor = 1;
        }
        public override int VirtualMethod(int dividend)
        {
            return base.VirtualMethod(dividend / divisor);
        }
    }
در قطعه کد بالا هر چند که همه چیز درست به نظر میرسد، اما اگر از کلاس DerivedClass نمونه‌ای ساخته شود، با پیام خطای DivideByZeroException مواجه میشویم. علت این مشکل ترتیب مقدار دهی اولیه در کلاسهای فرزند است. ابتدا فیلدهای کلاس فرزند مقدار دهی میشوند و بعد فیلدهای کلاس پایه، بعد سازنده کلاس پایه فراخوانی میشود و پس از آن سازنده کلاس فرزند. ترتیب فراخوانی‌ها به همین جا محدود نمیشود. 
در مثال بالا متد VirtualMethod که در سازنده کلاس پایه فراخوانی شده، پیش از این که کد داخل خود را اجرا کند، متد VirtualMethod را در کلاس فرزند، فراخوانی میکند و کلاس فرزند مجالی را برای مقدار دهی متغیر divisor، در سازنده خود نخواهد داشت. در نتیجه مقدار این متغیر در متد VirtualMethod صفر خواهد ماند و باعث صدور استثناء میشود. برای پرهیز از چنین مشکلاتی بهتر است فیلدهای یک کلاس به صورت مستقیم مقدار دهی اولیه بشوند. مقدار دهی اولیه و یا فراخوانی متدهای virtual در سازنده کلاس‌ها میتواند باعث بروز رفتارهای پیش بینی نشده‌ای شوند.

چند ریختی 

 چند ریختی قابلیتی است برای کلاسهای متفاوت تا بتوانند یک اینترفیس مشابه را به صورت‌های مختلفی پیاده‌سازی کنند. اما قطعه کد زیر قاعده چند ریختی را نقض میکند. 
 class Program
    {
        static void Main(string[] args)
        {
            var instance = new DerivedClass();
            var result = instance.Method();
            result = ((BaseClass)instance).Method();
            Console.WriteLine(instance + " -> " + result); // Derived Class ...  -> Method in BaseClass
            Console.ReadLine();

        }
    }

    public class BaseClass
    {
        public virtual string Method()
        {
            return "Method in BaseClass";
        }
    }

    public class DerivedClass : BaseClass
    {
        public override string ToString()
        {
            return "Derived Class ... ";
        }

        public new string Method()
        {
            return "Method in DerivedClass";
        }
    }
در خروجی کنسول هرچند که Instance همچنان وهله‌ای از DerivedClass است اما به دلیل تبدیل در خط ۷، Method کلاس DerivedClass به وسیله کلاس پایه پنهان شده و Method کلاس پایه فراخوانی میشود. در قطعه کد زیر حالت مشابه‌ای را که در بالا داشتیم، برای interface‌ها دیده میشود.
class Program
    {
        static void Main(string[] args)
        {
            var instance = new DerivedClass();
            var result = instance.Method(); // -> Method in DerivedClass
            result = ((IInterface)instance).Method(); // -> Method belonging to IInterface
            Console.WriteLine(result);
            Console.ReadLine();
        }
    }

    public interface IInterface
    {
        string Method();
    }

    public class DerivedClass : IInterface
    {
        public string Method()
        {
            return "Method in DerivedClass";
        }
        string IInterface.Method()
        {
            return "Method belonging to IInterface";
        }
}
هرچند که به نظر میرسد دلیلی برای استفاده از روشهای گفته شده وجود ندارد، اما اگر بخواهیم بیش از یک پیاده‌سازی را برای یک متد در یک کلاس داشته باشیم، میتواند مورد توجه قرار گیرد. بخصوص اگر نیاز باشد که پیاده‌سازی دوم خودش به طور مستقلی در کلاسی دیگر استفاده شود.

Iterators 

Iterator‌ها (تکرار شونده‌ها) ساختارهایی هستند که برای حرکت در عناصر یک collection استفاده میشوند. عموما از دستور foreach استفاده و نوع جنریک <IEnumerable<T را نمایندگی میکنند. هر چند که استفاده از آنها ساده است، اما اگر کارکرد داخلی iteratorها را درک نکنیم ممکن است به دام استفاده نادرست از آنها گرفتار شویم. در قطعه کد زیر کلاس Test صدا زده میشود و مقادیر یک تا پنج به صورت یک IEnumerable از داخل بلوک using بازگشت داده میشود. 
private IEnumerable<int> GetEnumerable(StringBuilder log)
{
     using (var test = new Test(log))
      {
          return Enumerable.Range(1, 5);
      }
}

فرض کنیم کلاس Test اینترفیس IDisposable را پیاده‌سازی کرده و در سازنده و متد Dispose خود پیامهایی را به log اضافه کند. در مثالهای واقعی، کلاس Testمیتواند اتصالی به پایگاه داده باشد و رکوردهای خوانده شده، بازگشت داده شوند. توسط حلقه زیر مقدار خروجی تابع را چاپ میکنیم.
var log = new StringBuilder();
            
foreach (var number in GetEnumerable(log))
{
     log.AppendLine($"{number}");
}
انتظار میرود که خروجی به این صورت باشد که ابتدا رشته Created (از سازنده کلاس Test) چاپ شود بعد اعداد یک تا پنج و در نهایت رشته Disposed (از متد Dispose کلاس Test). به عبارتی در ابتدای کار، بلوک using، سازنده کلاس را فراخوانی کند و بعد از اینکه بلوک به پایان کارش رسید متد Dispose کلاس فراخوانی شود. اما در واقع خروجی به صورت زیر خواهد بود. 
Created
Disposed
1
2
3
4
5
این تفاوت در دنیای واقعی مهم است؛ به اینصورت که مثلا اتصال به پایگاه داده قبل از اینکه داده‌ها خوانده شوند، بسته میشود و قطعه کد به درستی عمل نخواهد کرد. تنها راه حل، پیمایش در collection داخل using و بازگشت هر مقدار به صورت مجزا است، که در زیر آمده است.
 using (var test = new Test(log))
 {
     foreach (var i in Enumerable.Range(1,5))
     {
         yield return i;
     }
 }
فقط در این صورت است که کلاس Test بعد از اتمام کار حلقه و در زمان درست به پایان میرسد. توسط کلمه کلیدی yield و برای متدی که خروجی قابل پیمایش داشته باشد میتوان چندین مقدار را بازگشت داد. ترتیب اجرای دستورات در قطعه کد بالا به این صورت است که ابتدا نمونه‌ای از کلاس Test ایجاد میشود و سازنده کلاس فراخوانی میشود، سپس حلقه foreach به تعداد مشخص شده در Range مقادیر بازگشتی را در خروجی تابع قرار میدهد. وقتی که کار حلقه تمام شد، بلوک using دستورات را ادامه خواهد داد که برابر با خاتمه دادن به تمام نمونه‌ها و منابع استفاده شده در بلوک است؛ یعنی فراخوانی متد Dispose. با استفاده از این روش خروجی به شکل زیر خواهد بود. 
Created
1
2
3
4
5
Disposed

نظرات مطالب
C# 7.1 - Tuple Name Inference
استفاده از tuple برای انجام مبادله  متغیرها
 public void Swap()
 {
      int x = 5;
      int y = 50;
      (x, y) = (y, x);
 }
 
مطالب
معرفی List Patterns Matching در C# 11
در C# 11، افزونه‌ای به switch expressionها اضافه شده‌است که امکان بررسی توالی مقادیر آرایه‌ها و مجموعه‌ها را نیز می‌دهد که به آن list expressions هم می‌گویند. List Patterns امکان بررسی شکل یک لیست و یا آرایه را ممکن می‌کنند. برای مثال اگر نیاز است بررسی کنیم که آیا مجموعه‌ای با یک مقدار خاص، شروع می‌شود، پایان می‌یابد و یا حاوی آن است، List Patterns مفید واقع خواهند شد. در اینجا List Patterns، با [] مشخص می‌شوند و در بین []ها، توالی مقادیری را که قرار است با اعضای مجموعه‌ی مشخص شده، انطباق داده شوند، مشخص می‌کنیم. این افزونه به همراه ویژگی slice pattern نیز هست که امکان انطباق با صفر و یا چند المان یک مجموعه را میسر می‌کند. در این حالت از دو نقطه برای نمایش آن در بین []ها استفاده می‌شود. برای مثال الگوی زیر:
[1, 2, .., 10]
با تمام آرایه‌های زیر انطباق دارد:
int[] arr1 = { 1, 2, 10 };
int[] arr2 = { 1, 2, 5, 10 };
int[] arr3 = { 1, 2, 5, 6, 7, 8, 9, 10 };

بررسی چند مثال جهت آشنایی با مفهوم List Patterns

ابتدا مجموعه‌ی زیر را در نظر بگیرید:
int[] collection = { 1, 2, 3, 4 };

الف) روش انطباق با یک توالی مشخص
Console.WriteLine(collection is [1, 2, 3, 4]); // True
Console.WriteLine(collection is [1, 2, 4]); // False
توالی مشخص شده‌ی در الگوی اول، دقیقا با توالی عناصر آرایه انطباق دارد. اما در حالت دوم، چون توالی اعداد الگوی مشخص شده، با توالی اعداد آرایه یکی نیست، انطباقی رخ نداده‌است.

ب) امکان استفاده از discard و همچنین لیستی از عناصر
Console.WriteLine(collection is [_, 2, _, 4]); // True
Console.WriteLine(collection is [.., 3, _]); // True
- اگر نیاز به صرفنظر کردن از عناصر خاصی در یک توالی بود، می‌توان از discard و یا همان _ استفاده کرد؛ مانند الگوی اول. الگوی اول به معنای نیاز به انطباق با چهار عدد است که حتما باید دومین و چهارمین آن‌ها اعداد 2 و 4 باشند؛ اما مقدار اولین و سومین آن‌ها، مهم نیست.
- الگوی دوم به معنای تعریف یک توالی نامشخص، اما خاتمه یافته‌ای با عنصر 3 است و سپس صرفنظر کردن از آخرین عنصر آرایه.

در مثال زیر، الگوی انطباق با مجموعه‌ای که حداقل دو عضو دلخواهی را دارد، مشاهده می‌کنید:
if (new[] { 6, 7, 8 } is [_, _, ..])
{
   Console.WriteLine($"collection with at least two items");
}
و الگوی انطباق با مجموعه‌ای که اولین و آخرین عضو آن صفر هستند:
if (new[] { 0, 42, 42, 0 } is [0, .., 0])
{
   Console.WriteLine($"collection with first and last element equal to 0");
}


ج) امکان تعریف اعمال منطقی
Console.WriteLine(collection is [_, >= 2, _, _]); // True
بر اساس این الگو، هر مجموعه‌ی چهارتایی که عنصر دوم آن، بزرگتر و یا مساوی 2 باشد، معتبر شناخته می‌شود؛ صرفنظر از مقدار سایر عناصر آن.

در مثال زیر، الگوی انطباق با مجموعه‌ای را که اولین عضو آن یک عدد مثبت است، مشاهده می‌کنید:
if (new[] { 9, -1, -2 } is [> 0, ..])
{
   Console.WriteLine($"collection with positive first element");
}
و یا الگوی انطباق با مجموعه‌ای که دومین عضو آن، یکی از دو عدد 42 و منهای 42 می‌تواند باشد:
if (new[] { 1, 42, 0 } is [_, 42 or -42, ..])
{
   Console.WriteLine($"collection with second element equal to 42 or -42");
}


یک مثال دیگر: بررسی نحوه‌ی عملکرد List Patterns

namespace CS11Tests;

public static class ListPatternsMatching
{
    public static void Test()
    {
        Console.WriteLine(CheckSwitch(new[] { 1, 2, 10 }));          // prints 1
        Console.WriteLine(CheckSwitch(new[] { 1, 2, 7, 3, 3, 10 })); // prints 1
        Console.WriteLine(CheckSwitch(new[] { 1, 2 }));              // prints 2
        Console.WriteLine(CheckSwitch(new[] { 1, 3 }));              // prints 3
        Console.WriteLine(CheckSwitch(new[] { 1, 3, 5 }));           // prints 4
        Console.WriteLine(CheckSwitch(new[] { 2, 5, 6, 7 }));        // prints 50
    }

    public static int CheckSwitch(int[] values)
        => values switch
        {
            [1, 2, .., 10] => 1,
            [1, 2] => 2,
            [1, _] => 3,
            [1, ..] => 4,
            [..] => 50
        };
}
توضیحات:

- اولین الگوی تعریف شده‌ی در متد CheckSwitch، به معنای انطباق با هر توالی است که با 1 و 2 شروع می‌شود و سپس می‌تواند شامل هر نوع توالی دلخواهی باشد (صرفنظر از مقدار و یا ترتیب این مقادیر) و در نهایت با عدد 10 خاتمه پیدا می‌کند.
- دومین الگوی تعریف شده، تنها یک آرایه‌ی دو عضوی با مقادیر مشخص 1 و 2 را می‌پذیرد.
- توالی قابل انطباق با سومین الگوی تعریف شده، از دو عضو تشکیل می‌شود. اولین عضو آن حتما باید 1 باشد و مقدار دومین عضو آن مهم نیست.
- توالی قابل انطباق با چهارمین الگوی تعریف شده، از یک یا چند عضو دلخواه تشکیل می‌شود که اولین عضو آن حتما باید عدد 1 باشد.
- هر توالی تعریف شده‌ای با پنجمین الگوی تعریف شده، انطباق پیدا می‌کند.


امکان ترکیب list pattern matching و object pattern matching

در مثال‌های زیر، نمونه‌ای از ترکیب list pattern matching و object pattern matching را جهت ساخت شرط‌های پیچیده‌ای، مشاهده می‌کنید:
if (new[] { 1, 2, 3 } is [var first, _, _])
{
   Console.WriteLine($"three item collection with first item {first}");
}

if (new[] { 4, 5, 6 } is [_, var second, _])
{
   Console.WriteLine($"three item collection with second item {second}");
}
این الگو که var pattern هم نامیده می‌شود، به همراه ذکر var و نام یک متغیر است. در این حالت کار الگو، دریافت مقدار واقع شده‌ی در آن موقعیت خاص است.
نمونه مثالی از این قابلیت جهت جدا سازی اجزای یک URL:
var uri = new Uri("http://www.mysite.com/categories/category-a/sub-categories/sub-category-a.html");
var result = uri.Segments switch
{
    ["/"] => "Root",
    [_, var single] => single,
    [_, .. string[] entries, _] => string.Join(" > ", entries)
};


سایر نوع‌هایی که توسط List patterns قابل بررسی هستند

List patterns تنها با آرایه‌ها و لیست‌ها کار نمی‌کنند. بلکه می‌توان از آن‌ها با هر نوعی که به همراه تعریف indexer‌ها و یا خواص Length و Count است نیز استفاده کرد. اگر نیاز به استفاده از Slice patterns بود، این الگو با نوع‌هایی کار می‌کند که دارای indexer هایی با آرگومان‌هایی از نوع Range است و یا به همراه متد Slice دارای دو آرگومان Int است. برای مثال رشته‌ها نیز در اینجا قابل بررسی هستند.
مطالب
کوئری هایی با قابلیت استفاده ی مجدد
با توجه به اصل Dry تا می‌توان باید از نوشتن کدهای تکراری خودداری کرد و کد‌ها را تا جایی که ممکن است به قسمت هایی با قابلیت استفاده‌ی مجدد تبدیل کرد. حین کار کردن با ORM‌های معروف مثل NHibernate و EntityFramework زمان زیادی نوشتن کوئری‌ها جهت واکشی داده‌ها از دیتابیس صرف می‌شود. اگر بتوان کوئری هایی با قابلیت استفاده‌ی مجدد نوشت علاوه بر کاهش زمان توسعه قابلیت هایی قدرتمندی مانند زنجیر کردن کوئری‌ها به دنبال هم به دست می‌آید. 
با یک مثال نحوه‌ی نوشتن و مزایای کوئری با قابلیت استفاده‌ی مجدد را بررسی می‌کنیم : 
برای مثال دو جدول شهر‌ها و دانش آموزان را درنظر بگیرید:
namespace ReUsableQueries.Model
{
    public class Student
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
    [ForeignKey("BornInCityId")]
        public virtual City BornInCity { get; set; }
        public int BornInCityId { get; set; }
    }

    public class City
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public virtual ICollection<Student> Students { get; set; }
    }
}
در ادامه این کلاس‌ها را در معرض دید EF Code first قرار داده:
using System.Data.Entity;
using ReUsableQueries.Model;

namespace ReUsableQueries.DAL
{
    public class MyContext : DbContext
    {
        public DbSet<City> Cities { get; set; }
        public DbSet<Student> Students { get; set; }
    }
}

و همچنین تعدادی رکورد آغازین را نیز به جداول مرتبط اضافه می‌کنیم:
    public class Configuration : DbMigrationsConfiguration<MyContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
        }
        protected override void Seed(MyContext context)
        {
            var city1 = new City { Name = "city-1" };
            var city2 = new City { Name = "city-2" };
            context.Cities.Add(city1);
            context.Cities.Add(city2);
            var student1 = new Student() {Name = "Shaahin",LastName = "Kiassat",Age=22,BornInCity = city1};
            var student2 = new Student() { Name = "Mehdi", LastName = "Farzad", Age = 31, BornInCity = city1 };
            var student3 = new Student() { Name = "James", LastName = "Hetfield", Age = 49, BornInCity = city2 };
            context.Students.Add(student1);
            context.Students.Add(student2);
            context.Students.Add(student3);
            base.Seed(context);
        }
    }
فرض کنید قرار است یک کوئری نوشته شود که در جدول دانش آموزان بر اساس نام ، نام خانوادگی و سن جستجو کند : 
         var context = new MyContext();
          var query= context.Students.Where(x => x.Name.Contains(name)).Where(x => x.LastName.Contains(lastName)).Where(
              x => x.Age == age);
احتمالا هنوز کسانی هستند که فکر می‌کنند کوئری‌های LINQ همان لحظه که تعریف می‌شوند اجرا می‌شوند اما اینگونه نیست . در واقع این کوئری فقط یک Expression از رکورد‌های جستجو شده است و تا زمانی که متد ToList یا ToArray روی آن اجرا نشود هیچ داده ای برگردانده نمی‌شود.
در یک برنامه‌ی واقعی داده‌های باید به صورت صفحه بندی شده و مرتب شده برگردانده شود پس کوئری به این صورت خواهد بود : 
        var query= context.Students.Where(x => x.Name.Contains(name)).Where(x => x.LastName.Contains(lastName)).Where(
              x => x.Age == age).OrderBy(x=>x.LastName).Skip(skip).Take(take);
ممکن است بخواهیم در متد دیگری در لیست دانش آموزان بر اساس نام ، نام خانوادگی ، سن و شهر جستجو کنیم و سپس خروجی را اینبار بر اساس سن مرتب کرده و صفحه بندی نکنیم:
          var query = context.Students.Where(x => x.Name.Contains(name)).Where(x => x.LastName.Contains(lastName)).Where
              (
                  x => x.Age == age).Where(x => x.BornInCityId == 1).OrderBy(x => x.Age);
همانطور که می‌بینید قسمت هایی از این کوئری با کوئری هایی که قبلا نوشتیم یکی است ، همچنین حتی ممکن است در قسمت دیگری از برنامه نتیجه‌ی همین کوئری را به صورت صفحه بندی شده لازم داشته باشیم.
اکنون نوشتن این کوئری‌ها میان کد های Business Logic باعث شده هیچ استفاده‌ی مجددی نتوانیم از این کوئری‌ها داشته باشیم. حال بررسی می‌کنیم که چگونه می‌توان کوئری هایی با قابلیت استفاده‌ی مجدد نوشت : 
namespace ReUsableQueries.Quries
{
    public static class StudentQueryExtension
    {
        public static IQueryable<Student> FindStudentsByName(this IQueryable<Student> students,string name)
        {
            return students.Where(x => x.Name.Contains(name));
        }
        public static IQueryable<Student> FindStudentsByLastName(this IQueryable<Student> students, string lastName)
        {
            return students.Where(x => x.LastName.Contains(lastName));
        }
        public static IQueryable<Student> SkipAndTake(this IQueryable<Student> students, int skip , int take)
        {
            return students.Skip(skip).Take(take);
        }
        public static IQueryable<Student> OrderByAge(this IQueryable<Student> students)
        {
            return students.OrderBy(x=>x.Age);
        }
    }
}
همان طور که مشاهده می‌کنید به کمک متد‌های الحاقی برای شیء IQueryable<Student> چند کوئری نوشته ایم . اکنون در محل استفاده از کوئری‌ها می‌توان این کوئری‌ها را به راحتی به هم زنجیر کرد. همچنین اگر روزی قرار شد منطق یکی از کوئری‌ها عوض شود با عوض کردن آن در یک قسمت برنامه همه جا اعمال می‌شود.  نحوه‌ی استفاده از این متدهای الحاقی به این صورت خواهد بود : 
     var query = context.Students.FindStudentsByName(name).FindStudentsByLastName(lastName).SkipAndTake(skip,take);          
فرض کنید قرار است یک سیستم جستجوی پیشرفته به برنامه اضافه شود که بر اساس شرط‌های مختلف باید یک شرط در کوئری اعمال شود یا نشود ، به کمک این طراحی جدید به راحتی می‌توان بر اساس شرط‌های مختلف یک کوئری را اعمال کرد یا نکرد : 
        var query = context.Students.AsQueryable();
          if (searchByName)
          {
              query= query.FindStudentsByName(name);
          }
          if (orderByAge)
          {
              query = query.OrderByAge();
          }
          if (paging)
          {
             query =  query.SkipAndTake(skip, take);
          }
          return query.ToList();
همچنین این کوئری‌ها وابسته به ORM خاصی نیستند البته این نکته هم مد نظر است که LINQ Provider بعضی ORM‌ها ممکن است بعضی کوئری‌ها را پشتیبانی نکند.

مطالب
اشیاء Enumerable و Enumerator و استفاده از قابلیت‌های yield (قسمت اول)

در این مقاله می‌خواهیم نحوهٔ ساخت اشیایی با خصوصیات Enumerable را بررسی کنیم. بررسی ویژگی این اشیاء دارای اهمیت است حداقل به این دلیل که پایهٔ یکی از قابلیت مهم زبانی سی‌شارپ یعنی LINQ هستند. برای یافتن پیش‌زمینه‌ای در این موضوع خواندن این مقاله‌های بسیار خوب (۱ و  ۲) نیز توصیه می‌شود. 

Enumerableها

اشیاء Enumerable یا به‌عبارت دیگر اشیائی که اینترفیس IEnumerable را پیاده‌سازی می‌کنند، دامنهٔ گسترده‌ای از Collectionهای CLI را شامل می‌شوند. همانطور که در نمودار زیر نیز می‌توانید مشاهده کنید IEnumerable (از نوع غیر Generic آن) در بالای سلسله مراتب اینترفیس‌های Collectionهای CLI قرار دارد: 

درخت اینترفیس‌های Collectionها در سی‌شارپ

درخت اینترفیس‌های Collectionها در CLI منبع

IEnumerableها همچنین دارای اهمیت دیگری نیز هستند؛ قابلیت‌های LINQ که از دات‌نت ۳.۵ به دات‌نت اضافه شدند به‌عنوان Extensionهای این اینترفیس تعریف شده‌اند و پیاده‌سازی Linq to Objects را می‌توانید در کلاس استاتیک System.Linq.Enumerable در System.Core مشاهده کنید. (می‌توانید برای دیدن آن را با ILDasm یا Reflector باز کنید یا پیاده‌سازی آزاد آن در پروژهٔ Mono را اینجا مشاهده کنید که برای شناخت بیشتر LINQ واقعاً مفید است.)

همچنین این Enumerableها هستند که foreach را امکان‌پذیر می‌کنند. به عبارتی دیگر هر شئ‌ای که قرار باشد در foreach (var x in object) قرار بگیرد و بدین طریق اشیاء درونی‌اش را برای پیمایش یا عملی خاص قرار دهد باید Enumerable باشد.

همانطور که قبلاً هم اشاره شد IEnumerable از نوع غیر Generic در بالای نمودار Collectionها قرار دارد و حتی IEnumerable از نوع Generic نیز باید آن را پشتیبانی کند. این موضوع به احتمال به این دلیل در طراحی لحاظ شد که مهاجرت به .NET 2.0 که قابلیت‌های Generic را افزوده بود ساده‌تر کند. IEnumerable همچنین قابلیت covariance که از قابلیت‌های جدید C# 4.0 هست را دارا است (در اصل IEnumerable دارای Generic از نوع out است).

Enumerableها همانطور که از اسم اینترفیس IEnumerable انتظار می‌رود اشیایی هستند که می‌توانند یک شئ Enumerator که IEnumerator را پیاده‌سازی کرده‌است را از خود ارائه دهند. پس طبیعی است برای فهم و درک دلیل وجودی  Enumerable باید Enumerator را بررسی کنیم.

Enumeratorها

Enumerator شئ است که در یک پیمایش یا به‌عبارت دیگر گذر از روی تک‌تک عضوها ایجاد می‌شود که با حفظ موقعیت فعلی و پیمایش امکان ادامهٔ پیمایش را برای ما فراهم می‌آورد. اگر بخواهید آن را در حقیقت بازسازی کنید شئ Enumerator به‌مانند کاغذ یا جسمی است که بین صفحات یک کتاب قرار می‌دهید که مکانی که در آن قرار دارید را گم نکنید؛ در این مثال، Enumerable همان کتاب است که قابلیت این را دارد که برای پیمایش به وسیلهٔ قرار دادن یک جسم در وسط آن را دارد.

حال برای اینکه دید بهتری از رابطهٔ بین Enumerable و Enumerator از نظر برنامه‌نویسی به این موضوع پیدا کنیم یک کد نمونهٔ عملی را بررسی می‌کنیم.

در اینجا نمونهٔ ساده و خوانایی از استفاده از یک List برای پیشمایش تمامی اعداد قرار دارد:

List<int> list = new List<int>();
list.Add(1);
list.Add(2);
list.Add(3);
foreach (int i in list) 
{
    Console.WriteLine(i);
}

همانطور که قبلاً اشاره foreach نیاز به یک Enumerable دارد و List هم با پیاده‌سازی IList که گسترشی از IEnumerable  هست نیز یک نوع Enumerable هست. اگر این کد را Compile کنیم و IL آن را بررسی کنیم متوجه می‌شویم که CLI در اصل چنین کدی را برای اجرا می‌بینید:

List<int> list = new List<int>();
list.Add(1);
list.Add(2);
list.Add(3);
IEnumerator<int> listIterator = list.GetEnumerator();
while (listIterator.MoveNext())
{
    Console.WriteLine(listIterator.Current);
}
listIterator.Dispose();

(می‌توان از using استفاده نمود که Dispose را خود انجام دهد که اینجا برای سادگی استفاده نشده‌است.)

همانطور که می‌بینیم یک Enumerator برای Enumerable ما (یعنی List) ایجاد شد و پس از آن با پرسش این موضوع که آیا این پیمایش امکان ادامه دارد، کل اعضا پیموده‌شده و عمل مورد نظر ما بر آن‌ها انجام شده‌است.

خب، تا اینجای کار با خصوصیات و اهمیت  Enumerator‌ها و  Enumerable‌ها آشنا شدیم، حال نوبت به آن می‌رسد که بررسی کنیم آن‌ها را چگونه می‌سازند و بعد از آن با کاربردهای فراتری از آن‌ها نسبت به پیمایش یک  List آشنا شویم.

ساخت Enumeratorها و Enumerableها

همانطور که اشاره شد ایجاد اشیاء Enumerable به اشیاء Enumerator مربوط است، پس ما در یک قطعه کد که پیمایش از روی یک آرایه را فراهم می‌آورد ایجاد هر دوی آن‌ها و رابطهٔ بینشان را بررسی می‌کنیم.

    public class ArrayEnumerable<T> : IEnumerable<T>
    {
        private T[] _array;
        public ArrayEnumerable(T[] array)
        {
            _array = array;
        }


        public IEnumerator<T> GetEnumerator()
        {
            return new ArrayEnumerator<T>(_array);
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }

    public class ArrayEnumerator<T> : IEnumerator<T>
    {
        private T[] _array;
        public ArrayEnumerator(T[] array)
        {
            _array = array;
        }

        public int index = -1;

        public T Current { get { return _array[index]; } }

        object System.Collections.IEnumerator.Current { get { return this.Current; } }

        public bool MoveNext()
        {
            index++;
            return index < _array.Length;
        }

        public void Reset()
        {
            index = 0;
        }

        public void Dispose() { }
    }

ادامه

مطالب
نوع‌های نال نپذیر در TypeScript
تا پیش از ارائه‌ی کامپایلر TypeScript 2.0، مقادیر null و undefined، به هر نوعی قابل انتساب بودند و امکان تفکیک آن‌ها وجود نداشت که این مورد می‌تواند منشاء بروز بسیاری از خطاهای در زمان اجرا شود.
let name: string;
name = "Vahid"; // OK
name = null; // OK
name = undefined;  // OK
let age: number;
age = 24; // OK
age = null; // OK
age = undefined;  // OK
برای نمونه در اینجا یک متغیر رشته‌ای و همچنین عددی تعریف شده‌اند که انتساب null و یا undefined نیز به آن‌ها مجاز است. این مورد جهت نوع‌های ورودی و خروجی متدها، اشیاء و آرایه‌ها نیز میسر است.


نوع null در TypeScript

همانند JavaScript، نوع null تنها یک مقدار معتبر نال را می‌تواند داشته باشد و نمی‌توان برای مثال یک رشته را به آن انتساب داد. اما انتساب این مقدار به هر نوع متغیر دیگری، سبب پاک شدن مقدار آن خواهد شد. با فعالسازی strictNullChecks، این نوع را تنها به نوع‌های نال‌پذیر می‌توان انتساب داد.


نوع undefined در TypeScript

هر متغیری که مقداری به آن انتساب داده نشده باشد، با undefined مقدار دهی می‌شود. این مورد حتی جهت خروجی متدها نیز صادق است و اگر return ایی در آن‌ها فراموش شود، این خروجی نیز به undefined تفسیر می‌شود.
در اینجا نیز اگر نوع متغیری به undefined تنظیم شد، این متغیر تنها مقدار undefined را می‌تواند بپذیرد. تنها با خاموش کردن پرچم strictNullChecks می‌توان آن‌را به اعداد، رشته‌ها و غیره نیز انتساب داد.


فعالسازی نوع‌های نال نپذیر در TypeScript

برای فعالسازی این قابلیت، نیاز است پرچم strictNullChecks را در فایل تنظیمات کامپایلر به true تنظیم کرد:
{
    "compilerOptions": {
        "strictNullChecks": true
    }
}
از این پس دیگر نمی‌توان null و undefined را به هر نوعی انتساب داد و این‌ها تنها به خودشان و یا نوع any، قابل انتساب هستند. برای مثال اکنون نوع number فقط یک عدد است و دیگر قابلیت پذیرش null و یا undefined را ندارد. البته در اینجا یک استثناء هم وجود دارد: undefined را می‌توان به نوع void نیز انتساب داد.
برای مثال اگر متدی، رشته‌ای را به عنوان پارامتر قبول کند، تا پیش از TypeScript 2.0 و فعالسازی strictNullChecks آن، مشخص نبود که رشته‌ی دریافتی از آن واقعا یک رشته‌است و یا شاید null. اما اکنون یک رشته، فقط یک رشته‌است و دیگر نال پذیر نیست.
 let foo: string = null; // Error! Type 'null' is not assignable to type 'string'.
به این ترتیب دیگر به خطاهای زمان اجرایی مانند خطاهای ذیل نخواهیم رسید:
Uncaught ReferenceError: foo is not defined
Uncaught TypeError: window.foo is not a function

این مورد برای آرایه‌ها نیز صادق است:
// With strictNullChecks set to false
let d: Array<number> = [null, undefined, 10, 15]; //OK
let e: Array<string> = ["pie", null, ""];  //OK
 
 
// With strictNullChecks set to true
let d: Array<number> = [null, undefined, 10, 15]; // Error
let e: Array<string> = ["pie", null, ""]; // Error
اگر strictNullChecks فعال شود، دیگر نمی‌توان به اعضای یک آرایه مقادیر null و یا undefined را نسبت داد.


ساده سازی تعریف بررسی‌های با پرچم strict، در TypeScript 2.3

تعداد گزینه‌های قابل تنظیم در فایل tsconfig روز به روز بیشتر می‌شوند. به همین جهت برای ساده سازی فعالسازی آن‌ها، از TypeScript 2.3 به بعد، پرچم strict نیز به این تنظیمات اضافه شده‌است. کار آن فعالسازی یکجای تمام بررسی‌های strict است؛ مانند noImplicitAny، strictNullChecks و غیره.
{ 
    "compilerOptions": { 
        "strict": true  /* Enable all strict type-checking options. */ 
    } 
}
در این حالت اگر نیاز به لغو یکی از گزینه‌ها بود، می‌توان به صورت ذیل عمل کرد:
{ 
    "compilerOptions": { 
        "strict": true, 
        "noImplicitThis": false 
    } 
}
گزینه‌ی strict تمام بررسی‌های متداول را فعال می‌کند؛ اما ذکر و تنظیم صریح noImplicitThis به false، تنها این یک مورد را لغو خواهد کرد.

یک نکته: اجرای دستور tsc --init ، سبب تولید یک فایل tsconfig.json از پیش تنظیم شده، بر اساس آخرین قابلیت‌های کامپایلر TypeScript می‌شود.


اما ... اکنون چگونه یک نوع را نال‌پذیر کنیم؟

TypeScript به همراه دو نوع ویژه‌ی null و undefined نیز شده‌است که تنها دارای مقادیر null و undefined می‌توانند باشند. به این معنا که در حین تعریف نوع یک متغیر، می‌توان این دو را نیز ذکر کرد و دیگر تنها به عنوان دو مقدار مطرح نیستند. به این ترتیب می‌توان از آن‌ها یک union type را ایجاد کرد:
 let foo: string | null = null; // Okay!
اکنون تنها در این حالت است که متغیر foo می‌تواند یک رشته و یا یک null را دریافت کند و یا اگر مثال ابتدای بحث را بخواهیم اصلاح کنیم، به نمونه‌ی ذیل خواهیم رسید:
let name: string | null;
name = "Vahid"; // OK
name = null; // OK
name = undefined;  // Error
یکی دیگر از مزایای این روش، وضوح بیشتر تعریف نوع متغیرها و به نوعی «خود مستند سازی» بهتر آن‌ها است. در این حالت یا به صورت صریح مشخص می‌کنیم که متدی فقط یک رشته را می‌پذیرد و یا با ذکر string | null، به استفاده کننده اعلام می‌کنیم که ارسال null نیز به آن پیش بینی شده‌است و به نتیجه‌ی نامشخصی منتهی نخواهد شد.

یک نکته:
تا پیش از این اگر متغیری را به این صورت تعریف می‌کردیم:
let z = null;
نوع آن any درنظر گرفته می‌شد. اما اکنون، نوع آن تنها null است و تنها مقداری را هم که می‌تواند بپذیرد نال خواهد بود.


بررسی انتساب، پیش از استفاده

با فعالسازی strictNullChecks، اکنون کامپایلر برای تمام نوع‌هایی که undefined نیستند، یک مقدار اولیه را پیش از استفاده‌ی از آن‌ها درخواست می‌کند:
testAssignedBeforeUseChecking() {
    let x: number;
    console.log(x);
}
در اینجا چون x از نوع عددی است، به علت عدم مقدار دهی اولیه، قابلیت استفاده‌ی از آن وجود ندارد و کامپایلر خطای ذیل را اعلام می‌کند:
 [ts] Variable 'x' is used before being assigned.

اما در حالت ذیل، عدد z می‌تواند عدد و یا undefined باشد؛ به همین جهت کامپایلر با استفاده‌ی از آن مشکلی نخواهد داشت:
let z: number | undefined;
console.log(z);

یک نکته: خواص و پارامترهای اختیاری، به صورت خودکار دارای نوع undefined نیز هستند. برای مثال امضای متد ذیل:
method1(x?: number) {
}
با متد زیر یکی است:
method1(x?: number | undefined) {
}


اجبار به بررسی نال نبودن مقادیر، پیش از استفاده‌ی از آن‌ها در متدهای نال نپذیر

اگر پارامتر متدی یا خاصیت شیءایی نال پذیر نباشند، با ارسال مقدار نوعی به آن‌ها که می‌تواند null و یا undefined را بپذیرد، یک خطای زمان کامپایل صادر خواهد شد. در اینجا محافظ‌های نوع‌ها توسعه یافته‌اند تا اگر بررسی نال یا undefined بودن مقداری انجام شد، مشکلی در جهت استفاده‌ی از آن‌ها نباشد:
  f(x: number): string {
    return x.toString();
  }

  testTypeGuards() {
    let x: number | null | undefined;
    if (x) {
      this.f(x);  // Ok, type of x is number here
    } else {
      this.f(x);  // Error, type of x is number? here
    }
  }
در این مثال، متد f فقط یک عدد را می‌پذیرد (و نه نال و یا undefined). اما در حین کاربرد آن در متد testTypeGuards، مقدار متغیر x می‌تواند یک عدد، نال و یا undefined باشد. چون پیش از اولین استفاده‌ی از متد f در اینجا، بررسی دارای مقدار بودن این متغیر صورت گرفته‌است، فراخوانی صورت گرفته، مجاز است. اما در قسمت else این شرط، کامپایلر خطای ذیل را صادر می‌کند:
 Argument of type 'number | null | undefined' is not assignable to parameter of type 'number'.
Type 'undefined' is not assignable to type 'number'.

امکان این بررسی در مورد عبارات شرطی نیز صادق است:
getLength(s: string | null) {
   return s ? s.length : 0;
}


توسعه‌ی محافظ‌های نوع‌ها جهت کار با نوع‌های نال نپذیر

در مثال ذیل، خروجی متد isNumber دارای امضایی به همراه is است:
isNumber(n: any): n is number { // type guard
   return typeof n === "number";
}
به یک چنین متدهایی type guard گفته می‌شود که امکان بررسی یک نوع را میسر می‌کنند. از این امکان می‌توان جهت بررسی بهتر پارامترها و یا خواص اختیاری استفاده کرد:
  usedMb(usedBytes?: number): number | undefined {
    return this.isNumber(usedBytes) ? (usedBytes / (1024 * 1024)) : undefined;
  }
یک چنین بررسی، بهتر است از بررسی ذیل:
  usedMb2(usedBytes?: number): number | undefined {
    return usedBytes ? (usedBytes / (1024 * 1024)) : undefined;
  }
از این جهت که عبارت شرطی بررسی شده، مقدار صفر را نیز به صورت undefined بازگشت خواهد داد (if(0) به false تعبیر می‌شود و قسمت else این شرط فراخوانی خواهد شد).
همچنین امضای متد نیز به number | undefined تغییر یافته‌است. در غیر اینصورت، خطای زمان کامپایل Type undefined is not assignable to type number صادر خواهد شد.
در حین استفاده‌ی از یک چنین متدی، دیگر نمی‌توان به خروجی آن به صورت ذیل دسترسی یافت:
  formatUsedMb(): string {
    //ERROR: TS2531: Object is possibly undefined
    return this.usedMb(123).toFixed(0).toString();
  }
چون مقدار usedMb می‌تواند undefined باشد، باید ابتدا آن‌را بررسی کرد:
  formatUsed(): string {
    const usedMb = this.usedMb(123);
    return usedMb ? usedMb.toFixed(0).toString() : "";
  }


لغو بررسی strictNullChecks به صورت موقت

با استفاده از اپراتور ! می‌توان به کامپایلر اطمینان داد که این متغیر یا خاصیت، دارای مقدار نال نیست و نخواهد بود:
export interface User {
  name: string;
  age?: number;
}
در این اینترفیس، خاصیت age به صورت اختیاری تعریف شده‌است. برای نمایش مقدار age با فعال بودن strictNullChecks، یا باید ابتدا null نبودن آن‌را به صورت صریحی بررسی کرد:
  printUserInfo(user: User) {
    if (user.age != null) {
      console.log(`${user.name}, ${user.age.toString()}`);
    }
  }
در غیراینصورت قطعه کد ذیل با خطای 'Object is possibly 'undefined کامپایل نخواهد شد:
  printUserInfo(user: User) {
    console.log(`${user.name}, ${user.age.toString()}`);
  }

و یا می‌توان توسط اپراتور ! این بررسی را به صورت موقت خاموش کرد:
  printUserInfo(user: User) {
    console.log(`${user.name}, ${user.age!.toString()}`);
  }
البته استفاده‌ی از این اپراتور توسط tslint توصیه نمی‌شود:
 [tslint] Forbidden non null assertion (no-non-null-assertion)
چون بهتر است به کامپایلر عنوان نکنیم «قسم می‌خورم که این مقدار نال نیست»!



یک نکته‌ی تکمیلی
پس از آزمایش موفقیت آمیز نوع‌های نال نپذیر در TypeScript، مایکروسافت قصد دارد این ویژگی را به C# 8.0 نیز در مورد نوع‌های ارجاعی که می‌توانند نال پذیر باشند، اضافه کند (امکان داشتن نوع‌های ارجاعی نال‌نپذیر).