مطالب
تولید اطلاعات تصادفی توسط GenFu
گاها برای تولید اطلاعات تصادفی، خصوصا هنگام نوشتن تست‌ها، زمان زیادی بیهوده تلف شده و حجم زیادی کد اضافه تولید میشود. کتابخانه‌ای بنام GenFu ایجاد شده که وظیفه ایجاد داده‌های تصادفی را بر عهده گرفته‌است. این کتابخانه متن باز (Open Source) بوده و می‌توانید آن را از مخزن گیت‌هاب دریافت نمایید.

در مطلب جاری قصد ایجاد اطلاعات تصادفی برای کلاس زیر را داریم :
public class Person
{
    public int ID { get; set; }

    public string Firstname { get; set; }

    public string Lastname { get; set; }

    public string Email { get; set; }

    public string PhoneNumber { get; set; }

    public override string ToString()
    {
        return $"{ID}: {Firstname} {Lastname} - {Email} - {PhoneNumber}";
    }
}

نصب GenFu

برای نصب کتابخانه GenFu از دستور زیر در Package Manager Console استفاده میکنیم :
Install-Package GenFu

1. ایجاد یک شخص

برای ایجاد شخصی جدید همراه با اطلاعاتی تصادفی به شکل زیر عمل خواهیم کرد :
var person = A.New<Person>();
Console.WriteLine(person);
نتیجه کد فوق به این صورت خواهد بود :
18: Diedra Morgan - Zachary.Garcia@telus.net - (531) 273-9001
اگر دقت کنید متوجه میشوید که GenFu بصورت خودکار داده‌هایی مرتبط با Property هایی که نام گذاری کردید‌، ایجاد کرده‌است.
برای Email، داده‌ای با فرمت صحیح ایمیل و برای PhoneNumber هم شماره تلفنی با فرمت صحیح تولید شده است.

2. ایجاد چند شخص

برای ایجاد لیستی از اشخاص نیز میتوانید از متد ListOf استفاده کرده و تعداد اشخاص مورد نیازتان را به آن ارسال کنید ( پیشفرض 25 ) :
var people = A.ListOf<Person>(5);
people.ForEach(Console.WriteLine);
کد فوق باعث ایجاد 5 شخص با اطلاعات تصادفی متفاوتی خواهد شد:
97: Maria MacKenzie - Alexandra.Johnson@rogers.ca - (670) 787-3053
34: Alexander Scott - Isaiah.Price@gmail.com - (730) 645-4946
66: Kevin Perez - Gabrielle.Alexander@hotmail.com - (230) 758-8233
81: Maria Evans - Vanessa.Bell@rogers.ca - (508) 572-4343
79: Tyler Parker - Alyssa.Taylor@telus.net - (297) 357-7617

تا به اینجای کار GenFu بخوبی جوابگوی نیازهایمان بوده‌است. اما اگر پیشفرض‌ها جوابگو نبود و بخواهیم فرمت داده‌های تولید شده را تغییر دهیم چطور ؟
برای اینکار میتوانیم از متد Configure استفاده کرده و نحوه ایجاد داده را برای Property هایی که مشخص میکنیم، تغییر دهیم.

3. ایجاد چند شخص و مقدارهی یک property با مقدار ثابت

اگر بخواهیم داده‌های ایجاد شده را داخل دیتابیس لحاظ کنیم، نیاز داریم تا ID آن‌ها را برابر 0 قرار دهیم تا دیتابیس مشکلی برای ثبتشان نداشته باشد. برای ایجاد لیستی از اشخاص که ID آن‌ها برابر 0 باشد :
A.Configure<Person>().Fill(x => x.ID, 0);

var people = A.ListOf<Person>(5);
people.ForEach(Console.WriteLine);
نتیجه :
0: Darron Gonzalez - Benjamin.Daeninck@hotmail.com - (405) 418-7783
0: Melanie Garcia - Jennifer.Griffin@microsoft.com - (711) 277-8826
0: James Hughes - Tristan.Ward@live.com - (734) 400-8322
0: Miranda Torres - Ross.Davis@rogers.ca - (495) 479-8147
0: David Hughes - Jillian.Alexander@live.com - (361) 617-6642
در این حالت بدون هیچگونه مشکلی میتوانید داده‌های ایجاد شده را داخل دیتابیس ذخیره نمایید.

4. ایجاد چند شخص و مقداردهی یک property با متد

حالت دیگری که میتوانید نحوه مقداردهی یک Property را تنظیم کنید، استفاده از متد یا delegate است:
var i = 1;

A.Configure<Person>()
    .Fill(c => c.ID, () => i++);

var people = A.ListOf<Person>(5);
people.ForEach(Console.WriteLine);

نتیجه :
1: Paul Long - Carlos.Kelly@telus.net - (202) 573-6278
2: Jesse Iginla - Liberty.Moore@gmail.com - (589) 791-3606
3: Raymundo Price - Ang.Taylor@live.com - (336) 400-1601
4: Elizabeth Getzlaff - Leslie.Campbell@att.com - (662) 582-9010
5: Abigail Bailey - Tristan.Ross@live.com - (225) 661-7023
همانطور که می‌بینید، ID اشخاص بصورت تصاعدی مقداردهی شده است.

5. ایجاد چند شخص و مقداردهی یک property با مقادیر property‌های دیگر

همچنین میتوانید از مقادیر Property‌های دیگر برای مقداردهی یک Property استفاده کنید :
A.Configure<Person>()
    .Fill(c => c.ID, 0)
    .Fill(c => c.Email,
        c => $"{c.Firstname}.{c.Lastname}@gmail.com");

var people = A.ListOf<Person>(5);
people.ForEach(Console.WriteLine);
کد فوق باعث تولید اشخاصی میشود که ایمیل آن‌ها برابر (Firstname).(Lastname) خواهد بود :
0: Patrick Perry - Patrick.Perry@gmail.com - (796) 460-6576
0: Rebecca Main - Rebecca.Main@gmail.com - (757) 472-3332
0: Kimberly Carter - Kimberly.Carter@gmail.com - (436) 484-8273
0: Sara Lewis - Sara.Lewis@gmail.com - (424) 717-7682
0: Lauren Ross - Lauren.Ross@gmail.com - (277) 294-5776

6. استفاده از Extension‌های درون ساخت GenFu برای مقداردهی

GenFu دارای Extension هایی بوده که باعث میشوند اطلاعات یک Property با مقادیر قابل درک و مشخصی پر شوند.
مثال :
A.Configure<Person>()
    .Fill(x => x.Firstname).AsPersonTitle();

var people = A.ListOf<Person>(5);
people.ForEach(Console.WriteLine);  
نتیجه :
64: Miss. Ratzlaff - Bryce.Simmons@att.com - (386) 309-2414
7: Air Marshall Yarobi - Ariana.Russell@att.com - (459) 238-0717
96: Air Marshall Taylor - Luke.Olsen@gmail.com - (775) 401-5281
28: Doctor Cox - Leah.Diaz@att.com - (569) 464-7961
99: Master Phillips - Chloe.Scott@hotmail.com - (578) 221-9021

7. GenFu WireFrame

در نهایت GenFu دارای پکیج جانبی به اسم Wireframes است که شامل HTML Helper هایی است که با استفاده از آن‌ها میتوانید المان‌های HTML مانند P, Image, Table و ... را با مقادیری برای تست بعنوان Placeholder ایجاد کنید. 
برای نصب و مطالعه بیشتر درباره GenFu WireFrames این لینک را ببینید.
نظرات مطالب
بررسی مقدمات کتابخانه‌ی JSON.NET
یک نکته‌ی تکمیلی
استفاده از استریم‌ها برای کار با فایل‌ها در JSON.NET
        public static T DeserializeFromFile<T>(string filePath, JsonSerializerSettings settings = null)
        {
            if (!File.Exists(filePath))
                return default(T);

            using (var fileStream = File.OpenRead(filePath))
            {
                using (var streamReader = new StreamReader(fileStream))
                {
                    using (var reader = new JsonTextReader(streamReader))
                    {
                        var serializer = settings == null ? JsonSerializer.Create() : JsonSerializer.Create(settings);
                        return serializer.Deserialize<T>(reader);
                    }
                }
            }
        }

        public static void SerializeToFile(string filePath, object data, JsonSerializerSettings settings = null)
        {
            using (var fileStream = new FileStream(filePath, FileMode.Create))
            {
                using (var streamReader = new StreamWriter(fileStream))
                {
                    using (var reader = new JsonTextWriter(streamReader))
                    {
                        var serializer = settings == null ? JsonSerializer.Create() : JsonSerializer.Create(settings);
                        serializer.Serialize(reader, data);
                    }
                }
            }
        }
