مطالب
ساده سازی تعریف فضاهای نام در C# 10.0
در ادامه‌ی طراحی مبتنی بر مینی‌مالیسم C# 10.0، پس از پیش‌فرض شدن «top level programs» و همچنین «کاهش تعداد بار تعاریف usingها»، تغییر سوم صورت گرفته‌ی در قالب‌های پروژه‌های مبتنی بر دات نت 6، ساده سازی تعاریف فضاهای نام است. برای مثال یک کنترلر، به این صورت تعریف شده‌است:
namespace mvc.Controllers;

public class HomeController : Controller
{
}
که به آن «File-Scoped Namespaces» هم گفته می‌شود.


بررسی مفهوم «File-Scoped Namespaces»

یکی از اهداف مهم C# 10.0، کاهش نویز موجود در فایل‌های cs. است. اگر قرار است صدها بار در فایل‌های مختلف برنامه، using System نوشته شود، چرا یکبار آن‌را به صورت عمومی تعریف نکنیم و یا اگر در 99 درصد موارد، توسعه دهنده‌ها به ازای یک فایل، تنها یک فضای نام را تعریف می‌کنند، چرا باید یک فضای اضافی خالی، برای تعریف آن اختصاص داده شود و تمام فایل‌ها به همراه یک «tab فاصله‌ی» اضافی مختص به این فضای نام باشند؟
تعریف فعلی فضاهای نام در #C به صورت زیر است:
namespace MyNamespace
{
    public class MyClass
    {
        public void MyMethod()
        {
            //...Method implementation
        }
    }
}
در این حالت هر شیءای که داخل {} این فضای نام قرار گیرد، متعلق به آن است.
در C# 10.0، می‌توان این تعریف را ساده کرد؛ از آنجائیکه به ندرت چند فضای نام در یک تک فایل تعریف می‌شوند، می‌توان تعریف فضای نام را در یک سطر، در ابتدای فایل ذکر کرد، تا به صورت خودکار به کل فایل و اشیاء موجود در آن اعمال شود:
namespace MyNamespace;
public class MyClass
{
    public void MyMethod()
    {
        //...Method implementation
    }
}
در این حالت، روش استفاده‌ی از یک چنین اشیایی هیچ تغییری نخواهد کرد؛ فقط یک tab space و فاصله از کنار صفحه، صرفه‌جویی می‌شود!


محدویت‌های «File-Scoped Namespaces»

- بدیهی است در این حالت دیگر نمی‌توان چندین فضای نام را همانند قبل در یک فایل cs. تعریف کرد:
namespace Name1
{
    public class Class1
    {
    }
}

namespace Name1.Name2
{
  public class Class2
  {
  }
}
 و البته این موردی است که جزو best practices توسعه‌ی برنامه‌های #C به هیچ عنوان توصیه نمی‌شود.
- همچنین امکان ترکیب روش قبلی تعریف فضاهای نام، با روش جدید، در یک فایل وجود ندارد.
- به علاوه امکان تعریف فضاهای نام تو در تو که با روش قدیمی وجود دارد:
namespace Name1
{
    public class Class1
    {
    }

    namespace Name1.Name2
    {
        public class Class2
        {
        }
    }
}
در این حالت جدید پشتیبانی نمی‌شود.
مطالب
آشنایی با Refactoring - قسمت 10

یکی دیگر از روش‌هایی که جهت بهبود کیفیت کدها مورد استفاده قرار می‌گیرد، «طراحی با قراردادها» است؛ به این معنا که «بهتر است» متدهای تعریف شده پیش از استفاده از آرگومان‌های خود، آن‌ها را دقیقا بررسی کنند و به این نوع پیش شرط‌ها، قرارداد هم گفته می‌شود.
نمونه‌ای از آن‌را در قسمت 9 مشاهده کردید که در آن اگر آرگومان‌های متد AddRole، خالی یا نال باشند، یک استثناء صادر می‌شود. این نوع پیغام‌های واضح و دقیق در مورد عدم اعتبار ورودی‌های دریافتی، بهتر است از پیغام‌های کلی و نامفهوم null reference exception که بدون بررسی stack trace و سایر ملاحظات، علت بروز آن‌ها مشخص نمی‌شوند.
در دات نت 4، جهت سهولت این نوع بررسی‌ها، مفهوم Code Contracts ارائه شده است. (این نام هم از این جهت بکارگرفته شده که Design by Contract نام تجاری شرکت ثبت شده‌ای در آمریکا است!)


