C# 6 - String Interpolation
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: شش دقیقه

تا پیش از C# 6 یکی از روش‌های توصیه شده‌ی جهت اتصال رشته‌ها به هم، استفاده از متدهایی مانند string.Format و StringBuilder.AppendFormat بود:
using System;
 
namespace CS6NewFeatures
{
    class Person
    {
        public string FirstName { set; get; }
        public string LastName { set; get; }
        public int Age { set; get; }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            var person = new Person { FirstName = "User 1", LastName = "Last Name 1", Age = 50 };
            var message = string.Format("Hello!  My name is {0} {1} and I am {2} years old.",
                                          person.FirstName, person.LastName, person.Age);
            Console.Write(message);
        }
    }
}
مشکل این روش، کاهش خوانایی آن با بالا رفتن تعداد پارامترهای متد Format است و همچنین گاهی از اوقات فراموش کردن مقدار دهی بعضی از آن‌ها و یا حتی ذکر ایندکس‌هایی غیر معتبر که در زمان اجرا، برنامه را با یک خطا متوقف می‌کنند.
در C# 6 جهت رفع این مشکلات، راه حلی به نام String interpolation ارائه شده‌است و اگر افزونه‌ی ReSharper یا یکی از افزونه‌های Roslyn را نصب کرده باشید، به سادگی امکان تبدیل کدهای قدیمی را به فرمت جدید آن خواهید یافت:


در این حالت کد قدیمی فوق، به کد ذیل تبدیل خواهد شد:
static void Main(string[] args)
{
    var person = new Person { FirstName = "User 1", LastName = "Last Name 1", Age = 50 };
    var message = $"Hello!  My name is {person.FirstName} {person.LastName} and I am {person.Age} years old.";
    Console.Write(message);
}
در اینجا ابتدا یک $ در ابتدای رشته قرار گرفته و سپس هر متغیر به داخل {} انتقال یافته‌است. همچنین دیگر نیازی هم به ذکر string.Format نیست.
عملیاتی که در اینجا توسط کامپایلر صورت خواهد گرفت، تبدیل این کدهای جدید مبتنی بر String interpolation به همان string.Format قدیمی در پشت صحنه‌است. بنابراین این قابلیت جدید C# 6 را به کدهای قدیمی خود نیز می‌توانید اعمال کنید. فقط کافی است VS 2015 را نصب کرده باشید و دیگر شماره‌ی دات نت فریم ورک مورد استفاده مهم نیست.


امکان انجام محاسبات با String interpolation

زمانیکه $ در ابتدای رشته قرار گرفت، عبارات داخل {}‌ها توسط کامپایلر محاسبه و جایگزین می‌شوند. بنابراین می‌توان چنین محاسباتی را نیز انجام داد:
 var message2 = $"{Environment.NewLine}Test {DateTime.Now}, {3*2}";
Console.Write(message2);
بدیهی اگر $ ابتدای رشته فراموش شود، اتفاق خاصی رخ نخواهد داد.


تغییر فرمت عبارات نمایش داده شده توسط String interpolation

همانطور که با string.Format می‌توان نمایش سه رقم جدا کننده‌ی هزارها را فعال کرد و یا تاریخی را به نحوی خاص نمایش داد، در اینجا نیز همان قابلیت‌ها برقرار هستند و باید پس از ذکر یک : عنوان شوند:
 var message3 = $"{Environment.NewLine}{1000000:n0} {DateTime.Now:dd-MM-yyyy}";
Console.Write(message3);
حالت کلی و استاندارد آن در متد string.Format به صورت {index[,alignment][:formatString]} است.


سفارشی سازی String interpolation
 
اگر متغیر رشته‌‌ای معرفی شده‌ی توسط $ را با یک var مشخص کنیم، نوع آن به صورت پیش فرض، از نوع string خواهد بود. برای نمونه در مثال‌های فوق، message و message2 از نوع string تعریف می‌شوند. اما این رشته‌های ویژه را می‌توان از نوع IFormattable و یا FormattableString نیز تعریف کرد.
در حقیقت رشته‌های آغاز شده‌ی با $ از نوع IFormattable هستند و اگر نوع متغیر آن‌ها ذکر نشود، به صورت خودکار به نوع FormattableString که اینترفیس IFormattable را پیاده سازی می‌کند، تبدیل می‌شوند. بنابراین پیاده سازی این اینترفیس، امکان سفارشی سازی خروجی string interpolation را میسر می‌کند. برای نمونه می‌خواهیم در مثال message2، نحوه‌ی نمایش تاریخ را سفارشی سازی کنیم.
class MyDateFormatProvider : IFormatProvider
{
    readonly MyDateFormatter _formatter = new MyDateFormatter();
 
