‫۹ ماه قبل، شنبه ۱۸ آذر ۱۴۰۲، ساعت ۱۲:۱۲
اصلاحیه: کارآیی spread operator بیشتر نیست!

در متن در مورد spread operator عنوان شده «که ... نگارش C# 12 آن کارآیی بیشتری دارد». این مورد بدون توجه به #Low-Level C تولیدی، نوشته شد و ... متاسفانه نادرست است!
برای مثال فرض کنید، چنین متدی را دارید که با استفاده از spread operator، کار بازکردن یک آرایه را انجام می‌دهد:
public int[] WithSpread()
{
   int[] data = new int[10_000];
   int[] results = [..data];
   return results;
}
معادل #Low-Level C آن (کد نهایی که کامپایلر برای تبدیل آن به IL تولید می‌کند) به صورت زیر است ( #Low-Level C را در Rider، در منوی #Tools -> IL Viewer -> Select Low-Level C می‌توانید تولید کنید):
public int[] WithSpread()
{
  int[] numArray1 = new int[10000];
  int index1 = 0;
  int[] numArray2 = new int[numArray1.Length];
  int[] numArray3 = numArray1;
  for (int index2 = 0; index2 < numArray3.Length; ++index2)
  {
    int num = numArray3[index2];
    numArray2[index1] = num;
    ++index1;
  }
  return numArray2;
}
همانطور که مشاهده می‌کنید، این قطعه کد در C#12 و دات‌نت 8، به شدت ابتدایی تولید شده و به همراه هیچ نوع بهینه سازی نیست. کارآیی این قطعه کد، نسبت به زمانیکه از متد قدیمی CopyTo آرایه‌ها استفاده می‌شود، به مراتب کمتر است (تا 3 برابر!)؛ چون متد CopyTo به همراه بهینه سازی‌های سخت‌افزاری هم هست. به نظر قرار شده بهینه سازی کارآیی spread operator در نگارش بعدی دات‌نت انجام شود.

برای آزمایش شخصی آن، از کلاس زیر استفاده کنید:
using BenchmarkDotNet.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SpreadBenchmark
{
    [MemoryDiagnoser]
    public class Tests
    {
        private readonly int[] _myData = new int[10_000];

        [Benchmark(Baseline = true)]
        public int[] WithToArray()
        {
            int[] results = _myData.ToArray();
            return results;
        }

        [Benchmark]
        public int[] WithCopyTo()
        {
            int[] results = new int[_myData.Length];
            _myData.CopyTo(results, 0);
            return results;
        }

        [Benchmark]
        public int[] WithSpread()
        {
            int[] results = [.._myData];
            return results;
        }
    }
}
که در فایل Program.cs به این صورت فراخوانی می‌شود:
using BenchmarkDotNet.Running;
using SpreadBenchmark;

BenchmarkRunner.Run<Tests>();
با این وابستگی:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="BenchmarkDotNet" Version="0.13.10" />
  </ItemGroup>
</Project>
‫۹ ماه قبل، سه‌شنبه ۱۴ آذر ۱۴۰۲، ساعت ۱۱:۱۶
یک نکته‌ی تکمیلی: روش معرفی مستنداتی به استفاده کننده‌ها در مورد قابلیت آزمایشی در حال استفاده

اگر علاقمند هستید تا در حین نمایش خطاها، استفاده کننده‌ها را به آدرس خاصی نیز راهنمایی کنید، می‌توان از خاصیت UrlFormat به صورت زیر استفاده کرد:
[Experimental("SPC101", UrlFormat = "https://www.example.com/diagnostics/{0}.html")]
public static void TestMethod(string path)
در اینجا {0} به صورت خودکار با diagnostic ID جایگزین می‌شود.
‫۹ ماه قبل، دوشنبه ۱۳ آذر ۱۴۰۲، ساعت ۱۱:۵۳
یک نکته‌ی تکمیلی: روش اعتبارسنجی پارامترهای سازنده‌ی اولیه

اگر برای نمونه پیشتر کار اعتبارسنجی و یا بررسی نال نبودن پارامترهای دریافتی از متد سازنده را در همانجا انجام می‌دادید، روش انجام آن در حالت استفاده‌ی از پارامترهای سازنده‌ی اولیه، به صورت زیر است:
class Employee(string firstName, string lastName)
{
    private readonly string _firstName = firstName.Length >= 5 ? 
                                        firstName : 
                                        throw new ArgumentException("There must be atleast 5 characters");

    private readonly string _lastName = lastName;

    public string FullName()
    {
        return $"Full name  = {_firstName} {_lastName}";
    }
}
‫۹ ماه قبل، جمعه ۱۰ آذر ۱۴۰۲، ساعت ۱۷:۱۵
یک نکته‌ی تکمیلی: اضافه شدن پارامترهای از نوع ref readonly به C# 12

