ویژگی Primary Constructors در سی شارپ 12


در سی شارپ 12 ، ویژگی های مختلفی معرفی شده‌اند که یکی از آن ها Primary Constructors نام دارد.

مزایای استفاده از Primary Constructors در سی شارپ عبارتند از :

بهبود خوانایی کدها و درک سریع‌تر آن‌ ها : استفاده از Primary Constructor باعث کاهش تکرار کدها و افزایش خوانایی آن‌ها می‌شود.

پذیرش مستقیم Dependency ها توسط پارامتر ها.

عدم نیاز به استفاده از کلمه کلیدی this برای مقدار دهی به Property‌ های سازنده.

تولید کدهای بهینه‌شده و بهبوداتی در کارایی : استفاده از Primary Constructor باعث بهبود کارایی کدها می‌شود.

می خواییم ببینیم Primary Constructor علاوه بر کدنویسی تمیزتر و کوتاه‌تر چه محدودیت هایی هم داره؟ با چن تا مثال به‌ همراه کدنویسی ویژگی ها و محدودیت های Primary Constructor و همچنین به تفاوت primary constructor class و primary constructor record می پردازیم :

خب اگه بخواییم یه فیلدی رو اساین کنیم توی یه کلاس و در زمان ساخت این مقدار و بگیریم و فورس کنیم که حتما بهمون مقدار بده میاییم Constructor می سازیم یعنی :

  public class Student
{
       private readonly int _id;
       public Student(int id)
 {
         _id = id;
}
}

اینجا id رو میگیریم و اساین می کنیم. مثل تمام سرویس هایی که inject می کنیم. مثلا :

   public class StudentService
   {
       private readonly IStudentProvider _studentProvider;
       public StudentService(IStudentProvider studentProvider)
       {
           _studentProvider = studentProvider;
       }
}

خب IStudentProvider رو inject می کنیم. Primary Constructor میگه اگه (int id) رو بیاری جلو اسم کلاس
یعنی : { } public class Student (int id) ، دیگه نیازی نیست اینارو بنویسی :

  private readonly int _id;
   public Student (int id) 
   {
       _id = id;
  }

من برات همه رو می نویسم و هر جایی که داری از این فیلد یعنی id_ استفاده میکنی من برات هندلش می کنم پس نیازی به تعریف ;private readonly int _id و تعریف Constructor نیست. تمام این ها به عنوانPrimary Constructor میاد این بالا :

 public class Student (int id)
 {
  }

ولی این یه محدودیتی داره ، محدودیتش این که این مقدار :

  private readonly int _id;
  public Student (int id) 
  {
        _id = id;
  }

هنگام کامپایل به یه همچین کدی تبدیل میشه :

  public class Student_Compiled
   {
       private int _srutokjdmvnghtyoeks456lsk_id;
       public Student_Compiled (int id)
       {
           _srutokjdmvnghtyoeks456lsk_id = id;
       }
 }

اولین تفاوتی که میبینیم readonly نداره ، وقتی از Primary Constructor استفاده می کنیم فیلدی که برای ما میسازه ، readonly نیستش. پس یکی از محدودیت های Primary Constructor این که readonly رو متوجه نمیشه. اگه بخواییم readonly رو داشته باشیم ، بیاییم یک فیلد readonly تعریف کنیم. دیگه مستقیما با خود این id کار نکنیم. یعنی : ;private readonly int _id = id ، که id رو اساین کنیم بهش و با id_ کار کنیم. فقط برای اینکه اون constructor رو ننویسیم. پس اولین محدودیت Primary Constructor این که readonly تبدیل نمیشه. دومین محدودیتش این که id رو داریم :

   public class Student(int id)
   {
       public string ToString()
       {
           id = 123;
           return id.ToString();
       }
  }

زمان کامپایل این id میاد داخل constructor :

 public Student_Compiled(int id)
{
}

یه فیلد درست میکنه که اسمش رو نمیدونیم چیه : ;srutokjdmvnghtyoeks456lsk_id_ ؟؟؟ فقط خود کامپایلر می فهمتش و جاگزاری میکنه ، جاهایی که نیاز داریم یعنی این id دقیقا تبدیل شده به این : ;srutokjdmvnghtyoeks456lsk_id_ ، محدودیتش این که id رو داریم ولی نمیتونیم بگیم this.id ، بخاطر این که کامپایلر نمیدونه اینو که داریم می نویسیم this.id ، تبدیل به چی کنه؟ پس چون نمیدونیم و هنگام کامپایل این اتفاق میفته پس نمیتونیم از کلمه this استفاده کنیم، فقط مستقیما میتونیم از id استفاده کنیم.