یک مثال:
متد زیر را در نظر بگیرید. اگر divisor مساوی صفر باشد، استثنای کلی DivideByZeroException صادر می‌شود:

namespace Refactoring.Day10.DesignByContract.Before
{
public class MathMehods
{
public double Divide(int dividend, int divisor)
{
return dividend / divisor;
}
}
}

روش متداول «طراحی با قراردادها» جهت بهبود کیفیت کد فوق پیش از دات نت 4 به صورت زیر است:

using System;

namespace Refactoring.Day10.DesignByContract.After
{
public class MathMehods
{
public double Divide(int dividend, int divisor)
{
if (divisor == 0)
throw new ArgumentException("divisor cannot be zero", "divisor");

return dividend / divisor;
}
}
}

در اینجا پس از بررسی آرگومان divisor، قرارداد خود را به آن اعمال خواهیم کرد. همچنین در استثنای تعریف شده، پیغام واضح‌تری به همراه نام آرگومان مورد نظر، ذکر شده است که از هر لحاظ نسبت به استثنای استاندارد و کلی DivideByZeroException مفهوم‌تر است.

در دات نت 4 ، به کمک امکانات مهیای در فضای نام System.Diagnostics.Contracts، این نوع بررسی‌ها نام و امکانات درخور خود را یافته‌اند:

using System.Diagnostics.Contracts;

namespace Refactoring.Day10.DesignByContract.After
{
public class MathMehods
{
public double Divide(int dividend, int divisor)
{
Contract.Requires(divisor != 0, "divisor cannot be zero");

return dividend / divisor;
}
}
}

البته اگر قطعه کد فوق را به همراه divisor=0 اجرا کنید، هیچ پیغام خاصی را مشاهده نخواهید کرد؛ از این لحاظ که نیاز است تا فایل‌های مرتبط با آن‌را از این آدرس دریافت و نصب کنید. این کتابخانه با VS2008 و VS2010 سازگار است. پس از آن، برگه‌ی Code contracts به عنوان یکی از برگه‌های خواص پروژه در دسترس خواهد بود و به کمک آن می‌توان مشخص کرد که برنامه حین رسیدن به این نوع بررسی‌ها چه عکس العملی را باید بروز دهد.

برای مطالعه بیشتر:
مسیرراه‌ها
Entity framework code-first
شروع به کار با EF Code first

برای تکمیل بحث نیاز است تغییرات انجام شده از نگارش 4 به 6 را نیز مد نظر داشته باشید:


آشنایی با مباحث Migrations



آشنایی با تنظیمات نگاشت‌ها به دو روش استفاده از ویژگی‌ها و Fluent API



اعتبارسنجی و بررسی استثناءها



ردیابی تغییرات