در انتهای نکته‌ی خروجی ref readonly عنوان شد که «در ابتدا قصد داشتند ref readonly را برای تعریف پارامترهای value type نیز بکار برند، اما این تصمیم با معرفی پارامترهای از نوع in جایگزین شد» اما ... مجددا به C# 12 اضافه شده‌است:
مثال زیر را درنظر بگیرید:
namespace CS8Tests;

public class RefReadonlySample
{
   public void Test()
   {
      var number = 5;
      Print(ref number);
      Console.WriteLine($"After Print -> Your number is {number}");
      
      // Output:
      // Print -> Your number is 5
      // After Print -> Your number is 6
   }
   
   private void Print(ref int number)
   {
      Console.WriteLine($"Print -> Your number is {number}");
      number++;
   }
}
در این مثال، ارجاعی از متغیر عددی number (که یک value type است) به کمک واژه‌ی کلیدی ref به متد Print ارسال شده و درون این متد، مقدار این متغیر تغییر کرده‌است که این تغییر به خارج از متد Print نیز منعکس می‌شود.
اگر بخواهیم از تغییرات پارامتر number در متد Print جلوگیری کنیم، می‌توان از واژه‌ی کلیدی in که در C# 7.2 ارائه شد، استفاده کرد:
 private void Print(in int number)
در این حالت در سطر ++number، به خطای زیر می‌رسیم:
error CS8331: Cannot assign to variable 'number' or use it as the right hand side of a ref assignment because it is a readonly variable

اکنون در C# 12 همین عمل را توسط واژه‌های کلیدی ref readonly نیز می‌توان پیاده سازی کرد:
private void Print(ref readonly int number)
خطایی را هم که گزارش می‌دهد، دقیقا همانند خطای ذکر شده‌ی واژه‌ی کلیدی in است.

سؤال: چرا این تغییر در C# 12 رخ داده‌است، زمانیکه واژه‌ی کلیدی in، دقیقا همین کار را انجام می‌داد؟
هدف، وضوح بیشتر API تولیدی و تاکید بر readonly بودن ارجاع دریافتی در این حالت و یکدستی قسمت‌های مختلف زبان است.
همچنین واقعیت این است که یک چنین قابلیت‌هایی، استفاده‌ی روزمره‌ای را در زبان #‍C ندارند و بیشتر هدف از وجود آن‌ها، استفاده از API کتابخانه‌های C++/C در زبان #C است. برای مثال بجای اینکه تمام ارجاعات فقط خواندنی آن‌ها را به پارامترهایی از نوع in تبدیل کنند (در کدهای قدیمی) که سبب بروز مشکلات عدم سازگاری می‌شود، اکنون می‌توانند به سادگی refهای قدیمی تعریف شده را ref readonly کنند؛ بدون اینکه استفاده کنندگان با مشکلی مواجه شوند.
‫۹ ماه قبل، جمعه ۱۰ آذر ۱۴۰۲، ساعت ۱۰:۵۹
یک نکته‌ی تکمیلی: کتابخانه‌ی «PolySharp» امکان استفاده‌ی از یک چنین قابلیت‌هایی را در نگارش‌های پایین‌تر دات‌نت نیز میسر می‌کند. همچنین کتابخانه‌ی مشابه دیگری به نام «Polyfill» هم در این زمینه موجود است.
‫۹ ماه قبل، جمعه ۱۰ آذر ۱۴۰۲، ساعت ۰۲:۰۸
یک نکته‌ی تکمیلی: در C# 12 می‌توان کلاس‌ها، structها و interfaceهای بدون بدنه داشت!

اگر به متن دقت کرده باشید، یک چنین تعریفی هم در آن هست:
public class MyBaseClass(string s); // no body required
این مورد هم جزو تازه‌های C# 12 است. برای مثال بجای {}class Foo می‌توان نوشت ;class Foo. تمام موارد زیر در C# 12 مجاز هستند:
class Foo;

struct Bar;

interface IFoo;
معمولا از اینترفیس‌های بدون بدنه برای علامتگذاری یک‌سری کلاس‌ها و یافتن ساده‌تر آن‌ها از طریق Reflection استفاده می‌شود.
‫۹ ماه قبل، چهارشنبه ۸ آذر ۱۴۰۲، ساعت ۱۱:۱۵
یک نکته‌ی تکمیلی: backing fields تولید شده‌ی در حالت سازنده‌های اولیه، readonly نیستند.