مطالب
پیاده سازی پروژه‌های React با TypeScript - قسمت چهارم - تعیین نوع هوک‌های useState و useRef
پس از بررسی روش تعیین نوع‌های خواص props در قسمت‌های قبل، اکنون نوبت به بررسی روش تعیین نوع‌های انواع React Hooks است. در این قسمت دو هوک پرکاربرد useState و useRef را بررسی می‌کنیم.


روش تعیین نوع useState Hook

برای این منظور در ابتدا فایل جدید src\components\Input.tsx را ایجاد کرده و به صورت زیر تکمیل می‌کنیم:
import React, { useState } from "react";

export const Input = () => {
  const [name, setName] = useState("");
  return <input value={name} onChange={(e) => setName(e.target.value)} />;
};
همچنین تعریف المان آن‌را نیز به فایل src\App.tsx جهت نمایش </ Input> با ذکر "import { Input } from "./components/Input، انجام می‌دهیم.

پس از این تعاریف ... برنامه بدون مشکل کار می‌کند و کامپایل می‌شود. اکنون سؤال اینجا است که آیا تایپ‌اسکریپت در ایجا اصلا کاری را هم انجام می‌دهد؟ برای درک این موضوع، سطر useState را به صورت زیر تغییر می‌دهیم:
const [name, setName] = useState(0);
بلافاصله خطای زیر ظاهر می‌شود:

عنوان می‌کند که مقدار رشته‌ای e.target.value، به مقدار عددی name قابل انتساب نیست. به عبارتی TypeScript از قابلیت Type Inference خود در اینجا استفاده می‌کند. درست است که به ظاهر نوعی را برای useState و خروجی منتسب به آن تعیین نکرده‌ایم، اما بر اساس نوع مقدار پیش‌فرض آن، نوع name و setName به صورت خودکار مشخص می‌شوند و نیازی به ذکر صریح این نوع، نیست. برای مثال در حالت اول چون مقدار پیش‌فرض useState را یک رشته‌ی خالی معرفی کرده بودیم، نوع name نیز string درنظر گرفته شده بود. در حالت دوم بر اساس مقدار پیش‌فرض عددی useState، اینبار نوع name نیز یک number خواهد بود و دیگر نمی‌توان یک مقدار رشته‌ای را مانند e.target.value به آن انتساب داد. مزیت کار کردن با TypeScript در اینجا، مشاهده‌ی آنی خطای یک چنین استفاده‌ها و انتساب‌های نادرستی است.

مفهوم Type Inference را در تصاویر زیر بهتر می‌توان مشاهده کرد. اشاره‌گر ماوس را به تعریف useState نزدیک کنید. در توضیحاتی که ظاهر می‌شود، بر اساس نوع مقدار پیش‌فرض آن، نوع آرگومان جنریک متد useState نیز به صورت خودکار تغییر می‌کند:



و نکته‌ی مهم اینجا است که نیازی به ذکر صریح این نوع جنریک، مانند مثال زیر نیست:
const [name, setName] = useState<string>("");
و سطر فوق با سطر زیر که بیانگر Type Inference است، دقیقا یکی است:
const [name, setName] = useState("");

سؤال: اگر مقدار اولیه‌ی useState را null تعیین کردیم و یا اصلا تعریف نکردیم و undefined بود، چطور؟
در یک چنین حالتی که نوع دقیق state، از طریق مقدار اولیه‌ی آن قابل استنتاج نیست، نیاز هست همانند تصاویر فوق، تعریف جنریک useState را به نحو صریحی ذکر کرده و آن‌را با union types تکمیل کنیم:
const [name, setName] = useState<string | null>(null);
به این ترتیب عنوان کرده‌ایم که نوع name در اینجا می‌تواند رشته‌ای و یا نال باشد.


روش تعیین نوع useRef Hook

در ادامه می‌خواهیم نحوه‌ی تعیین نوع DOM Elements را در React بررسی کنیم. با استفاده از useRef می‌توان به ارجاعی از یک DOM Element دسترسی یافت.
import React, { useRef, useState } from "react";

export const Input = () => {
  const [name, setName] = useState("");
  const inputRef = useRef(null);

  if (inputRef && inputRef.current) {
    console.log("ref", inputRef.current.value);
  }
  return (
    <input
      ref={inputRef}
      value={name}
      onChange={(e) => setName(e.target.value)}
    />
  );
};
برای اینکار ابتدا useRef را با یک مقدار اولیه‌ی null، توسط ویژگی ref، به یک DOM Element خاص متصل می‌کنیم. تا اینجا برنامه بدون مشکل کار می‌کند؛ اما زمانیکه خواستیم برای مثال به inputRef.current.value دسترسی پیدا کنیم، دیگر تعریف ساده‌ی useRef(null) پاسخگو نبوده و خطای زیر گزارش می‌شود:


عنوان می‌کند نوعی که inputRef.current دارد، نال است و نال به همراه خاصیت value نیست. برای اینکه نوع inputRef را بهتر بتوانیم بررسی کنیم، دقیقا بر روی آن کلیک راست کرده و گزینه‌ی Go to Type Definition را انتخاب کنید. بلافاصله به تعریف زیر هدایت خواهیم شد:
interface MutableRefObject<T> {
   current: T;
}
inputRef، از نوع MutableRefObject جنریک است که تنها دارای یک خاصیت current است. نوع T آن هم در اینجا با توجه به مقدار اولیه‌ی آن، null درنظر گرفته شده‌است. به همین جهت هرچند می‌دانیم inputRef.current به المان input صفحه اشاره می‌کند، اما نمی‌توانیم به خواص و متدهای آن دسترسی پیدا کنیم.
برای رفع این مشکل فقط کافی است نوع المان مدنظر را صریحا به عنوان آرگومان جنریک useRef معرفی کنیم:
const inputRef = useRef<HTMLInputElement>(null);
نحوه‌ی تشخیص این نوع هم ساده‌است. فقط کافی است اشاره‌گر ماوس را بر روی المانی خاص قرار دهید. بلافاصله نوع آن مشخص می‌شود:



فعال بودن Strict Null Checking در پروژه‌های TypeScript ای React

نکات مطلب «نوع‌های نال نپذیر در TypeScript» به صورت پیش‌فرض در پروژه‌های تایپ اسکریپتی React هم فعال هستند؛ چون پرچم strict واقع در فایل tsconfig.json پروژه، به صورت پیش‌فرض به true تنظیم شده‌است و این پرچم، Strict Null Checking را نیز شامل می‌شود. برای آزمایش فعال بودن آن، کدهای فوق را به صورت زیر تغییر دهید تا صرفا if آن حذف شود:
//if (inputRef && inputRef.current) {
  console.log("ref", inputRef.current.value);
//}
بلافاصله خطای امکان نال بودن inputRef.current در اولین بار رندر کامپوننت جاری را دریافت خواهید کرد:

که برای رفع آن، همانند قبل باید ذکر if بررسی نال بودن inputRef و خاصیت current آن‌را اضافه کرد؛ تا دیگر در زمان اجرای برنامه، با شانس نال بودن یکی از این‌دو مواجه نشویم و به کیفیت بالاتری در برنامه‌ی خود برسیم.
روش بررسی if (inputRef && inputRef.current) معادل ساده‌تری را نیز در TypeScript 3.7 به بعد دارد که به Optional Chaining معروف است؛ به صورت زیر:
console.log("ref", inputRef?.current?.value);
در این حالت دیگر نیازی به ذکر if یاد شده نیست و وجود .? به معنای ادامه‌ی این زنجیره، در صورت نال نبودن قطعه‌ی قبلی است.
مطالب
C# 12.0 - Interceptors
به C# 12 و دات‌نت 8، ویژگی «آزمایشی» جدیدی به نام Interceptors اضافه شده‌است که به آن «monkey patching» هم می‌گویند. هدف از آن، جایگزین کردن یک پیاده سازی، با پیاده سازی دیگری است. به این ترتیب توسعه دهندگان دات‌نتی می‌توانند فراخوانی متدهایی خاص را ره‌گیری کرده (interception) و سپس آن‌را به فراخوانی یک پیاده سازی جدید، هدایت کنند.