استفاده از SQL خام و بانک‌های اطلاعاتی متفاوت

      نکات مهم کوئری نویسی در EF



      استفاده از EF در WPF


      لایه بندی پروژه‌های EF Code first



      پروژ‌ه‌های انجام شده با EF Code first

       
      نظرات مطالب
      EF Code First #12
      با سلام، من کد شما را به صورت زیر تغییر دادم
      ابتدا یک اینترفیس به صورت زیر ایجاد کردم
      namespace Service.Interfaces
      {
          public interface IGenericService<T> 
          {
              void AddOrUpdate(T entity);
              void Delete(T entity);
              T Find(Func<T, bool> predicate);
              T GetLast(Func<T, bool> predicate);
              IList<T> GetAll();
              IList<T> GetAll(Func<T, bool> predicate);
              IList<T> GetAll(Expression<Func<T, object>> orderby);
              IList<T> GetAll(Func<T, bool> predicate, Expression<Func<T, object>> orderby);
      
              Task<List<T>> GetAllAsync();
              Task<List<T>> GetAllAsync(Func<T, bool> predicate);
              Task<List<T>> GetAllAsync(Expression<Func<T, object>> orderby);
              Task<List<T>> GetAllAsync(Func<T, bool> predicate, Expression<Func<T, object>> orderby);
      
              int Count();
              int Count(Func<T, bool> predicate);
          }
      }

      و تمام اینترفیس‌های دیگر از این به صورت زیر به ارث برده شده اند
      public interface IBookGroupService:IGenericService<BookGroup>
          {
          }

      و در قسمت Servise یک کلاس ایجاد کردم که اینترفیس IGenericService  را پیاده سازی می‌کند که کدهای آن به صورت زیر است

      public class EFGenericService<TEntity> : IGenericService<TEntity>
               where TEntity : class
          {
              protected IUnitOfWork _uow;
              protected IDbSet<TEntity> _tEntities;
      
              public EFGenericService(IUnitOfWork uow)
              {
                  _uow = uow;
                  _tEntities = _uow.Set<TEntity>();
              }
      
      
              public void AddOrUpdate(TEntity entity)
              {
                   _tEntities.AddOrUpdate(entity);
              }
      
              public virtual void Delete(TEntity entity)
              {
                  _tEntities.Remove(entity);
              }
      
              public virtual TEntity Find(Func<TEntity, bool> predicate)
              {
                  return _tEntities.Where(predicate).FirstOrDefault();
              }
      
              public virtual TEntity GetLast(Func<TEntity, bool> predicate)
              {
                  return _tEntities.Where(predicate).Last();
              }
      
      
              public virtual IList<TEntity> GetAll()
              {
                  return _tEntities.ToList();
              }
      
              public virtual IList<TEntity> GetAll(Func<TEntity, bool> predicate)
              {
                  return _tEntities.Where(predicate).ToList();
              }
      
              public virtual IList<TEntity> GetAll(Expression<Func<TEntity, object>> @orderby)
              {
                  return _tEntities.OrderBy(@orderby).ToList();
              }
      
              public virtual IList<TEntity> GetAll(Func<TEntity, bool> predicate, Expression<Func<TEntity, object>> @orderby)
              {
                  return _tEntities.OrderBy(@orderby).Where(predicate).ToList();
              }
      
              public async Task<List<TEntity>> GetAllAsync()
              {
                 return await Task.Run(() => _tEntities.ToList());
              }
      
              public async Task<List<TEntity>> GetAllAsync(Func<TEntity, bool> predicate)
              {
                return await Task.Run(() => _tEntities.Where(predicate).ToList());
              }
      
              public async Task<List<TEntity>> GetAllAsync(Expression<Func<TEntity, object>> @orderby)
              {
                 return await Task.Run(() => _tEntities.OrderBy(@orderby).ToList());
              }
      
              public async Task<List<TEntity>> GetAllAsync(Func<TEntity, bool> predicate, Expression<Func<TEntity, object>> @orderby)
              {
                 return await Task.Run(()=> _tEntities.OrderBy(@orderby).Where(predicate).ToList());
              }
      
              public virtual int Count()
              {
                  return _tEntities.Count();
              }
      
              public virtual int Count(Func<TEntity, bool> predicate)
              {
                  return _tEntities.Count(predicate);
              }
          }


      و بقیه کلاس‌ها از کلاس بالا به ارث می‌برند.

      public class EFBorrowService:EFGenericService<Borrow>,IBorrowService
          {
              public EFBorrowService(IUnitOfWork uow) : base(uow)
              {
              }
          }


      حال سوال من اینه که این پیاده سازی از لحاظ پیاده سازی مشکلی ندارد؟ و می‌توانم در پروژه هام از این روش استفاده کنم یا خیر؟

      ممنونم
      مطالب
      حل مشکل بارگذاری اولیه دستورات جاوا اسکریپتی در پروژه‌های Blazor
      مشکل:
      ممکن است بخواهید در برنامه‌های Blazor از یک قطعه کد آماده استفاده نمایید که در آن از دستورات Javascript استفاده شده باشد و تعدادی رویداد برای المان‌های صفحه تعریف کرده باشند؛ به عنوان مثال من از قالب آماده Nice Admin استفاده می‌کنم که در آن برای تمام قالب، از یک فایل به نام main.js استفاده شده‌است و در آن برای مخفی و ظاهر نمودن منو، از یک دکمه toggle استفاده کرده‌است. برای این عملیات، یک رویداد کلیک در این فایل تعریف شده‌:
      /**
      * Template Name: NiceAdmin - v2.1.0
      * Template URL: https://bootstrapmade.com/nice-admin-bootstrap-admin-html-template/
      * Author: BootstrapMade.com
      * License: https://bootstrapmade.com/license/
      */
      (function() {
        "use strict";
      
        /**
         * Easy selector helper function
         */
        const select = (el, all = false) => {
          el = el.trim()
          if (all) {
            return [...document.querySelectorAll(el)]
          } else {
            return document.querySelector(el)
          }
        }
      
        /**
         * Easy event listener function
         */
        const on = (type, el, listener, all = false) => {
          if (all) {
            select(el, all).forEach(e => e.addEventListener(type, listener))
          } else {
            select(el, all).addEventListener(type, listener)
          }
        }
      
        /**
         * Easy on scroll event listener 
         */
        const onscroll = (el, listener) => {
          el.addEventListener('scroll', listener)
        }
      
        /**
         * Sidebar toggle
         */
        if (select('.toggle-sidebar-btn')) {
          on('click', '.toggle-sidebar-btn', function(e) {
            select('body').classList.toggle('toggle-sidebar')
          })
        }
      
        /**
         * Search bar toggle
         */
        if (select('.search-bar-toggle')) {
          on('click', '.search-bar-toggle', function(e) {
            select('.search-bar').classList.toggle('search-bar-show')
          })
        }
      .
      .
      .
      })();
      و فراخوانی این فایل را در انتهای قسمت body فایل index.html یا Host.cshtml_ بصورت زیر قرار می‌دهیم:
      <!DOCTYPE html>
      <html dir="rtl">
      .
      .
      .
      
      <body>
          <div id="app">Loading...</div>
      
          <div id="blazor-error-ui">
              An unhandled error has occurred.
              <a href="">Reload</a>
              <a>🗙</a>
          </div>
          <script src="_framework/blazor.webassembly.js"></script>
      
      
          <!-- Vendor JS Files -->
          <script src="assets/vendor/bootstrap/js/bootstrap.bundle.js"></script>
          <script src="assets/vendor/php-email-form/validate.js"></script>
          <script src="assets/vendor/quill/quill.min.js"></script>
          <script src="assets/vendor/tinymce/tinymce.min.js"></script>
          <script src="assets/vendor/simple-datatables/simple-datatables.js"></script>
          <script src="assets/vendor/chart.js/chart.min.js"></script>
          <script src="assets/vendor/apexcharts/apexcharts.min.js"></script>
          <script src="assets/vendor/echarts/echarts.min.js"></script>
          <!-- Template Main JS File -->
          <script src="assets/js/main.js"></script>
      </body>
      
      </html>
      و حالا که پروژه را اجرا کنید، رویداد کلیک بر روی دکمه‌ی toggle کار نمی‌کند!
      دلیل:
      مشکل به این دلیل می‌باشد که کدهای جاوا اسکریپتی، بلافاصله با دانلود فایل اجرا می‌شوند؛ در حالیکه بارگذاری صفحه هنوز توسط blazor به اتمام نرسیده‌است. در نتیجه المان‌هایی که در این فایل به آن‌ها اشاره شده‌است، هنوز قابل دسترسی نیستند و رویدادهای تعریف شده‌ی برای آن‌ها، اجرا نمی‌شوند.
      راه حل:
      باید اجرای کدهای جاوا اسکریپتی را تا بارگذاری کامل صفحه به تعویق بیاندازیم. برای همین منظور ابتدا کدهای تعریف شده‌ی در فایل main.js را بجای اینکه مستقیما اجرا شوند، در یک تابع قرار می‌دهیم:
      /**
      * Template Name: NiceAdmin - v2.1.0
      * Template URL: https://bootstrapmade.com/nice-admin-bootstrap-admin-html-template/
      * Author: BootstrapMade.com
      * License: https://bootstrapmade.com/license/
      */
      function initilizeNiceAdminJs() {
        "use strict";
      
        /**
         * Easy selector helper function
         */
        const select = (el, all = false) => {
          el = el.trim()
          if (all) {
            return [...document.querySelectorAll(el)]
          } else {
            return document.querySelector(el)
          }
        }
      
        /**
         * Easy event listener function
         */
        const on = (type, el, listener, all = false) => {
          if (all) {
            select(el, all).forEach(e => e.addEventListener(type, listener))
          } else {
            select(el, all).addEventListener(type, listener)
          }
        }
      
        /**
         * Easy on scroll event listener 
         */
        const onscroll = (el, listener) => {
          el.addEventListener('scroll', listener)
        }
      
        /**
         * Sidebar toggle
         */
        if (select('.toggle-sidebar-btn')) {
          on('click', '.toggle-sidebar-btn', function(e) {
            select('body').classList.toggle('toggle-sidebar')
          })
        }
      
        /**
         * Search bar toggle
         */
        if (select('.search-bar-toggle')) {
          on('click', '.search-bar-toggle', function(e) {
            select('.search-bar').classList.toggle('search-bar-show')
          })
        }
      
      .
      .
      .
      }
      در مثال بالا من دستورات را داخل یک تابع به نام initilizeNiceAdminJs قرار دادم. سپس در فایل index.razor این تابع را در رویداد OnAfterRenderAsync  فراخوانی می‌نماییم:
        @page "/"
      
       @inject IJSRuntime JSRuntime
      
          <div>
              this is index page
          </div>
      @code {
      
          protected override async Task OnAfterRenderAsync(bool firstRender)
          {
              if (firstRender)
              {
                  await JSRuntime.InvokeVoidAsync("initilizeNiceAdminJs");// Initialize main.js after site completely loaded
              }
          }
      }

      بازخوردهای دوره
      تزریق وابستگی‌ها
      تزریق وابستگی رو تا چه سطحی باید انجام داد؟ یعنی رعایت کردن اون تو تمام سطوح نرم افزار باید انجام بشه؟ برای مثال کلاس زیر رو در نظر بگیرید که در لایه Entity  وجود داره
      class Parent
      {
          public IChild child {get;set;}
          public Parent(Ichild child)
         {
            this.child =child;
         }
      }
      آیا با اینکه کلاس پدر و فرزند در یک لایه مشترک هستند  ، در اینجا ارزش داره که  تزریق وابستگی رو انجام بدیم ؟حجم کد و کار بالا بالا نمیره؟ یا اینکه پیچیدگی زیاد نمیشه؟
      نظرات مطالب
      Soft Delete در Entity Framework 6
      برای مطلب فوق 2 متد به شکل زیر پیاده سازی کردم 

              public void SetEnableActiveFilter(bool isActive)
              {
                  this.EnableFilter("IsActive").SetParameter("isActive", isActive);
              }
              public void SetDisableActiveFilter()
              {
                  this.DisableFilter("IsActive");
              }
      و در Context هم برای تعریف فیلتر یه صورت زیر عمل کردم
                  modelBuilder.Conventions.Add(FilterConvention.Create<BaseEntity, bool>("IsActive", (entity, isActive) => entity.IsActive == isActive));

      مشکل اینجاست که فعال سازی به صورت کامل انجام می‌شود اما غیر فعال سازی آن انجام نمی‌گیرد و در تمام فرایندهای بعدی نیز وجود دارد.
      مشکل از نحوه تعریف هست؟
      مطالب
      Functional Programming - قسمت پنجم - وسواس استفاده از نوع های اولیه
      در ادامه سری مقالات مرتبط با برنامه نویسی تابعی ، قصد دارم به استفاده کردن یا نکردن از نوع‌های داده اولیه (Primitive Types) را بررسی کنیم. پیشنهاد میکنم در صورتی که قسمت‌های قبلی را مطالعه نکرده اید ابتدا قسمت‌های قبل را بخوانید.

      در طراحی مدل دامین، بیشتر مواقع از نوع‌های اولیه مانند int , string,… استفاده میکنیم و به عبارتی میتوانیم بگوییم در استفاده از این نوع داده وسواس داریم. قطعه کد زیر را در نظر بگیرید:
      public class UserFactory
      {
          public User CreateUser(string email) {
              return new User(email);
          }
      }
      کلاس UserFactory، یک متد به نام CreateUser دارد که یک رشته را به عنوان ورودی میگیرد و یک شیء از کلاس User را بر می‌گرداند. خوب مشکل این متد کجاست؟
      اگر به خاطر داشته باشید، در قسمت‌های قبلی در مورد مفهومی به نام Honesty صحبت کردیم. به طور ساده باید بتوانیم از روی امضای تابع، کاری را که تابع انجام میدهد و خروجی آن را ببینیم. این تابع Honest نیست؛ شرایطی که string می‌تواند درست نباشد، خالی باشد، طول غیر مجاز داشته باشد و ... را نمیتوانیم از امضای تابع حدس بزنیم.

      برای روشن‌تر شدن بحث، مثال بالا را همیشه در ذهن خود داشته باشید. در این مثال، در تابع Divide که عمل تقسیم را انجام می‌دهد، پارامتر y که یک عدد از نوع int است، میتواند مقدار صفر را داشته باشد و باعث یک exception شود.و از آنجائیکه نوع خروجی این متد هم int است، انتظار دریافت یک exception را نداریم. در مورد exception‌ها به طول مفصل در قسمت قبلی صحبت کردیم. در مثال بالا تصور کنید که بجای یک ایمیل، از چند ایمیل به عنوان ورودی می‌خواهید استفاده کنید. آیا منطق Validation را به ازای هر پارامتر ورودی باید تکرار کنید؟

      به طور کلی استفاده‌ی نابجا و بیش از حد از نوع‌های داده‌ی اولیه، باعث می‌شود تا Honesty متد‌ها را از دست بدهیم و قاعده‌ی DRY را نقض کنیم.

      صحبت در مورد استفاده کردن یا نکردن، جنبه‌های زیادی دارد و یکی از مواردی است که در معماری DDD تحت عنوان Value Object به آن پرداخته شده. هدف ما در این قسمت از مقاله، صرفا پرداختن به گوشه‌ای از این مورد هست. ولی شما میتوانید برای مطالعه بیشتر و اطلاعات تکمیلی کتاب Domain-Driven Design: Tackling Complexity in the Heart of Software نوشته Eric Evans را مطالعه کنید.


      به جای نوع‌های اولیه از چی استفاده کنیم؟

      جواب خیلی ساده‌است؛ شما نیاز دارید تا یک Type اختصاصی را ایجاد کنید. برای مثال بجای استفاده از نوع string برای یک ایمیل، می‌توانید یک کلاس را به عنوان Email ایجاد کنید که مشخصه‌ای به نام Value دارد. این کار به روش‌های مختلفی قابل انجام است؛ اما پیشنهاد من استفاده از این روش هست:

      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Linq.Expressions;
      using System.Reflection;
      
      namespace ValueOf
      {
          public class ValueOf<TValue, TThis> where TThis : ValueOf<TValue, TThis>, new()
          {
              private static readonly Func<TThis> Factory;
      
              /// <summary>
              /// WARNING - THIS FEATURE IS EXPERIMENTAL. I may change it to do
              /// validation in a different way.
              /// Right now, override this method, and throw any exceptions you need to.
              /// Access this.Value to check the value
              /// </summary>
              protected virtual void Validate()
              {
              }
      
              static ValueOf()
              {
                  ConstructorInfo ctor = typeof(TThis)
                      .GetTypeInfo()
                      .DeclaredConstructors
                      .First();
      
                  var argsExp = new Expression[0];
                  NewExpression newExp = Expression.New(ctor, argsExp);
                  LambdaExpression lambda = Expression.Lambda(typeof(Func<TThis>), newExp);
      
                  Factory = (Func<TThis>)lambda.Compile();
              }
      
              public TValue Value { get; protected set; }
      
              public static TThis From(TValue item)
              {
                  TThis x = Factory();
                  x.Value = item;
                  x.Validate();
      
                  return x;
              }
      
              protected virtual bool Equals(ValueOf<TValue, TThis> other)
              {
                  return EqualityComparer<TValue>.Default.Equals(Value, other.Value);
              }
      
              public override bool Equals(object obj)
              {
                  if (obj is null)
                      return false;
      
                  if (ReferenceEquals(this, obj))
                      return true;
      
                  return obj.GetType() == GetType() && Equals((ValueOf<TValue, TThis>)obj);
              }
      
              public override int GetHashCode()
              {
                  return EqualityComparer<TValue>.Default.GetHashCode(Value);
              }
      
              public static bool operator ==(ValueOf<TValue, TThis> a, ValueOf<TValue, TThis> b)
              {
                  if (a is null && b is null)
                      return true;
      
                  if (a is null || b is null)
                      return false;
      
                  return a.Equals(b);
              }
      
              public static bool operator !=(ValueOf<TValue, TThis> a, ValueOf<TValue, TThis> b)
              {
                  return !(a == b);
              }
      
              public override string ToString()
              {
                  return Value.ToString();
              }
          }
      }
      در این روش، یک کلاس را به عنوان Value Object ایجاد کرده‌ایم. این کلاس، نوع اولیه‌ای را که با آن سر و کار داریم، در بر خواهد گرفت و منطق مربوط به مقایسه، همچنین عملگرهای == و != را هم از طریق Equals و GetHashCode، پیاده سازی کرده. برای مثال جهت کلاس ایمیل می‌توانیم به صورت زیر عمل کنیم:
      public class EmailAddress : ValueOf<string, EmailAddress> { }
      همچنین برای مقدار دهی این کلاس میتوانید به صورت زیر عمل کنید:
      EmailAddress emailAddress = EmailAddress.From("foo@bar.com");
      برای مثال‌های پیچیده‌تر مانند آدرس، که شامل آدرس، کد پستی و … می‌باشد، میتوانید با استفاده از امکان Tuple‌ها که از سی شارپ 7 به بعد معرفی شده، مانند مثال زیر عمل کنید:
      public class Address : ValueOf<(string firstLine, string secondLine, Postcode postcode), Address> { }
      و در نهایت برای نوشتن منطق مربوط به validation می‌توانید متد Validate را Override کنید و قاعده‌ی DRY را هم نقض نکنید.

      روش معرفی شده‌ی در این مقاله، صرفا جهت آشنایی بیشتر شما و داشتن کدی تمیز‌تر از طریق مفاهیم برنامه نویسی تابعی خواهد بود. در دنیای واقعی، احتمالا مسائلی را برای ذخیره سازی این آبجکت‌ها و یا کار با کتابخانه‌هایی مانند Entity Framework خواهید داشت که به سادگی قابل حل است.

      در صورتیکه مشکلی در پیاده سازی داشتید، می‌توانید مشکل خود را زیر همین مطلب و یا بر روی gist آن کامنت کنید.