اگر به کدهای #Low-Level C مطلب فوق دقت کنید، به یک چنین تعاریفی می‌رسیم:
private string <FirstName>k__BackingField;
یعنی فیلدهای خصوصی متناظر با پارامترهای سازنده‌ی تعریف شده‌ی توسط کامپایلر در C# 12.0 از نوع readonly نیستند. بنابراین باید دقت داشت که این فیلدها قابل تغییر هستند و برخلاف رویه‌ی متداول تعریف فیلدهای متناظر با پارامترهای تزریق وابستگی‌ها (مانند مثال زیر)، readonly تعریف نشده‌اند:
public class AbcController : Controller
{
   private readonly IMyService _myService;

   public AbcController(IMyService myService) {
     _myService = myService;
   }
}
یعنی در حالت استفاده‌ی از سازنده‌های اولیه‌ی C# 12 می‌توان این فیلدها را تغییر داد که باید به این مورد دقت کافی را داشت. برای نمونه اگر مانند مثال زیر این فیلد را نال کنیم و تغییر دهیم، ممکن است در سایر قسمت‌های استفاده کننده‌ی از آن مشکل درست شود:
public class MyController(IMyService _myService) : ControllerBase
{
   [HttpGet]
   public IActionResult RandomNumber()
   {
     var number = _myService.RandomNumber();

     _myService = null;

     var number2 = MyRandom();
     return Ok(number + number2);            
   }

   public int MyRandom()
   {
     return _myService.RandomNumber();
   }
}
در این مثال ابتدا از myService_ استفاده شده، سپس نال شده (و با خطایی هم مواجه نمی‌شویم چون readonly نیست) و مجددا از آن استفاده شده که به علت نال بودن، دیگر چنین چیزی میسر نیست.
‫۹ ماه قبل، سه‌شنبه ۷ آذر ۱۴۰۲، ساعت ۱۷:۰۶
یک نکته‌ی تکمیلی: تکامل lambda expressions در C# 12 با امکان تعریف مقدار پیش‌فرض پارامترها

در C# 12 می‌توان برای پارامترهای lambda expressions نیز مقدار پیش‌فرض تعریف کرد و از این لحاظ با مابقی قسمت‌ها و ویژگی‌های فعلی زبان، هماهنگی کاملی دارد:
var lambdaWithDefaultParam = (int val = 10) => val + 1;
Console.WriteLine(lambdaWithDefaultParam() == 11);
Console.WriteLine(lambdaWithDefaultParam(4) == 5);
در این مثال در حین فراخوانی lambda، زمانیکه پارامتری مشخص نشده‌است، از همان مقدار پیش‌فرض استفاده می‌کند.

همچنین در اینجا اگر به هر دلیلی نیاز به دسترسی مقدار پیش‌فرض را داشته باشید، روش کار به صورت زیر است:
Console.WriteLine(lambdaWithDefaultParam.Method.GetParameters()[0].DefaultValue);

یک نکته: دلیل اصلی اضافه کردن یک چنین قابلیتی، ساده سازی تعاریف Minimal API's است تا بتوان مقادیر پیش‌فرضی را برای پارامترهای درخواست رسیده، تعریف کرد:
app.MagGet("/items", (int? limit, int offset = 0) =>{
   // paginated query for items
});
‫۹ ماه قبل، سه‌شنبه ۷ آذر ۱۴۰۲، ساعت ۱۵:۰۴
یک نکته‌ی تکمیلی: تکامل اپراتور nameof در C# 12.0

همانطور که در این مطلب مشاهده کردید، اپراتور nameof، روشی بسیار مفید جهت دسترسی به نام متغیرها، نوع‌ها و یا اعضای یک کلاس است. در C# 12، این ویژگی اندکی بهبود یافته‌است و امکان دسترسی به اطلاعات اعضای یک کلاس را هم دارد:
public class NameofClass
{
    public string SomeProperty { get; set; }

    // Now legal with C# 12
    // would show "Length" on the console
    public const string NameOfSomePropertyLength = nameof(SomeProperty.Length); 
    
    public static int StaticField;
    public const string NameOfStaticFieldMinValue  = nameof(StaticField.MinValue);

    [Description($"String {nameof(SomeProperty.Length)}")]
    public int StringLength(string s)
    {
        return s.Length;
    }
}
در این مثال، اگر سعی کنیم مقدار NameOfSomePropertyLength را در کنسول نمایش دهیم، عبارت Length ظاهر خواهد شد. تا پیش از C# 12 برای دسترسی به یک چنین قابلیتی نیاز به نمونه سازی و تولید شیءای از کلاس NameofClass فوق وجود داشت تا بتوان اپراتور nameof را به خواص آن اعمال کرد. این محدودیت در C# 12 برطرف شده‌است.

همچنین همانطور که مشاهده می‌کنید، امکان دسترسی به اطلاعات فیلدهای استاتیک و یا بکارگیری این قابلیت در Attributes هم میسر شده‌است.