    public object GetFormat(Type formatType)
    {
        return formatType == typeof(ICustomFormatter) ? _formatter : null;
    }
 
    class MyDateFormatter : ICustomFormatter
    {
        public string Format(string format, object arg, IFormatProvider formatProvider)
        {
            if (arg is DateTime)
                return ((DateTime)arg).ToString("MM/dd/yyyy");
            return arg.ToString();
        }
    }
}
در اینجا ابتدا کار با پیاده سازی اینترفیس IFormatProvider شروع می‌شود. متد GetFormat آن همیشه به همین شکل خواهد بود و هر زمانیکه نوع ارسالی به آن ICustomFormatter بود، یعنی یکی از اجزای {} دار در حال آنالیز است و خروجی مدنظر آن همیشه از نوع ICustomFormatter است که نمونه‌ای از پیاده سازی آن‌را جهت سفارشی سازی DateTime ملاحظه می‌کنید.
پس از پیاده سازی این سفارشی کننده‌ی تاریخ، نحوه‌ی استفاده‌ی از آن به صورت ذیل است:
static string formatMyDate(FormattableString formattable)
{
      return formattable.ToString(new MyDateFormatProvider());
}
ابتدا یک متد static را تعریف کنید که ورودی آن از نوع FormattableString باشد؛ از این جهت که رشته‌های شروع شده‌ی با $ نیز از همین نوع هستند. سپس سفارشی سازی پردازش {}‌ها در قسمت ToString آن انجام می‌شود و در اینجا می‌توان یک IFormatProvider جدید را معرفی کرد.
در ادامه برای اعمال این سفارشی سازی، فقط کافی است متد formatMyDate را به رشته‌ی مدنظر اعمال کنیم:
 var message2 = formatMyDate($"{Environment.NewLine}Test {DateTime.Now}, {3*2}");
Console.Write(message2);

و اگر تنها می‌خواهید فرهنگ جاری را عوض کنید، از روش ساده‌ی زیر استفاده نمائید:
public static string faIr(IFormattable formattable)
{
    return formattable.ToString(null, new CultureInfo("fa-Ir"));
}
در اینجا با اعمال متد faIr به عبارت شروع شده‌ی با $، فرهنگ ایران به رشته‌ی جاری اعمال خواهد شد.
نمونه‌ی کاربردی‌تر آن اعمال InvariantCulture به String interpolation است:
 static string invariant(FormattableString formattable)
{
    return formattable.ToString(CultureInfo.InvariantCulture);
}


یک نکته: همانطور که عنوان شد این قابلیت جدید با نگارش‌های قبلی دات نت نیز سازگار است؛ اما این کلاس‌های جدید را در این نگارش‌ها نخواهید یافت. برای رفع این مشکل تنها کافی است این کلاس‌های یاد شده را به صورت دستی در فضای نام اصلی آن‌ها تعریف و اضافه کنید. یک مثال


غیرفعال سازی String interpolation