Interceptor چیست؟

از زمان ارائه‌ی NET 8 preview 6 SDK. به بعد، امکان ره‌گیری هر متدی از کدهای برنامه، به دات‌نت اضافه شده‌است؛ به همین جهت از واژه‌ی Interceptor/ره‌گیر در اینجا استفاده می‌شود. خود تیم دات‌نت از این قابلیت در جهت بازنویسی پویای قسمت‌هایی از کدهای زیرساخت دات‌نت که از Reflection استفاده می‌کنند، با نگارش‌های کامپایل شده‌ی مختص به برنامه‌ی شما، کمک می‌گیرند. به این ترتیب سرعت و کارآیی برنامه‌های دات‌نت 8، بهبود قابل ملاحظه‌ای را پیدا کرده‌اند. برای مثال ahead-of-time compilation (AOT) در دات‌نت 8 و ASP.NET Core 8x بر اساس این ویژگی پیاده سازی شده‌است. این ویژگی جدید، مکمل source generators است که در نگارش‌های پیشین دات‌نت ارائه شده بود.


بررسی  Interceptors با تهیه‌ی یک مثال ساده

فرض کنید می‌خواهیم فراخوانی متد GetText زیر را ره‌گیری کرده و سپس آن‌را با نمونه‌ی دیگری جایگزین کنیم:
namespace CS8Tests;

public class InterceptorsSample
{
    public string GetText(string text)
    {
        return $"{text}, World!";
    }
}
برای اینکار ابتدا نیاز است یک فایل جدید را به نام InterceptsLocationAttribute.cs با محتوای زیر به پروژه اضافه کرد:
namespace System.Runtime.CompilerServices;

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public sealed class InterceptsLocationAttribute : Attribute
{
    public InterceptsLocationAttribute(string filePath, int line, int character)
    {
    }
}
همانطور که در مقدمه‌ی بحث هم عنوان شد، این ویژگی هنوز آزمایشی است و نهایی نشده و ویژگی فوق نیز هنوز به دات‌نت اضافه نشده‌است. به همین جهت فعلا باید آن‌را به صورت دستی به پروژه اضافه کرد و احتمالا در نگارش‌های بعدی دات‌نت، امضای آن تغییر خواهد کرد ... یا حتی ممکن است بطور کامل حذف شود!

سپس فرض کنید فراخوانی متد GetText در فایل Program.cs برنامه به صورت زیر انجام شده‌است:
using CS8Tests;

var example = new InterceptorsSample();
var text = example.GetText("Hello");
Console.WriteLine(text); //Hello, World!
یعنی متد GetText، در سطر چهارم و کاراکتر 20 ام آن فراخوانی شده‌است. این اعداد مهم هستند!

در ادامه از این اطلاعات در ره‌گیر سفارشی زیر استفاده خواهیم کرد:
using System.Runtime.CompilerServices;

namespace CS8Tests;

public static class MyInterceptor
{
    [InterceptsLocation("C:\\Path\\To\\CS8Tests\\Program.cs", 4, 20)] 
    public static string InterceptorMethod(this InterceptorsSample example, string text)
    {
        return $"{text}, DNT!";
    }
}
این ره‌گیر که به صورت متدی الحاقی برای کلاس InterceptorsSample دربرگیرنده‌ی متد GetText تهیه می‌شود، کار جایگزینی فراخوانی آن‌را در سطر چهارم و کاراکتر 20 ام فایل Program.cs انجام می‌دهد. امضای پارامترهای این متد، باید با امضای پارامترهای متد ره‌گیری شده، یکی باشد.

اکنون اگر برنامه را اجرا کنیم ... با خطای زیر مواجه می‌شویم:
 error CS9137: The 'interceptors' experimental feature is not enabled in this namespace. Add
'<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);CS8Tests</InterceptorsPreviewNamespaces>'
to your project.
عنوان می‌کند که این ویژگی آزمایشی است و باید فایل csproj. را به صورت زیر تغییر داد تا بتوان از آن استفاده نمود:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <!--<NoWarn>Test001</NoWarn>-->
    <InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);CS8Tests</InterceptorsPreviewNamespaces>
  </PropertyGroup>
</Project>
اینبار برنامه کامپایل شده و اجرا می‌شود. در این حالت خروجی جدید برنامه، خروجی تامین شده‌ی توسط ره‌گیر سفارشی ما است:
Hello, DNT!


سؤال: آیا ره‌گیری انجام شده، در زمان کامپایل انجام می‌شود یا در زمان اجرا؟

برای این مورد می‌توان به Low-Level C# code تولیدی مراجعه کرد. برای مشاهده‌ی یک چنین کدهایی می‌توانید از منوی Tools->IL Viewer برنامه‌ی Rider استفاده کرده و در برگه‌ی ظاهر شده، گزینه‌ی Low-Level C# آن‌را انتخاب نمائید:
using CS8Tests;
using System;
using System.Runtime.CompilerServices;

[CompilerGenerated]
internal class Program
{
  private static void <Main>$(string[] args)
  {
    Console.WriteLine(new InterceptorsSample().InterceptorMethod("Hello"));
  }

  public Program()
  {
    base..ctor();
  }
}
همانطور که مشاهده می‌کنید، این ره‌گیری و جایگزینی، در زمان کامپایل انجام شده و کامپایلر، به‌طور کامل نحوه‌ی فراخوانی متد GetText اصلی را به متد ره‌گیر ما تغییر داده و بازنویسی کرده‌است.


سؤال: آیا این قابلیت واقعا کاربردی است؟!

اکنون شاید این سؤال مطرح شود که ... واقعا چه کسی قرار است مسیر کامل یک فایل، شماره سطر و شماره ستون فراخوانی متدی را به اینگونه در اختیار سیستم ره‌گیری قرار دهد؟! آیا واقعا این قابلیت، یک قابلیت کاربردی و مناسب است؟!
اینجا است که اهمیت source generators مشخص می‌شود. توسط source generators دسترسی کاملی به syntax trees وجود دارد و همچنین یکسری اطلاعات تکمیلی مانند FilePath و سپس CSharpSyntaxNodeها که دسترسی به داده‌های متد ()GetLocation را دارند که مکان دقیق سطر و ستون‌های فراخوانی‌ها را مشخص می‌کند.


کاربردهای فعلی ره‌گیرها در دات نت 8

در دات نت 8، این موارد با استفاده از ره‌گیرها بهینه سازی شده و سرعت آن‌ها افزایش یافته‌اند:
- فراخوانی‌هایی که تمام اطلاعات آن‌ها در زمان کامپایل فراهم است، مانند Regex.IsMatch(@"a+b+") که از یک الگوی ثابت و مشخص استفاده می‌کند، ره‌گیری شده و پیاده سازی آن با کدی استاتیک، جایگزین می‌شود.
- در ASP.NET Minimal API، استفاده از lambda expressions جهت ارائه‌ی تعاریفی مانند:
app.MapGet("/products", handler: (int? page, int? pageLength, MyDb db) => { ... })
مرسوم است. این نوع فراخوانی‌ها نیز توسط ره‌گیرها برای جایگزینی handler آن‌ها با کدهای استاتیک، جهت بالابردن کارآیی و کاهش تخصیص‌های حافظه انجام می‌شود.
- بهبود کارآیی foreach loops جهت استفاده از ریاضیات برداری و SIMD در صورت امکان.
- بهبود کارآیی تزریق وابستگی‌ها، زمانیکه به تعاریف مشخصی مانند ()<provider.Register<MyService ختم می‌شود.
- بجای استفاده از expression trees در زمان اجرای برنامه، اکنون می‌توان کدهای SQL معادل را در زمان کامپایل برنامه تولید کرد.
- بهبود کارآیی Serializers، زمانیکه از یک نوع مشخص مانند ()<Serialize<MyType استفاده می‌شود و کامپایلر می‌تواند آن‌را با کدهای زمان کامپایل، جایگزین کند.


