مطالب دوره‌ها
نکاتی درباره برنامه نویسی دستوری(امری)
در این فصل نکاتی را درباره برنامه نویسی دستوری در #F فرا خواهیم گرفت. برای شروع از mutale خواهیم گفت.

mutable
Keyword
در فصل دوم(شناسه ها) گفته شد که برای یک شناسه امکان تغییر مقدار وجود ندارد. اما در #F راهی وجود دارد که در صورت نیاز بتوانیم مقدار یک شناسه را تغییر دهیم.در #F هرگاه بخواهیم شناسه ای تعریف کنیم که بتوان در هر زمان مقدار شناسه رو به دلخواه تغییر داد از کلمه کلیدی mutable کمک می‌گیریم و برای تغییر مقادیر شناسه‌ها کافیست از علامت (->) استفاده کنیم. به یک مثال در این زمینه دقت کنید:
#1 let mutable phrase = "Can it change? "

#2 printfn "%s" phrase

#3 phrase <- "yes, it can."

#4 printfn "%s" phrase


در خط اول یک شناسه را به صورت mutable(تغییر پذیر) تعریف کردیم و در خط سوم با استفاده از (->) مقدار شناسه رو update کردیم. خروجی مثال بالا به صورت زیر است:
Can it change?  
yes, it can.

نکته اول: در این روش هنگام update کردن مقدار شناسه حتما باید مقدار جدید از نوع مقدار قبلی باشد در غیر این صورت با خطای کامپایلری متوقف خواهید شد.
#1 let mutable phrase = "Can it change?  "

#3 phrase <- 1

اجرای کد بالا خطای زیر را به همراه خواهد داشت.(خطا کاملا واضح است و نیاز به توضیح دیده نمی‌شود)
Prog.fs(9,10): error: FS0001: This expression has type
int
but is here used with type
string
نکته دوم :ابتدا به مثال زیر توجه کنید.
let redefineX() =
let x = "One"
printfn "Redefining:\r\nx = %s" x
if true then
   let x = "Two"
printfn "x = %s" x
printfn "x = %s" x

در مثال بالا در تابع redefineX یک شناسه به نام x تعریف کردم با مقدار "One". یک بار مقدار شناسه x رو چاپ می‌کنیم و بعد دوباره بعد از شرط true یک شناسه دیگر با همون نام یعنی x تعریف شده است و در انتها هم دو دستور چاپ. ابتدا خروجی مثال بالا رو با هم مشاهده می‌کنیم.
Redefining:
x = One
x = Two
x = One
همان طور که میبینید شناسه دوم x بعد از تعریف دارای مقدار جدید Two بود و بعد از اتمام محدوده(scope) مقدار x دوباره به One تغییر کرد.(بهتر است بگوییم منظور از دستور print x سوم اشاره به شناسه x اول برنامه است). این رفتار مورد انتظار ما در هنگام استفاده از روش تعریف مجدد شناسه هاست. حال به بررسی رفتار muatable در این حالت می‌پردازیم.
let mutableX() =
let mutable x = "One"
printfn "Mutating:\r\nx = %s" x
if true then
   x <- "Two"
printfn "x = %s" x
printfn "x = %s" x
تنها تفاوت در استفاده از mutable keyword و (->) است. خروجی مثال بالا نیز به صورت زیر خواهد بود. کاملا واضح است که مقدار شناسه x بعد از تغییر و اتمام محدوده(scope) هم چنان Two خواهد بود.
Mutating:
x = One
x = Two
x = Two

Reference Cells

روشی برای استفاده از شناسه‌ها به صورت mutable است. با این روش می‌تونید شناسه هایی تعریف کنید که امکان تغییر مقدار برای اون‌ها وجود دارد. زمانی که از این روش برای مقدار دهی به شناسه‌ها استفاده کنیم یک کپی از مقدار مورد نظر به شناسه اختصاص داده می‌شود نه آدرس مقدار در حافظه.
به جدول زیر توجه کنید:

 Member Or Field
Description
 Definition
(derefence operator)!
 مقدار مشخص شده را برگشت می‌دهد
 

let (!) r = r.contents 

 (Assignment operator)=: مقدار مشخص شده را تغییر می‌دهد
 

let (:=) r x = r.contents <- x 

ref operator
 یک مقدار را در یک reference cell  جدید کپسوله می‌کند
 

let ref x = { contents = x } 

Value Property
 برای عملیات get  یا set  مقدار مشخص شده
 