اگر می‌خواهید در رشته‌ای که با $ شروع شده، بجای محاسبه‌ی عبارتی، دقیقا خود آن‌را نمایش دهید (و { را escape کنید)، از {{}} استفاده کنید:
 var message0 = $"Hello! My name is {person.FirstName} {{person.FirstName}}";
در این مثال اولین {} محاسبه خواهد شد و دومی خیر.


پردازش عبارات شرطی توسط String interpolation

همانطور که عنوان شد، امکان ذکر یک عبارت کامل هم در بین {} وجود دارد (محاسبات، ذکر یک عبارت LINQ، ذکر یک متد و امثال آن). اما در این میان اگر یک عبارت شرطی مدنظر بود، باید بین () قرار گیرد:
 Console.Write($"{(person.Age>50 ? "old": "young")}");
علت اینجا است که کامپایلر سی‌شارپ، : بین {} را به format specifier تفسیر می‌کند. نمونه‌ی آن‌را پیشتر با مثال «تغییر فرمت عبارات نمایش داده شده» ملاحظه کردید. ذکر : در اینجا به معنای شروع مشخص سازی فرمتی است که قرار است به این حاصل اعمال شود. برای تغییر این رفتار پیش فرض، کافی است عبارت مدنظر را بین () ذکر کنیم تا تمام آن به صورت یک عبارت سی‌شارپ تفسیر شود.
  • #
    ‫۵ سال و ۴ ماه قبل، شنبه ۱۱ خرداد ۱۳۹۸، ساعت ۱۶:۳۰
    یک نکته‌ی تکمیلی: بهبود روش کار با interpolated verbatim strings در C# 8.0

    تا پیش از C# 8.0 اگر قطعه کد زیر را بنویسیم:
    var x = @$"Time now at UTC {DateTime.UtcNow}";
    سبب بروز این استثناء خواهد شد:
     Error  CS1646  Keyword, identifier, or string expected after verbatim specifier: @
    این مشکل یا به عبارتی اهمیت ذکر $ در ابتدا و سپس @، در C# 8.0 حذف شده و اکنون با هر ترتیبی می‌توان این دو را ذکر کرد.
  • #
    ‫۳ سال و ۸ ماه قبل، شنبه ۱۳ دی ۱۳۹۹، ساعت ۱۴:۰۸
    یک نکته‌ی تکمیلی: روش رفع خطای CA1305

    با «غنی سازی کامپایلر C# 9.0 با افزونه‌ها» نکات جالبی در کدها ظاهر می‌شوند که یکی از آن‌ها CA1305 است و به صورت خلاصه عنوان می‌کند که اگر متد در حال استفاده، دارای overload ای با پارامتر از نوع IFormatProvider هست، حتما از آن استفاده کنید؛ تا شاهد مشکلاتی مانند « تغییرات مهم مقایسه‌ی رشته‌ها در NET 5.0. » و « از متد DateTime.ToString بدون پارامتر استفاده نکنید! » نباشید. در مورد C# 6 - String Interpolation در متن جاری نکاتی در این مورد عنوان شده‌است. می‌توان جهت تکمیل آن، نکته‌ی ریز زیر را هم مدنظر داشت:
    using static System.FormattableString;
    
    
    var number = 11;
    var text = Invariant($"{number}");
    System.FormattableString استاندارد، به همراه متد استاتیک Invariant هم هست که همان کار ToString(CultureInfo.InvariantCulture) را انجام می‌دهد. جهت ساده سازی آن هم از ویژگی using static استفاده شده‌است.
    • #
      ‫۱۶ روز قبل، پنجشنبه ۲۹ شهریور ۱۴۰۳، ساعت ۱۴:۰۳

      یک نکته‌ی تکمیلی: در نگارش‌های جدیدتر دات‌نت، بجای متد Invariant از متد string.Create استفاده کنید

      همانطور که پیشتر نیز عنوان شد، formattable stringها، بر اساس فرهنگ جاری سیستم عامل، خروجی را تغییر می‌دهند. یعنی حاصل نهایی رشته‌ی "{Id} :value"$ بسته به فرهنگ جاری، می‌تواند یکبار با اعداد انگلیسی و بار دیگر با اعداد فارسی جایگزین شود. برای عدم مواجه شدن با یک چنین ناهماهنگی‌هایی، استفاده از متد System.FormattableString.Invariant بر روی یک چنین رشته‌هایی، توصیه می‌شد. اکنون (از زمان NET Core 2.1. به بعد)، استفاده از متد string.Create بجای آن توصیه می‌شود که سرعت بیشتری داشته و همچنین مصرف حافظه‌ی کمتری را نیز به همراه دارد.

      همچنین اگر علاقمند هستید تا این موارد را به صورت یک خطا دریافت کنید و مجبور به تغییر آن‌ها شوید، یک سطر زیر را به فایل editorconfig. خود اضافه کنید؛ که مرتبط است به Meziantou.Analyzer:

      dotnet_diagnostic.MA0111.severity = error
  • #
    ‫۲ سال و ۱۰ ماه قبل، پنجشنبه ۲۹ مهر ۱۴۰۰، ساعت ۱۲:۵۹
    معرفی «Constant interpolated strings» در C# 10.0

    C# 10.0 به همراه تغییر و بهبود جزئی در مورد interpolated strings است. تا پیش از آن، امکان تعریف یک interpolated strings به صورت const وجود نداشت؛ اما اکنون این محدودیت برطرف شده‌است و قطعه کد زیر مجاز است:
    const string constStrFirst = "FirstStr";
    const string summaryConstStr = $"SecondStr {constStrFirst}";

    محدودیت: این نکته تنها در مورد const string‌ها صادق است. برای مثال اگر از const char استفاده شود:
    const char a = 'a';
    const string constStrFirst = "FirstStr";
    const string summaryConstStr = $"SecondStr {constStrFirst} {a}";
    با خطای زیر مواجه می‌شویم:
    // Error CS0133
    // The expression being assigned to
    // 'summaryConstStr' must be constant
    • #
      ‫۲ سال و ۱۰ ماه قبل، جمعه ۳۰ مهر ۱۴۰۰، ساعت ۱۵:۰۱
      آیا این امکان وجود دارد که طبق مثال، summaryConstStr از یک فایل کانفیگ خوانده شود؟! 
      • #
        ‫۲ سال و ۱۰ ماه قبل، جمعه ۳۰ مهر ۱۴۰۰، ساعت ۱۵:۱۹
        خیر. هر دو باید compile time constant باشند.
  • #
    ‫۱ سال و ۹ ماه قبل، سه‌شنبه ۲۴ آبان ۱۴۰۱، ساعت ۱۴:۱۴
    یک نکته‌ی تکمیلی: امکان تعریف عبارات string interpolation چند سطری در C# 11

    در C# 11، متن درون {} یک عبارت string interpolation، می‌تواند در طی چند سطر نیز تعریف شود و متن بین {} به صورت یک عبارت #C تفسیر می‌شود. در اینجا هر عبارت معتبر #C ای را می‌توان بکار برد. در این حالت تعریف عبارات LINQ و pattern matching switch expressions ساده‌تر می‌شوند. برای مثال:
    namespace CS11Tests;
    
    public static class NewLinesStringInterpolations
    {
        public static void Test()
        {
            var month = 8;
            var season = $"The season is {month switch
            {
                >= 1 and <= 3 => "spring",
                >= 4 and <= 6 => "summer",
                >= 7 and <= 9 => "autumn",
                10 or 11 or 12 => "winter",
                _ => "Wrong month number",
            }}.";
            Console.WriteLine(season);
    
            int[] numbers = { 1, 2, 3, 4, 5, 6 };
            var message = $"The reversed even values of {nameof(numbers)} are {string.Join(", ",
                numbers.Where(n => n % 2 == 0).Reverse())}.";
            Console.WriteLine(message);
        }
    }
  • #
    ‫۱۱ روز قبل، دوشنبه ۲ مهر ۱۴۰۳، ساعت ۲۳:۲۵
    یک نکته‌ی تکمیلی: اضافه شدن CompositeFormat به دات‌نت 8 برای کش کردن الگوهای رشته‌ها

    زمانیکه از متد string.Format استفاده می‌کنیم، الگوی معرفی شده‌ی به آن، بارها و بارها در زمان اجرا Parse می‌شود که در برنامه‌های مبتنی بر رشته‌ها، حلقه‌ها و امثال آن، سبب افت کارآیی خواهد شد. برای رفع این مشکل، CompositeFormat به دات‌نت 8 اضافه شده‌است تا بتوان این Parse الگو را یکبار انجام داد و نتیجه را کش کرد.

    یک مثال:
    - عدم کش شدن الگوی تعریف شده، تا پیش از دات‌نت 8:
    var text = string.Format("Format one value: {0}", 42);
    - روش کش کردن الگوی تعریف شده، در دات‌نت 8:
    private static readonly CompositeFormat StaticField = CompositeFormat.Parse("Format one value: {0}");
    
    var text = string.Format(StaticField, 42);

    اگر علاقمند هستید تا این نکته را به صورت یک خطا دریافت کنید و مجبور به تغییر آن‌ها شوید، یک سطر زیر را به فایل editorconfig. خود اضافه کنید:
    dotnet_diagnostic.CA1863.severity = error