مثلا : ;id = 123 ، چون وقتی کامپایل میشه این id میاد داخل constructor و تبدیل به فیلد میشه و چون نمیدونیم کامپایلر قراره چه عددی اینجا ;srutokjdmvnghtyoeks456lsk_id_ بزاره پس به واسطه همین از کلمه کلیدی this.id نمیتونیم استفاده کنیم.

خب بریم مثال بعدی از Primary Constructor :

تو کلاس Student میتونیم چن تا Constructor داشته باشیم : یعنی یه Constructor Default ،

 public class Student
 {
     private readonly int _id;
     public Student()
     {
     }
      public Student(int id)
     {
         _id = id;
     }
}

و یه Constructor که اینطوری id رو بگیریم. اما اگه (int id) رو بزاریم این جا :

 public class Student(int id)
{
}

دیگه نیازی به تعریف فیلد نیست ، ولی هنگام استفاده از Primary Constructor اگه دیفالت Constructor می خواییم ، باید this رو بنویسیم یه default value هم بهش بدیم :

  public Student() : this (15)
  {
  }

حتما باید این کارو انجام بدیم تا Default Constructor برامون ساخته بشه. اینم یکی از امکاناتی که اگه از Primary Constructor و Default Constructor بخواییم هم زمان استفاده کنیم باید از this(15) استفاده کنیم تا بتونیم کامپایل کنیم.

خب یه مثال دیگه از Primary Constructor :

یه کلاس Entity داریم با یه Constructor :

   public class Entity
    {
        public Entity(int id)
        {
        }
  }

و یه کلاس Student :

   public class Student : Entity
   {
       public Student(int id) : base(id)
       {
       }
  } 

با base اشاره داریم به کلاس Entity ، خب اگه این کلاس Entity مون Default Constructor بگیره :

 public class Entity(int id)
 {
 } 

پس :

 public Entity(int id)
    {
    } 

حذف میشه ، این جا از کلاس Student و base(id) داریم استفاده می کنیم ، اگه کلاس Student رو Default Constructor کنیم :

  public class Student(int id) : Entity
  {      
   }

دیگه base , constructor نداریم ، میاییم مستقیما id رو بهش میدیم :

  public class Student(int id) : Entity(id)
  {     
  }

کلمه کلیدی base دیگه نیازی نیست.

تفاوت Primary Constructor Class با Primary Constructor Record :
  public record Student_Record(int Id);

خب این Id مون ، Pascal ، یعنی تبدیل میشه به یه پراپرتی.

     public class Student(int id)
{
    public string ToString()
    {
        return id.ToString();
    }
} 

و این id مون ، Camel Case ، یعنی تبدیل میشه به یه فیلد.

اگه از Student_Record یه instance بگیریم :

    public class Program
    {
        public static void Main(string[] args) 
        {
            Student_Record record = new(14);
           // record.Id 
        }
   }

میتونیم Id رو ببینیم ، تبدیل میشه به یه پراپرتی. اما وقتی از Student یه instance بگیریم :

    public class Program
    {
        public static void Main(string[] args) 
        {
            Student student = new(14);
           // student.id
        }
   }

نمیتونیم id رو ببینیم ، تبدیل میشه به یه فیلد.

پس فرق record با class متفاوت بودنشون از این جهت که :

public record Student_Record(int Id);

این Id تبدیل به یه پراپرتی میشه. ولی :

public class Student(int id) 
{
 }

این id تبدیل به یه فیلد میشه و همچنین در کلاس کلمه کلیدی this رو نداریم. یعنی this.id نمی بینیم. چون نمیدونیم قراره کامپایلر تبدیل به چه عددی بکنه ، مثل این :

private int _srutokjdmvnghtyoeks456lsk_id;

بخاطر همین از this.id نمیتونیم استفاده کنیم ، فقط مستقیما از id استفاده می کنیم.

خب این از قابلیت Primary Constructor ، محدودیت ها و بهینه تر بودنش.