member x.Value = x.contents 

 contents record field
 برای عملیات get  یا set  مقدار مشخص شده

let ref x = { contents = x } 

  یک مثال:
let refVar = ref 6

refVar := 50
printfn "%d" !refVar
خروجی مثال بالا 50 خواهد بود.
let xRef : int ref = ref 10

printfn "%d" (xRef.Value)
printfn "%d" (xRef.contents)

xRef.Value <- 11
printfn "%d" (xRef.Value)
xRef.contents <- 12
printfn "%d" (xRef.contents)
خروجی مثال بالا:
10
10
11
12 

خصیصه اختیاری در #F
در #F زمانی از خصیصه اختیاری استفاده می‌کنیم که برای یک متغیر مقدار وجود نداشته باشد. option  در #F نوعی است که می‌تواند هم مقدار داشته باشد و هم نداشته باشد.
let keepIfPositive (a : int) = if a > 0 then Some(a) else None
از None زمانی استفاده می‌کنیم که option مقدار نداشته باشد و از Some  زمانی استفاده می‌کنیم که option مقدار داشته باشد.
let exists (x : int option) = 
    match x with
    | Some(x) -> true
    | None -> false
در مثال بالا ورودی تابع exists از نوع int و به صورت اختیاری تعریف شده است.(معادل با ?int یا<Nullable<int در #C) در صورتی که x مقدار داشته باشد مقدار true در غیر این صورت مقدار false را برگشت می‌دهد.

چگونگی استفاده  از option
مثال
let rec tryFindMatch pred list =
    match list with
    | head :: tail -> if pred(head)
                        then Some(head)
                        else tryFindMatch pred tail
    | [] -> None

let result1 = tryFindMatch (fun elem -> elem = 100) [ 200; 100; 50; 25 ] //برابر با 100 است

let result2 = tryFindMatch (fun elem -> elem = 26) [ 200; 100; 50; 25 ]// برابر با None است
یک تابع به نام tryFindMatch داریم با دو پارامتر ورودی. با استفاده از الگوی Matching از عنصر ابتدا تا انتها را در لیست (پارامتر list) با مقدار پارامتر pred مقایسه می‌کنیم. اگر مقادیر برابر بودند مقدار head در غیر این صورت None(یعنی option مقدار ندارد) برگشت داده می‌شود.
یک مثال کاربردی تر
open System.IO
let openFile filename =
    try 
        let file = File.Open (filename, FileMode.Create)
        Some(file)
    with
        | ex -> eprintf "An exception occurred with message %s" ex.Message
                None
در مثال بالا از option‌ها برای بررسی وجود یا عدم وجود فایل‌های فیزیکی استفاده کردم.

Enumeration
تقریبا همه با نوع داده شمارشی یا enums آشنایی دارند. در اینجا فقط به نحوه پیاده سازی آن در #F می‌پردازیم. ساختار کلی تعریف آن به صورت زیر است:
type enum-name =
   | value1 = integer-literal1
   | value2 = integer-literal2
   ...
یک مثال از تعریف:
type Color =
   | Red = 0
   | Green = 1
   | Blue = 2
نحوه استفاده
let col1 : Color = Color.Red
enums فقط از انواع داده ای sbyte, byte, int16, uint16, int32, uint32, int64, uint16, uint64, char پشتیبانی می‌کند که البته مقدار پیش فرض آن Int32 است. در صورتی که بخواهیم صریحا نوع داده ای را ذکر کنیم به صورت زیر عمل می‌شود.
type uColor =
   | Red = 0u
   | Green = 1u
   | Blue = 2u
let col3 = Microsoft.FSharp.Core.LanguagePrimitives.EnumOfValue<uint32, uColor>(2u)

توضیح درباره use
در دات نت خیلی از اشیا هستند که اینترفیس IDisposable رو پیاده سازی کرده اند. این بدین معنی است که حتما یک متد به نام dispose برای این اشیا وجود دارد که فراخوانی آن به طور قطع باعث بازگرداندن حافظه ای که در اختیار این کلاس‌ها بود می‌شود. برای راحتی کار در #C یک عبارت به نام using وجود دارد که در انتها بلاک متد dispose شی مربوطه را فراخوانی می‌کند.
using(var writer = new StreamWriter(filePath))
{
   
}
در #F نیز امکان استفاده از این عبارت با اندکی تفاوت وجود دارد.مثال:
let writeToFile fileName =
    use sw = new System.IO.StreamWriter(fileName : string)
    sw.Write("Hello ")
Units Of Measure
در #F اعداد دارای علامت و اعداد شناور دارای وابستگی با واحد‌های اندازه گیری هستند که به نوعی معرف اندازه و حجم و مقدار و ... هستند. در #F شما مجاز به تعریف واحد‌های اندازه گیری خاص خود هستید و در این تعاریف نوع عملیات اندازه گیری را مشخص می‌کنید. مزیت اصلی استفاده از این روش جلوگیری از رخ دادن خطاهای کامپایلر در پروژه است. ساختار کلی تعریف:
[<Measure>] type unit-name [ = measure ]
یک مثال از تعریف واحد cm:
[<Measure>] type cm
مثالی از تعریف میلی لیتر:
[<Measure>] type ml = cm^3
برای استفاده از این واحد‌ها می‌تونید به روش زیر عمل کنید.
let value = 1.0<cm>
توابع تبدیل واحد ها
قدرت اصلی واحد‌های اندازه گیری  #F در توابع تبدیل است. تعریف توابع تبدیل به صورت زیر می‌باشد:
[<Measure>] type g                 تعریف واحد گرم
[<Measure>] type kg               تعریف واحد کیلوگرم
let gramsPerKilogram : float<g kg^-1> = 1000.0<g/kg>    تعریف تابع تبدیل
یک مثال دیگر :
[<Measure>] type degC // دما بر حسب سلسیوس
[<Measure>] type degF // دما بر حسب فارنهایت

let convertCtoF ( temp : float<degC> ) = 9.0<degF> / 5.0<degC> * temp + 32.0<degF> // تابع تبدیل سلسیوس به فارنهایت
let convertFtoC ( temp: float<degF> ) = 5.0<degC> / 9.0<degF> * ( temp - 32.0<degF>) // تابع تبدیل فارنهایت به سلسیوس

let degreesFahrenheit temp = temp * 1.0<degF> // درجه به فارنهایت
let degreesCelsius temp = temp * 1.0<degC> // درجه به سلسیوس

printfn "Enter a temperature in degrees Fahrenheit." 
let input = System.Console.ReadLine()
let mutable floatValue = 0.
if System.Double.TryParse(input, &floatValue)// اگر ورودی عدد بود
   then 
      printfn "That temperature in Celsius is %8.2f degrees C." ((convertFtoC (degreesFahrenheit floatValue))/(1.0<degC>))
   else
      printfn "Error parsing input."

خروجی مثال بالا :

Enter a temperature in degrees Fahrenheit.
90
That temperature in degrees Celsius is    32.22.
مطالب
طراحی و پیاده سازی DomainEvents

شرایطی را در نظر بگیرید که نیاز است از تغییرات یک Entity در سیستم آگاه شویم. برای مثلا در زمان ثبت سفارش جدید در فروشگاه، ایمیلی به مدیر فروشگاه ارسال شود، یک Business Rule نیز چک شود و همچنین بنابر نیاز مشتری، تعداد آنها روز به روز ممکن است افزایش یابد و چه بسا در اعمال این Ruleها، موجودیت‌های مختلفی درگیر باشند. در این صورت است که خواسته یا ناخواسته اتصال بین کلاس‌ها خیلی افزایش خواهد یافت. یکی از راه حل‌های رهایی از این پیچیدگی و اتصال بالا، استفاده از Event می‌باشد.

هدف طراحی و پیاده سازی زیرساختی برای استفاده از DomainEventها می‌باشد. کدهای کامل این مطلب را می‌توانید از اینجا دریافت کنید. 


Domain Event چیست؟

چیزی که در یک Domain خاصی رخ داده است و هدف از آن آگاه کردن سایر بخش‌های آن Domain می‌باشد تا بتوانند واکنش مناسبی را نشان دهند. با بهره گیری از این نوع رویدادها، می‌توان Separation Of Concerns خوبی را بین کلاس‌های موجود در آن Domain اعمال کرد و به طراحی ای با Coupling پایین رسید. این رویداد‌ها عموما داخل پروسه Raise می‌شوند.

برای اطلاعات بیشتر در این زمینه پیشنهاد میکنم این مطلب را مطالعه کنید.
کار را با معرفی واسط IDomainEvent آغاز می‌کنیم.
namespace DomainEventsSample.Framework.Eventing.DomainEvents
{
    public interface IDomainEvent : ITransientDependency
    {
    }
}
در کد بالا، واسط ITransientDepedency برای اعمال طول عمر وهله‌های ساخته شده توسط StructureMap در نظر گرفته شده است. برای علامت گذاری DomainEventها، از واسط بالا استفاده خواهیم کرد. 

واسط IDomainEventHandler
namespace DomainEventsSample.Framework.Eventing.DomainEvents
{
    public interface IDomainEventHandler<in T> : ITransientDependency
        where T : IDomainEvent
    {
        bool IsAdvisable { get; }
        void Handle(T domainEvent);
    }
}
از کلاس‌های پیاده ساز واسط بالا، می‌توان برای مدیریت رویداد خاصی استفاده کرد. برای علامت گذاری DomainEventHandlerها نیز از این واسط استفاده میشود. 
خصوصیت IsAdvisable: اگر مقدار آن true باشد، در این صورت در زمان صدور استثنایی در روند اجرای متد Handle آن از این استثناء چشم پوشی شده و مابقی هندلرها فراخوانی خواهند شد.

پیاده سازی Engine مربوط به Raise کردن رویدادها
روند کار به این شکل است:
  1. متد Raise مربوط به Engine برای رویداد خاصی فراخوانی می‌شود.
  2. با استفاده از یک IOC Container، تمام هندلرهای مربوط به رویداد جمع آوری می‌شود.
  3. متد Handle مربوط به تک تک هندلرها، فراخوانی خواهد شد.
namespace DomainEventsSample.Framework.Eventing.DomainEvents
{
    public interface IDomainEventEngine : ISingletonDependency
    {
        void Raise<T>(T domainEvent) where T : IDomainEvent;
    }
}

namespace DomainEventsSample.Framework.Eventing.DomainEvents
{
    public class DomainEventEngine : IDomainEventEngine
    {
        private readonly IContainer _container;

        public DomainEventEngine(IContainer container)
        {
            _container = container;
        }

        public void Raise<T>(T domainEvent) where T : IDomainEvent
        {
            foreach (var handler in _container.GetAllInstances<IDomainEventHandler<T>>())
                try
                {
                    handler.Handle(domainEvent);
                }
                catch (Exception)
                {
                    if (domainEvent.IsAdvisable && handler.IsAdvisable)
                        throw;
                }
        }
    }
}
طول عمر این Engine به صورت Singleton در نظر گرفته شده است. همانطور که گفته شد، در صورت صدور استثناء، در صورت IsAdvisable بودن خود رویداد، خصوصیت IsAdvisable هندلر آن بررسی خواهد شد.
شاید بهتر باشد یکسری رویداد پیش فرض هم در زیرساخت پروژه خود داشته باشیم. برای مثال رویدادهای مربوط به Entityها که در زیر آنها را مشاهده می‌کنید:
namespace DomainEventsSample.Framework.Domain.Events
{
    public abstract class EntityDomainEvent<TEntity> : IDomainEvent
        where TEntity : Entity
    {
        protected EntityDomainEvent(TEntity entity)
        {
            Entity = entity;
        }

        public TEntity Entity { get; }
    }
}

کلاس بالا به عنوان کلاس پایه یکسری رویداد مشترک مابین Entity‌های سیستم در نظر گرفته شده است.

namespace DomainEventsSample.Framework.Domain.Events
{
    public class EntityCreatingEvent<TEntity> : EntityDomainEvent<TEntity>
        where TEntity : Entity
    {
        public EntityCreatingEvent(TEntity entity) : base(entity)
        {
        }
    }
}
namespace DomainEventsSample.Framework.Domain.Events
{
    public class EntityCreatedEvent<TEntity> : EntityDomainEvent<TEntity>
        where TEntity : Entity
    {
        public EntityCreatedEvent(TEntity entity) : base(entity)
        {
        }
    }
}

این رویدادها مربوط به زمان قبل و بعد از ایجاد یک Entity می‌باشند.

namespace DomainEventsSample.Framework.Domain.Events
{
    public class EntityEditingEvent<TEntity> : EntityDomainEvent<TEntity>
        where TEntity : Entity
    {
        public EntityEditingEvent(TEntity entity) : base(entity)
        {
        }
    }
}
namespace DomainEventsSample.Framework.Domain.Events
{
    public class EntityEditedEvent<TEntity> : EntityDomainEvent<TEntity>
        where TEntity : Entity
    {
        public EntityEditedEvent(TEntity entity) : base(entity)
        {
        }
    }
}

این رویدادها مربوط به زمان قبل و بعد از ویرایش یک Entity می‌باشند. 

namespace DomainEventsSample.Framework.Domain.Events
{
    public class EntityDeletingEvent<TEntity> : EntityDomainEvent<TEntity>
        where TEntity : Entity
    {
        public EntityDeletingEvent(TEntity entity) : base(entity)
        {
        }
    }
}
namespace DomainEventsSample.Framework.Domain.Events
{
    public class EntityDeletedEvent<TEntity> : EntityDomainEvent<TEntity>
        where TEntity : Entity
    {
        public EntityDeletedEvent(TEntity entity) : base(entity)
        {
        }
    }
}

این رویدادها مربوط به زمان قبل و بعد از حذف یک Entity می‌باشند.

namespace DomainEventsSample.Framework.Domain.Events
{
    public class EntitySavingEvent<TEntity> : EntityDomainEvent<TEntity>
        where TEntity : Entity
    {
        public EntitySavingEvent(TEntity entity) : base(entity)
        {
        }
    }
}

namespace DomainEventsSample.Framework.Domain.Events
{
    public class EntitySavedEvent<TEntity> : EntityDomainEvent<TEntity>
        where TEntity : Entity
    {
        public EntitySavedEvent(TEntity entity) : base(entity)
        {
        }
    }
}

این رویدادها مربوط به زمان قبل و بعد از ذخیره (ایجاد و ویرایش) یک Entity می‌باشند.

نکته: برای اسکن کردن تمام هندلرها لازم است کد زیر را به تنظیمات StructureMap اضافه کنید:

Scan(scan =>
{
    scan.ConnectImplementationsToTypesClosing(typeof(IDomainEventHandler<>));
});

مثال: برای مثال این بار برای آگاه سازی کاربران به صورت بلادرنگ از اضافه شدن یک کالا، می‌توان با هندل کردن رویداد مربوط به ایجاد کالا، به شکل زیر عمل کرد:
public class ProductCreatedEventHandler : IDomainEventHandler<EntityCreatedEvent<Product>>
{
    public bool IsAdvisable => false;

    public void Handle(EntityCreatedEvent<Product> domainEvent)
    {
        //todo: notify users
    }
}

در متد Create مربوط به ProductApplicationService و بعد از عملیات ذخیره سازی به شکل زیر می‌بایست عمل کرد:

public class ProductApplicationService : IProductApplicationService
{
    private readonly IDomainEventEngine _eventEngine;
    private readonly IUnitOfWork _unitOfWork;
    private readonly IMapper _mapper;

    public ProductApplicationService(IDomainEventEngine eventEngine,IMapper mapper,IUnitOfWork unitOfWork)
    {
        _eventEngine = eventEngine;
        _mapper=mapper;
        _unitOfWork=unitOfWork;
    }

    [Transactional]
    public void Create(ProductCreateViewModel model)
    {
         var entity=_mapper.Map<Product>(model);
        _unitOfWork.Set<Product>().Add(entity);
        _unitOfWork.SaveChanges();
        _eventEngine.Raise(new EntityCreatedEvent<Product>(entity));
    }
}

البته بهتر است برای Raise کردن این نوع رویدادها از مکانیزم Hook استفاده کرد و در زمان ذخیره سازی و فراخوانی متد SaveChange، این عملیات به صورت خودکار صورت گیرند.

در مقاله بعدی با استفاده از Hookها این عملیات را انجام خواهیم داد. 


کدهای این قسمت را می‌توانید از اینجا دریافت کنید.
اشتراک‌ها
نقشه ذهنی #C

This blog post contains a mind map of language features starting from C# 1 up til now - including some of the new C# 12 features that will be released in November 2023.

نقشه ذهنی #C
نظرات مطالب
آشنایی با TransactionScope
SaveChanges operates within a transaction. SaveChanges will roll back that transaction and
throw an exception if any of the dirty ObjectStateEntry objects cannot be persisted.
خود متد SaveChanges از تراکنش استفاده می‌کنه و کل تغییرات DbContext رو در طی یک تراکنش اعمال می‌کنه.
اشتراک‌ها
رندر سمت سرور کامپوننت‌های Blazor در دات نت 8

  The "Blazor United" effort is really a collection of features we're adding to Blazor so that you can get the best of server & client based web development. These features include: Server-side rendering, streaming rendering, enhanced navigations & form handling, add client interactivity per page or component, and determining the client render mode at runtime. We've started delivering server-side rendering support for Blazor with .NET 8 Preview 3, which is now available to try out. We plan to deliver the remaining features in upcoming previews. We hope to deliver them all for .NET 8, but we'll see how far we get. 

رندر سمت سرور کامپوننت‌های Blazor در دات نت 8