محدودیت‌های ره‌گیرها در دات‌نت 8

- ره‌گیرهای دات‌نت 8 فقط با متدها کار می‌کنند.
- مسیر ارائه شده حتما باید یک مسیر کامل و مشخص باشد. یعنی اگر این قطعه کد، به سیستم دیگری منتقل شود، کامپایل نخواهد شد و امکان ارائه‌ی مسیرهای نسبی وجود ندارد.
- امضای متدها، حتما باید یکی باشد. یعنی نمی‌توان یک ره‌گیر جنریک را تعریف کرد.
مطالب
مدیریت پیشرفته‌ی حالت در React با Redux و Mobx - قسمت دوم - بررسی توابع Redux
همانطور که در مقدمه‌ی قسمت قبل نیز عنوان شد، در این سری ابتدا کتابخانه‌ی Redux را به صورت مجزایی از React بررسی می‌کنیم؛ چون در اصل، یک کتابخانه‌ی مدیریت حالت عمومی است و وابستگی خاصی را به React ندارد و در بسیاری از برنامه و فریم‌ورک‌های دیگر نیز قابل استفاده‌است.


ایجاد یک برنامه‌ی خالی React برای آزمایش توابع Redux

در اینجا برای بررسی توابع Redux، یک پروژه‌ی جدید React را ایجاد می‌کنیم:
> npm i -g create-react-app
> create-react-app state-management-redux-mobx
> cd state-management-redux-mobx
> npm start
در ادامه کتابخانه‌ی Redux را نیز نصب می‌کنیم. برای این منظور پس از باز کردن پوشه‌ی اصلی برنامه توسط VSCode، دکمه‌های ctrl+` را فشرده (ctrl+back-tick) و دستور زیر را در ترمینال ظاهر شده وارد کنید:
> npm install --save redux
البته برای کار با Redux، الزاما نیازی به طی مراحل فوق نیست؛ ولی چون این قالب، یک محیط آماده‌ی کار با ES6 را فراهم می‌کند، به سادگی می‌توان فایل index.js آن‌را خالی کرد و سپس شروع به کدنویسی و آزمایش Redux نمود.


معرفی سریع توابع Redux

Redux، کتابخانه‌ی کوچکی است و تنها از 5 تابع تشکیل شده‌است:
applyMiddleware: function()
bindActionCreators: function()
combineReducers: function()
compose: function()
createStore: function()
و سه مورد از آن‌ها بیشتر کمکی هستند. برای مثال تابع compose مانند متد flow و یا pipe در کتابخانه‌ی lodash است و اصلا به Redux مرتبط نیست. تابع combineReducers، اشیاء موجود در state را با هم ترکیب می‌کند. bindActionCreators نیز یک تابع کمکی است جهت ایجاد ساده‌تر ActionCreators. بنابراین کتابخانه‌ی Redux، آنچنان گسترده نیست.


بررسی تابع compose با یک مثال

پس از ایجاد پروژه‌ی React و افزودن کتابخانه‌ی Redux به آن، به فایل src\index.js این پروژه مراجعه کرده و محتویات آن‌را با قطعه کد ذیل، تعویض می‌کنیم:
import { compose } from "redux";

const makeLouder = text => text.toUpperCase();
const repeatThreeTimes = text => text.repeat(3);
const embolden = text => text.bold();

const makeLouderAndRepeatThreeTimesAndEmbolden = compose(
  embolden,
  repeatThreeTimes,
  makeLouder
);
console.log(makeLouderAndRepeatThreeTimesAndEmbolden("Hello"));
- در ابتدای این ماژول، تابع compose را از کتابخانه‌ی redux دریافت کرده‌ایم.
- سپس سه تابع ساده را برای ضخیم کردن، تکرار و با حروف بزرگ نمایش دادن یک متن ورودی، تعریف کرده‌ایم.
- اکنون با استفاده از متد compose کتابخانه‌ی redux، این سه متد را به صورت ترکیبی، بر روی متن ورودی Hello، اعمال کرده‌ایم.
- در آخر اگر برای مشاهده‌ی نتیجه‌ی اجرای console.log بر روی آن، به کنسول توسعه دهندگان مرورگر مراجعه کنیم، به خروجی زیر خواهیم رسید:
 <b>HELLOHELLOHELLO</b>
همانطور که مشاهده می‌کنید، متن Hello را سه بار با حروف بزرگ تکرار نموده و در نهایت آن‌را با تک b محصور کرده‌است.


بررسی تابع createStore با یک مثال

Store در Redux، جائی است که در آن state برنامه ذخیره می‌شود. تابع createStore که کار ایجاد store را انجام می‌دهد، یک پارامتر را دریافت می‌کند و آن‌هم تابع reducer است که در قسمت قبل معرفی شد و در ساده‌ترین حالت آن، به نحو زیر با یک متد خالی، قابل فراخوانی است:
import { createStore } from "redux";

createStore(() => {});
تابع reducer دو پارامتر را دریافت می‌کند: وضعیت فعلی برنامه (state در اینجا) و رخ‌دادی که واقع شده‌است (action در اینجا). خروجی آن نیز یک state جدید بر این اساس است:
import { createStore } from "redux";

const reducer = (state, action) => {
  return state;
};
const store = createStore(reducer);
console.log(store);
در این مثال، تابع reducer را طوری تعریف کرده‌ایم که بر اساس هر نوع رخ‌دادی که به آن برسد، همان وضعیت قبلی را بازگشت دهد. سپس بر اساس آن یک store را ایجاد کرده‌ایم. اگر به خروجی console.log بررسی محتوای این شیء دقت کنیم، به صورت زیر خواهد بود:


در شیء store، چهار متد dispatch, subscribe, getState, replaceReducer مشخص هستند:
- کارکرد متد replaceReducer مشخص است؛ یک reducer جدید را به آن می‌دهیم و reducer قبلی را جایگزین می‌کند.
- متد dispatch آن مرتبط است به مبحث dispatch actions که در قسمت قبل به آن پرداختیم. هدف آن تغییر state، بر اساس یک action رسیده‌است.
- متد getState، وضعیت فعلی state را باز می‌گرداند.
- متد subscribe با هر تغییری در state، سبب بروز رخ‌دادی می‌شود. یکی از کاربردهای آن می‌تواند به روز رسانی UI، بر اساس تغییرات state باشد. برای مثال کتابخانه‌ی دیگری به نام react redux، دقیقا همین کار را به کمک متد subscribe، انجام می‌دهد. در این قسمت، هدف ما بررسی پشت صحنه‌ی کتابخانه‌هایی مانند react redux است که چه متدهایی را محصور کرده‌اند و دقیقا چگونه کار می‌کنند.

در مثال زیر، مقدار ابتدایی پارامتر state متد reducer را به یک شیء که دارای خاصیت value و مقدار 1 است، تنظیم کرده‌ایم:
import { createStore } from "redux";

const reducer = (state = { value: 1 }, action) => {
  return state;
};
const store = createStore(reducer);
console.log(store.getState());
سپس بر این اساس، store را ایجاد کرده و متد getState آن‌را فراخوانی کرده‌ایم. خروجی آن به صورت زیر است، که معادل وضعیت فعلی state در store می‌باشد:


در کامپوننت‌های React، این نوع خواص به صورت props ارسال می‌شوند. کل state در Redux ذخیره شده و سپس قابل دسترسی و خواندن خواهد شد.


بررسی متد dispatch یک store با مثال

در اینجا مثالی را از کاربرد متد dispatch ملاحظه می‌کنید که یک شیء را می‌پذیرد:
import { compose, createStore } from "redux";

const reducer = (state = { value: 1 }, action) => {
  console.log("Something happened!", action);
  return state;
};
const store = createStore(reducer);
console.log(store.getState());
store.dispatch({ type: "ADD" });
متد reducer با یک state ابتدایی تنظیم شده‌ی به شیء { value: 1 } تعریف شده و سپس با ارسال آن به createStore و ایجاد store، اکنون می‌توانیم رخ‌دادهایی را به آن dispatch کنیم.
فرمت شیءای که به متد dispatch ارسال می‌شود، حتما باید به همراه خاصیت type باشد؛ در غیر اینصورت با صدور استثنائی، این نکته را گوشزد می‌کند. مقدار آن نیز یک رشته‌است که بیانگر وقوع رخدادی در برنامه می‌باشد؛ برای مثال کاربر درخواست دریافت اطلاعاتی را کرده‌است و یا کاربر از سیستم خارج شده‌است و امثال آن.

خروجی قطعه کد فوق، به صورت زیر است:


با اجرای برنامه، یک رخ‌داد درونی از نوع redux/INITo.2.g.b.y.i@@ صادر شده‌است که هدف آن آغاز و مقدار دهی اولیه‌ی store است. پس از آن زمانیکه متد store.dispatch فراخوانی شده‌است، مجددا console.log داخل متد reducer فراخوانی شده‌است که اینبار به همراه type ای است که ما مشخص کرده‌ایم.

در حالت کلی، اینکه شیء ارسالی توسط dispatch را چگونه طراحی می‌کنید، اختیاری است؛ اما الگوی پیشنهادی در این زمینه، redux standard actions نام دارد و به صورت زیر است که هدف از آن، یک‌دست شدن طراحی و پیاده سازی برنامه است:
store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} });
- نوع action باید همواره قید شود و عموما رشته‌ای است.
- سپس به خاصیت payload، تمام داده‌های مرتبط با آن اکشن، مانند نتیجه‌ی بازگشتی از یک API، به صورت یک شیء، انتساب داده می‌شود.
- خاصیت meta، مرتبط با متادیتا و اطلاعات اضافی است که عموما استفاده نمی‌شود.

اکنون که طراحی شیء ارسالی به پارامتر action متد reducer مشخص شد، می‌توان بر اساس خاصیت type آن، یک switch را طراحی کرد و نسبت به نوع‌های مختلف رسیده، واکنش نشان داد:
import { createStore } from "redux";

const reducer = (state = { value: 1 }, action) => {
  console.log("Something happened!", action);

  if (action.type === "ADD") {
    const value = state.value;
    const amount = action.payload.amount;
    state.value = value + amount;
  }

  return state;
};
const store = createStore(reducer);
const firstState = store.getState();
console.log(firstState);
store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} });
const secondState = store.getState();
console.log(secondState);
console.log("secondState === firstState", secondState === firstState);
در اینجا اکشن از نوع ADD، با یک payload با محتوای شیءای که دارای خاصیت amount با مقدار 2 است، به متد reducer مربوط به store، ارسال می‌شود. سپس در متد reducer، بر اساس مقدار action.type، تصمیم‌گیری خواهد شد. اگر این مقدار مساوی اکشن خاص ADD بود، آنگاه مقدار value شیء state را با مقدار amount جمع زده و به state.value انتساب می‌دهیم. اکنون پس از فراخوانی متد store.dispatch، اگر خروجی متد ()store.getState را بررسی کنیم، به صورت {value: 3} در آمده‌است ... و این کاری است که نباید انجام شود! نباید مقدار شیء state را به صورت مستقیم تغییر داد. چون در آخرین بررسی صورت گرفته که کار مقایسه‌ی بین state پیش و پس از فراخوانی store.dispatch است (secondState === firstState)، حاصل true است. یعنی اگر با این روش با React کار کنیم، نمی‌تواند محاسبه کند که چه چیزی در state تغییر کرده‌است، تا بر اساس آن UI را به روز رسانی کند.

یک روش حل این مشکل، حذف سطر state.value = value + amount و جایگزینی آن با یک return است که شیء state جدیدی را بازگشت می‌دهد:
return {
  value: value + amount
};
اکنون نتیجه‌ی مقایسه‌ی secondState === firstState دیگر true نخواهد بود.


بررسی متد subscribe یک store با مثال

در ادامه به store همان مثال فوق، متد subscribe را نیز اضافه می‌کنیم و سپس دوبار، کار store.dispatch را انجام خواهیم داد:
const store = createStore(reducer);

const unsubscribe = store.subscribe(() => console.log(store.getState()));

store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} });
store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} });

unsubscribe();
- هر بار که متد store.dispatch فراخوانی می‌شود، یکبار callback function ارسالی به متد store.subscribe، فراخوانی خواهد شد که در اینجا کار نمایش مقدار شیء state را به عهده دارد. در یک چنین حالتی مشترکین به store، متوجه خواهند شد که احتمالا state جدیدی توسط متد reducer بازگشت داده شده‌است و سپس بر اساس آن برای مثال تصمیم خواهند گرفت تا UI را به روز رسانی کنند.
- خروجی مستقیم متد store.subscribe نیز یک متد است که unsubscribe نام دارد و می‌توان در پایان کار، برای جلوگیری از نشتی‌های حافظه، آن‌را فراخوانی کرد.

خروجی حاصل از console.log منتسب به متد store.subscribe، با دوبار فراخوانی متد store.dispatch پس از آن، به صورت زیر است:
{value: 3}
{value: 5}


بررسی تابع combineReducers با یک مثال

اگر قرار باشد در متد reducer، صدها if action.type را تعریف کرد ... پس از مدتی از کنترل خارج می‌شود و غیرقابل نگهداری خواهد شد. تابع combineReducers به همین جهت طراحی شده‌است تا چندین متد مجزای reducer را با هم ترکیب کند. بنابراین می‌توان در برنامه صدها متد کوچک reducer را که قابلیت توسعه و نگهداری بالایی را دارند، پیاده سازی کرد و سپس با استفاده از تابع combineReducers، آن‌ها را یکی کرده و به متد createStore ارسال کرد. نکته‌ی مهم اینجا است که هرچند اینبار می‌توان تعداد زیادی reducer را طراحی کرد، اما توسط تابع combineReducers، مقدار action ارسالی، از تمام این reducerها عبور داده خواهد شد. به این ترتیب اگر نیاز بود می‌توان به یک action، در چندین متد مختلف reducer گوش فرا داد و بر اساس آن تصمیم گیری کرد. بنابراین بهتر است هر reducer تعریف شده در صورتیکه action.type آن با action رسیده تطابق نداشته باشد، همان state قبلی را بازگشت دهد تا بتوان زنجیره‌ی reducerها را تشکیل داد و بهتر مدیریت کرد.
برای نمونه اگر متد reducer فعلی را به calculatorReducer تغییر نام دهیم، روش معرفی آن توسط متد combineReducers به متد createStore به صورت زیر است:
import { combineReducers, createStore } from "redux";
  // ...

const calculatorReducer = (state = { value: 1 }, action) => {
  // ...
};
const store = createStore(
  combineReducers({
    calculator: calculatorReducer
  })
);
به شیء ارسالی به متد combineReducers، هر reducer موجود به صورت یک خاصیت جدید اضافه می‌شود.


بررسی تابع bindActionCreators با یک مثال

فرض کنید می‌خواهیم متد dispatch را چندین بار فراخوانی کنیم:
store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} });
store.dispatch({ type: "ADD", payload: { amount: 2 }, meta: {} });
بجای اینکه شیء ارسالی به آن‌را به این صورت ایجاد کنیم، می‌توان یک تابع را برای آن ایجاد کرد تا مقدار amount را گرفته و شیء action مدنظر را بازگشت دهد:
const createAddAction = amount => {
  return {
    type: "ADD",
    payload: {
      amount // = amount: amount
    },
    meta: {}
  };
};
و سپس می‌توان آن‌را به صورت زیر مورد استفاده قرار داد:
store.dispatch(createAddAction(2));
store.dispatch(createAddAction(2));
و یا توسط تابع bindActionCreators، می‌توان فراخوانی فوق را به صورت زیر نیز انجام داد و نتیجه یکی است:
import { bindActionCreators, combineReducers, compose, createStore } from "redux";

// ...
const dispatchAdd = bindActionCreators(createAddAction, store.dispatch);
dispatchAdd(2);
dispatchAdd(2);
همانطور که ملاحظه می‌کنید، bindActionCreators فقط یک تابع کمکی است تا کار dispatch action را ساده کند.


میان‌افزارها (Middlewares) در Redux

پس از اینکه یک اکشن، به سمت reducer ارسال شد و پیش از رسیدن آن به reducer، می‌توان کدهای دیگری را نیز اجرا کرد. برای مثال چون این توابع خالص هستند، نمی‌توان اعمالی را داخل آن‌ها اجرا کرد که به همراه اثرات جانبی مانند کار با یک API خارجی باشند. با استفاده از میان‌افزارها در این بین می‌توان کدهایی را که با دنیای خارج تعامل دارند نیز اجرا کرد.
یک میان‌افزار در Redux، همانند سایر قسمت‌های آن، تنها یک تابع ساده‌ی جاوا اسکریپتی است:
const logger = ({ getState }) => {
  return next => action => {
    console.log(
      'MIDDLEWARE',
      getState(),
      action
    );
    const value = next(action);
    console.log({value});
    return value;
  }
}
این تابع میان‌افزار، امکان دریافت وضعیت فعلی store را دارد و در نهایت یک تابع دیگر را باز می‌گرداند. داخل این تابع بازگشت داده شده می‌توان اعمال دارای اثرات جانبی را نیز اجرا کرد؛ مانند فراخوانی console.log و یا صدور یک درخواست Ajax ای. یکی دیگر از کاربردهای میان‌افزارها، انجام کارهای آماری بر روی اکشن‌های ارسالی است. بجای اینکه کدهای متناظر را داخل  reducerها قرار داد، می‌توان به رسیدن اکشن‌های خاصی در این بین گوش فرا داد و برای مثال آن‌ها را لاگ کرد و یا آماری را از آن‌ها تهیه نمود.
در پایان کار میان‌افزار باید متد next آن‌را فراخوانی کرد تا بتوان میان‌افزارهای متعددی را زنجیروار اجرا کرد.
در آخر برای معرفی آن به یک store می‌توان از تابع applyMiddleware استفاده کرد:
import { applyMiddleware, bindActionCreators, combineReducers, compose, createStore } from "redux";

// ...

const store = createStore(
  combineReducers({
    calculator: calculatorReducer
  }),
  applyMiddleware(logger)
);

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: state-management-redux-mobx-part02.zip
نظرات مطالب
Blazor 5x - قسمت چهارم - مبانی Blazor - بخش 1 - Data Binding
یک نکته‌ی تکمیلی: روش تعریف data binding دو طرفه در کامپوننت‌ها
در مطلب جاری، binding دو طرفه بررسی شد؛ که نکته‌ی مورد بحث آن، به ویژگی‌های استاندارد HTML مانند ویژگی value یک input استاندارد اختصاص داشت. اما اگر بخواهیم در کامپوننت‌های سفارشی خود، این binding دو طرفه را تعریف کنیم تا قابل اعمال به خواص و ویژگی‌های #C باشد (مانند bind-ProprtyName@)، روش کار به نحو دیگری است. نمونه‌ی آن کامپوننت استاندارد InputText خود Blazor است که در اینجا هم دارای bind-Value@ است؛ اما این Value (شروع شده‌ی با حروف بزرگ) یک خاصیت #C تعریف شده‌ی در کلاس InputText است و نه یک ویژگی استاندارد HTML که عموما با حروف کوچک شروع می‌شوند:
<InputText @bind-Value="employee.FirstName" />
برای رسیدن به bind-Value@ فوق، سه مرحله باید طی شود:
الف) یک پارامتر عمومی به نام Value باید در کلاس کامپوننت جاری تعریف شود تا بتوان از طریق والد آن، مقداری را دریافت کرد (یک طرف binding به این نحو تشکیل می‌شود):
[Parameter] public string Value { set; get; }
ب) یک رویداد خاص Blazor، به نام EventCallback نیز باید اضافه شود تا به کامپوننت استفاده کننده‌ی از کامپوننت جاری، تغییرات را اطلاع رسانی کند (به این نحو است که این binding، دو طرفه می‌شود و تغییرات رخ‌داده‌ی در اینجا، به کامپوننت والد در برگیرنده‌ی آن، اطلاع رسانی می‌شود):
[Parameter] public EventCallback<string> ValueChanged { get; set; }
نام آن هم ویژه‌است. یعنی حتما باید با نام پارامتر Value شروع شود (نام پارامتری که قرار است binding دو طرفه روی آن اعمال گردد) و حتما باید ختم به واژه‌ی Changed باشد. این الگوی استاندارد از این جهت مورد استفاده قرار می‌گیرد که در تعریف InputText فوق، چنین پارامتر و مقدار دهی را مشاهده نمی‌کنیم، اما ... در پشت صحنه توسط Blazor به صورت خودکار تشکیل شده و مدیریت می‌شود.

نکته‌ی مهم: در اینجا بجای EventCallback، از Action هم می‌توان استفاده کرد:
[Parameter] public Action<string> ValueChanged { get; set; }
تفاوت اصلی و مهم آن با EventCallback، در فراخوانی نشدن خودکار متد StateHasChanged، در پایان کار آن است. زمانیکه EventCallback ای فراخوانی می‌شود، Blazor به صورت خودکار در پایان کار آن، متد StateHasChanged را نیز فراخوانی می‌کند تا والد دربرگیرنده‌ی کامپوننت جاری، مجددا رندر شود (به همراه تمام کامپوننت‌های فرزند آن). اما <Action<T فاقد این درخواست خودکار رندر و به روز رسانی مجدد UI است.

ج) برای فعالسازی اعتبارسنجی استاندارد فرم‌های Blazor، نیاز به خاصیت ویژه‌ی سومی نیز هست (که اختیاری است):
[Parameter] public Expression<Func<string>> ValueExpression { get; set; }
این خاصیت ویژه نیز باید با نام Value یا همان پارامتر تعریف شده، شروع شود و حتما باید ختم به واژه‌ی Expression شود. در مورد اعتبارسنجی‌ها در قسمت‌های بعدی بیشتر بحث خواهد شد. این پارامتر و مدیریت آن توسط خود Blazor صورت می‌گیرد و به ندرت توسط ما به صورت مستقیمی مقدار دهی خواهد شد؛ مگر اینکه بخواهیم کامپوننتی مانند InputText را سفارشی سازی کنیم.

مرحله‌ی آخر این طراحی، فراخوانی پارامتر ValueChanged است تا به کامپوننت والد این تغییرات را اطلاع رسانی کنیم. روش استاندارد آن به صورت زیر است:
private string _value;

[Parameter]
public string Value
{
   get => _value;
   set
   {
       var hasChanged = string.Equals(_value, value, StringComparison.Ordinal);
       if (hasChanged)
       {
           _value = value;

           if (ValueChanged.HasDelegate)
           {
              _ = ValueChanged.InvokeAsync(value);
           }
         }
    }
}
در اینجا در قسمت set همان پارامتر Value ای که در قسمت الف تعریف کردیم، در صورت بروز تغییری نسبت به قبل، متد InvokeAsync پارامتر ValueChanged را فراخوانی می‌کنیم. تا همین اندازه برای اطلاع رسانی به والد کافی است؛ همچنین وجود مقایسه‌ی بین مقدار جدید و مقدار قبلی، برای کاهش تعداد بار به روز رسانی UI ضروری است. هر بار که ValueChanged.InvokeAsync فراخوانی می‌شود، والد کامپوننت جاری، یکبار دیگر UI را مجددا رندر خواهد کرد. بنابراین هر چقدر تعداد این رندرها کمتر باشد، کارآیی برنامه بهبود خواهد یافت.
در این قطعه کد، بررسی ValueChanged.HasDelegate را هم مشاهده می‌کنید. زمانیکه پارامتر Value ای با طی سه مرحله‌ی فوق تعریف شد، قرار نیست حتما توسط bind-Value@ مورد استفاده قرار گیرد. می‌توان Value را به صورت یک طرفه هم مورد استفاده قرار داد. در این حالت دو پارامتر ب و ج دیگر توسط Blazor ایجاد و مقدار دهی نشده و رهگیری نخواهند شد. یعنی تعریف bind-Value@ در سمت والد، معادل سیم کشی خودکار به ValueChanged و ValueExpression از طرف Blazor است و تعریف دستی آن‌ها ضرورتی ندارد. اما می‌توان bind-Value@ را هم تعریف نکرد و فقط نوشت Value. در این حالت از تنظیمات ب و ج صرفنظر می‌شود. بنابراین ضروری است که بررسی کنیم آیا پارامتر ValueChanged واقعا متصل به روال رویدادگردانی شده‌است یا خیر. اگر خیر، نیازی به اطلاع رسانی و فراخوانی متد ValueChanged.InvokeAsync نیست.
نظرات مطالب
مقایسه بین حلقه های تکرار (Lambda ForEach و for و foreach)
یک روش ساده : 
Stopwatch sw = Stopwatch.StartNew();
var ListOfNumber = new List<int>() { 100, 200, 300 , 400 , 500  };
for ( int i = 0 ; i < ListOfNumber.Count ; i++ )
{
       Console.WriteLine( ListOfNumber[i] );
}
sw.Stop();
Console.WriteLine("Total time (ms): {0}", (long) sw.ElapsedMilliseconds);

نظرات مطالب
معرفی System.Text.Json در NET Core 3.0.
یک نکته‌ی تکمیلی: نوشتن تبدیلگرهای نوع‌ها برای System.Text.Json

System.Text.Json، در حال حاضر از مفهومی به نام type coercion/inference، پشتیبانی نمی‌کند. type coercion یعنی تبدیل یک مقدار، به مقداری دیگر که به صورت مستقیم قابل انتساب به یکدیگر نیستند. برای مثال اگر رشته‌ی "true" را درنظر بگیریم، قابلیت انتساب به یک خاصیت از نوع bool را ندارد. برای یک چنین مواردی در این API جدید، باید تبدیلگر نوشت.
یک مثال:
using System.Collections.Generic;
using System.Text.Json;

namespace JsonTests
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public bool IsInStock { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {            
         var products = JsonSerializer.Deserialize<List<Product>>("[{\"Id\":1026,\"Name\":\"P1\",\"IsInStock\":\"false\"}]");
        }
    }
}
در این مثال، خاصیت IsInStock از نوع bool است، اما مقداری را که باید از طریق متد Deserialize دریافت کنیم، یک رشته‌ی bool ای است که قابل انتساب به bool نیست. در این حالت اگر برنامه را اجرا کنیم به استثنای زیر خواهیم رسید:
An unhandled exception of type 'System.Text.Json.JsonException' occurred in System.Text.Json.dll
Inner exceptions found, see $exception in variables window for more details.
Innermost exception System.InvalidOperationException : Cannot get the value of a token type 'String' as a boolean.
برای رفع این مشکل، می‌توان تبدیلگر زیر را تدارک دید:
    public class BooleanConverter : JsonConverter<bool>
    {
        public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            var value = reader.GetString();
            if (value.Equals("true", StringComparison.OrdinalIgnoreCase)
                 || value.Equals("yes", StringComparison.OrdinalIgnoreCase)
                 || value.Equals("1", StringComparison.Ordinal))
            {
                return true;
            }

            if (value.Equals("false", StringComparison.OrdinalIgnoreCase)
                 || value.Equals("no", StringComparison.OrdinalIgnoreCase)
                 || value.Equals("0", StringComparison.Ordinal))
            {
                return false;
            }

            throw new NotSupportedException($"`{value}` can't be converted to `bool`.");
        }

        public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)
        {
            switch (value)
            {
                case true:
                    writer.WriteStringValue("true");
                    break;
                case false:
                    writer.WriteStringValue("false");
                    break;
            }
        }
    }
برای نوشتن یک تبدیلگر bool، کلاس مرتبط، باید <JsonConverter<bool را پیاده سازی کند. کلاس JsonConverter نیز به صورت زیر تعریف شده‌است:
    public abstract class JsonConverter<T> : JsonConverter
    {
        protected internal JsonConverter();

        public override bool CanConvert(Type typeToConvert);
        public abstract T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options);
        public abstract void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options);
    }
و پیاده سازی دو متد Read و Write آن الزامی است.
در متد Read آن، مقدار رشته‌ای دریافت شده‌ی از منبع داده، در اختیار ما قرار می‌گیرد. سپس باید بر اساس این مقدار، مقدار متناظری را از نوع T که در اینجا bool است، بازگشت دهیم. برای مثال اگر یکی از مقادیر رشته‌ای true ،yes و 1 را دریافت کردیم، بجای آن true را بازگشت می‌دهیم.

اکنون برای استفاده‌ی از آن خواهیم داشت:
var options = new JsonSerializerOptions();
options.Converters.Add(new BooleanConverter());
var products = JsonSerializer.Deserialize<List<Product>>(
   "[{\"Id\":1026,\"Name\":\"P1\",\"IsInStock\":\"false\"}]",
   options);
و یا روش دیگر انجام اینکار، استفاده از ویژگی JsonConverter، برای معرفی تبدیلگر تهیه شده‌است:
[JsonConverter(typeof(BooleanConverter))]
public bool IsInStock { get; set; }

از این تبدیلگر برای حالت Serialize نیز می‌توان استفاده کرد:
var options  = new JsonSerializerOptions() {WriteIndented = true };
options.Converters.Add(new BooleanConverter());
var data = JsonSerializer.Serialize<List<Product>>(productList, options);
مطالب
مفاهیم برنامه نویسی ـ آشنایی با سازنده‌ها
در مطلب پیشین برای نگهداری حالت شیء یا همان ویژگی‌های آن Property‌‌‌ها را در کلاس معرفی کردیم و پس از ایجاد شیء مقدار مناسبی را به پروپرتی‌ها اختصاص دادیم.
اگرچه ایجاد شیء و مقداردهی به ویژگی‌های آن ما را به هدفمان می‌رساند، اما بهترین روش نیست چرا که ممکن است مقداردهی به یک ویژگی فراموش شده و سبب شود شیء در وضعیت نادرستی قرار گیرد. این مشکل با استفاده از سازنده‌ها (Constructors) حل می‌شود.
سازنده (Constructor) عضو ویژه ای از کلاس است بسیار شبیه به یک متد که در هنگام وهله سازی (ایجاد یک شیء از کلاس) به صورت خودکار فراخوانی و اجرا می‌شود. وظیفه سازنده مقداردهی به ویژگی‌های عمومی و خصوصی شیء تازه ساخته شده و به طور کلی انجام هر کاری است که برنامه‌نویس در نظر دارد پیش از استفاده از شیء به انجام برساند.
مثالی که در این بخش بررسی می‌کنیم کلاس مثلث است. برای پیاده سازی این کلاس سه ویژگی در نظر گرفته ایم. قاعده، ارتفاع و مساحت. بله مساحت را این بار به جای متد به صورت یک پروپرتی پیاده سازی می‌کنیم. اگرچه در آینده بیشتر راجع به چگونگی انتخاب برای پیاده سازی یک عضو کلاس به صورت پروپرتی یا متد بحث خواهیم کرد اما به عنوان یک قانون کلی در نظر داشته باشید عضوی که به صورت منطقی به عنوان داده مطرح است را به صورت پروپرتی پیاده سازی کنید. مانند نام دانشجو. از طرفی اعضایی که دلالت بر انجام عملی دارند را به صورت متد پیاده سازی می‌کنیم. مانند متد تبدیل به نوع داده دیگر. (مثلاً ()Object.ToString)
public class Triangle
{
    private int _height;
    private int _baseLength;

    public int Height
    {
        get { return _height; }

        set
        {
            if (value < 1 || value > 100)
            {
                // تولید خطا
            }

            _height = value;
        }
    }

    public int BaseLength
    {
        get { return _baseLength; }

        set
        {
            if (value < 1 || value > 100)
            {
                // تولید خطا
            }

            _baseLength = value;
        }
    }

    public double Area
    {
        get { return _height * _baseLength * 0.5; }
    }
}
چون در بخشی از یک پروژه نیاز پیدا کردیم با یک سری مثلث کار کنیم، کلاس بالا را طراحی کرده ایم. به نکات زیر توجه نمایید.
 • در اکسسور set دو ویژگی قاعده و ارتفاع، محدوده مجاز مقادیر قابل انتساب را بررسی نموده ایم. در صورتی که مقداری خارج از محدوده یاد شده برای این ویژگی‌ها تنظیم شود خطایی را ایجاد خواهیم کرد. شاید برای برنامه نویسانی که تجربه کمتری دارند زیاد روش مناسبی به نظر نرسد. اما این یک روش قابل توصیه است. مواجه شدن کد مشتری (کد استفاده کننده از کلاس) با یک خطای مهلک که علت رخ دادن خطا را نیز می‌توان به همراه آن ارائه کرد بسیار بهتر از بروز خطاهای منطقی در برنامه است. چون رفع خطاهای منطقی بسیار دشوارتر است. در مطالب آینده راجع به تولید خطا و موارد مرتبط با آن بیشتر صحبت می‌کنیم.
 • در مورد ویژگی مساحت، اکسسور set را پیاده سازی نکرده ایم تا این ویژگی را به صورت فقط خواندنی ایجاد کنیم.

وقتی شیء ای از یک کلاس ایجاد می‌شود، بلافاصله سازنده آن فراخوانی می‌گردد. سازنده‌ها هم نام کلاسی هستند که در آن تعریف می‌شوند و معمولاً اعضای داده ای شیء جدید را مقداردهی می‌کند. همانطور که می‌دانید وهله سازی از یک کلاس با عملگر new انجام می‌شود. سازنده کلاس بلافاصله پس از آنکه حافظه برای شیء در حال تولید اختصاص داده شد، توسط عملگر new فراخوانی می‌شود.

سازنده پیش فرض

سازنده‌ها مانند متدهای دیگر می‌توانند پارامتر دریافت کنند. سازنده ای که هیچ پارامتری دریافت نمی‌کند سازنده پیش فرض (Default constructor) نامیده می‌شود. سازنده پیش فرض زمانی اجرا می‌شود که با استفاده از عملگر new شیء ای ایجاد می‌کنید اما هیچ آرگومانی را برای این عملگر در نظر نگرفته اید.
اگر برای کلاسی که طراحی می‌کنید سازنده ای تعریف نکرده باشید کامپایلر سی شارپ یک سازنده پیش فرض (بدون پارامتر) خواهد ساخت. این سازنده هنگام ایجاد اشیاء فراخوانی شده و مقدار پیش فرض متغیرها و پروپرتی‌ها را با توجه به نوع آن‌ها تنظیم می‌نماید. مثلاً مقدار صفر برای متغیری از نوع int یا false برای نوع bool و null برای انواع ارجاعی که در آینده در این مورد بیشتر خواهید آموخت.
اگر مقادیر پیش فرض برای متغیرها و پروپرتی‌ها مناسب نباشد، مانند مثال ما، سازنده پیش فرض ساخته شده توسط کامپایلر همواره شیء ای می‌سازد که وضعیت صحیحی ندارد و نمی‌تواند وظیفه خود را انجام دهد. در این گونه موارد باید این سازنده را جایگزین نمود.

جایگزینی سازنده پیش فرض ساخته شده توسط کامپایلر

افزودن یک سازنده صریح به کلاس بسیار شبیه به تعریف یک متد در کلاس است. با این تفاوت که:
  • سازنده هم نام کلاس است.
  • برای سازنده نوع خروجی در نظر گرفته نمی‌شود.

در مثال ما محدوده مجاز برای قاعده و ارتفاع مثلث بین ۱ تا ۱۰۰ است در حالی که سازنده پیش فرض مقدار صفر را برای آنها تنظیم خواهد نمود. پس برای اینکه مطمئن شویم اشیاء مثلث ساخته شده از این کلاس در همان بدو تولید دارای قاعده و ارتفاع معتبری هستند سازنده زیر را به صورت صریح در کلاس تعریف می‌کنیم تا جایگزین سازنده پیش فرضی شود که کامپایلر خواهد ساخت و به جای آن فراخوانی گردد.
public Triangle()
{
    _height = _baseLength = 1;
}
در این سازنده مقدار ۱ را برای متغیر خصوصی پشت (backing field یا backing store) هر یک از دو ویژگی قاعده و ارتفاع تنظیم نموده ایم.

اجرای سازنده

همانطور که گفته شد سازنده اضافه شده به کلاس جایگزین سازنده پیش فرض کامپایلر شده و در هنگام ایجاد یک شیء جدید از کلاس مثلث توسط عملگر new اجرا می‌شود. برای بررسی اجرا شدن سازنده به سادگی می‌توان کدی مشابه مثال زیر را نوشت.
Triangle triangle = new Triangle();
 
Console.WriteLine(triangle.Height);
Console.WriteLine(triangle.BaseLength);
Console.WriteLine(triangle.Area);
کد بالا مقدار ۱ را برای قاعده و ارتفاع و مقدار ۰.۵ را برای مساحت چاپ می‌نماید. بنابراین مشخص است که سازنده اجرا شده و مقادیر مناسب را برای شیء تنظیم نموده به طوری که شیء از بدو تولید در وضعیت مناسبی است.

سازنده‌های پارامتر دار

در مثال قبل یک سازنده بدون پارامتر را به کلاس اضافه کردیم. این سازنده تنها مقادیر پیش فرض مناسبی را تنظیم می‌کند. بدیهی است پس از ایجاد شیء در صورت نیاز می‌توان مقادیر مورد نظر دیگر را برای قاعده و ارتفاع تنظیم نمود. اما برای اینکه سازنده بهتر بتواند فرآیند وهله سازی را کنترل نماید می‌توان پارامترهایی را به آن افزود. افزودن پارامتر به سازنده مانند افزودن پارامتر به متدهای دیگر صورت می‌گیرد. در مثال زیر سازنده دیگری تعریف می‌کنیم که دارای دو پارامتر است. یکی قاعده و دیگری ارتفاع. به این ترتیب در حین فرآیند وهله سازی می‌توان مقادیر مورد نظر را منتسب نمود.
public Triangle(int height, int baseLength)
{
    Height = height;
    BaseLength = baseLength;
}
با توجه به اینکه مقادیر ارسالی به این سازنده توسط کد مشتری در نظر گرفته می‌شود و ممکن است در محدوده مجاز نباشد، به جای انتساب مستقیم این مقادیر به فیلد خصوصی پشت ویژگی قاعده و ارتفاع یعنی baseLength_ و height_ آنها را به پروپرتی‌ها منتسب کردیم تا قانون اعتبارسنجی موجود در اکسسور set پروپرتی‌ها از انتساب مقادیر غیر مجاز جلوگیری کند.
سازنده اخیر را می‌توان به صورت زیر با استفاده از عملگر new و فراهم کردن آرگومان‌های مورد نظر مورد استفاده قرار داد.
Triangle triangle = new Triangle(5, 8);
 
Console.WriteLine(triangle.Height);
Console.WriteLine(triangle.BaseLength);
Console.WriteLine(triangle.Area);
مقادیر چاپ شده برابر ۵ برای ارتفاع، ۸ برای قاعده و ۲۰ برای مساحت خواهد بود که نشان از اجرای صحیح سازنده دارد.
در مطالب بالا چندین بار از سازنده‌ها صحبت کردیم و گفتیم سازنده دیگری به کلاس اضافه می‌کنیم. این دو نکته را به خاطر داشته باشید:
  • یک کلاس می‌تواند دارای چندین سازنده باشد که بر اساس آرگومان‌های فراهم شده هنگام وهله سازی، سازنده مورد نظر انتخاب و اجرا می‌شود.
  • الزامی به تعریف یک سازنده پیش فرض (به معنای بدون پارامتر) نیست. یعنی یک کلاس می‌تواند هیچ سازنده بی پارامتری نداشته باشد.

در بخش‌های بعدی مطالب بیشتری در مورد سازنده‌ها و سایر اعضای کلاس خواهید آموخت.