بررسی ويژگی های اضافه شده به نسخه 8 زبان سی شارپ

بررسی ويژگی های اضافه شده به نسخه 8 زبان سی شارپ

از اولین نسخه ی زبان سی شارپ 20 سال می گذرد و در طول این سال ها، این زبان تغییرات زیادی کرده است. طراحان این زبان در هر نسخه سعی کرده اند، نوآوری های جدیدی به زبان اضافه کرده و هم چنین نواقص نسخه های پیشین را برطرف نمایند. نسخه ی 8 این زبان در روز 23 ام ماه سپتامبر سال 2019 با همین رویکرد، ارائه شد. در این مقاله قصد داریم ویژگی های جدیدی که به این نسخه از زبان سی شارپ اضافه شده است را بررسی کرده و با مثال هایی به تشریح آن ها بپردازیم. هم چنین ابهاماتی که ممکن است برای توسعه دهندگان در نحوه ی استفاده از این ویژگی ها پیش بیاید را رفع کنیم. پس بیایید که شروع کنیم.

پیش نیاز

نسخه ی 8 زبان سی شارپ با نسخه ی  ×.Net Core 3. و Net Standard 2.1. در دسترس است. لذا باید ×.Net Core 3. را روی سیستم خود نصب کنید. برای نصب آن می توانید به آدرس زیر رفته و پس از دریافت فایل اجرایی، آن را نصب کنید.

https : //dotnet.microsoft.com/en-us/download/dotnet/3.1

پس از نصب Net Core. ویژوال استدیو 2019 را باز کنید و پروژه ای از نوع Console Application ایجاد کرده و مثال هایی که در ادامه ذکر می کنیم را درون آن بنویسید.

ویژگی Default interface methods

کسانی که تجربه ی برنامه نویسی با زبان های شی گرا مانند سی شارپ و جاوا و . . . را دارند، می دانند که یکی از قوانین مهم در نوشتن واسط ها یا Interface ها در این گونه زبان ها این است که متد ها داخل واسط ها نباید بدنه (body) داشته باشند و فقط باید امضای متد را درون واسط تعریف کرد. در نسخه ی 8 زبان سی شارپ، ویژگی جدیدی به نام Default interface methods اضافه شده است که این امکان را در اختیار توسعه دهنده قرار می دهد تا در واسط ها بتواند، برای متدها بدنه تعریف کرده و پیاده سازی هایی برای متد ها انجام دهد. مزیت اصلی این ویژگی آن است که می توان متد هایی را به واسط های موجود برنامه اضافه کرد، بدون آن که کلاس هایی که آن واسط ها را پیاده سازی کرده اند به مشکل برخورد کنند. به عبارتی دیگر این ویژگی، متد هایی را به واسط ها اضافه می کند که کلاس ها در پیاده سازی آن متد ها آزاد هستند، که آن ها را بازنویسی (override) کنند یا از پیاده سازی پیش فرض آن استفاده کنند. مثال زیر را در نظر بگیرید.

using System;

namespace C_Sharp_8
{
    // - - - - - Default Interface Methods - - - - -
    public interface IDefaultInterfaceMethod
    {
        public void DefaultMethod()
        {
            Console.WriteLine(" I am a default method in the interface ! ");
        }
    }

    public class AnyClass : IDefaultInterfaceMethod
    {
    }

    public class Program
    {
        static void Main()
        {
            IDefaultInterfaceMethod anyClass = new AnyClass();
            anyClass.DefaultMethod();
        }
    }
}
Output > I am a default method in the interface !

در این جا واسطی تعریف شده است که دارای متدی با پیاده سازی پیش فرض هست. کلاسی که این واسط را پیاده سازی کرده است، مجبور نشده که حتما این متد را بازنویسی یا پیاده سازی کند و به طور کلی از وجود آن متد بی خبر است. یعنی اگر نحوه ی استفاده از مثال بالا را به صورت زیر تغییر دهیم، با خطای زمان کامپایل مواجه خواهیم شد.

public class Program
    {
        static void Main()
        {
            AnyClass anyClass = new AnyClass();
            anyClass.DefaultMethod();
        }
    }

' AnyClass ' does not contain a definition for ' DefaultMethod ' and no accessible extension method ' DefaultMethod ' accepting a first argument of type ' AnyClass ' could be found ( are you missing a using directive or an assembly reference ? )

کلاس پیاده سازی کننده ی واسط، خبری از متدی ندارد که در واسط پیاده سازی شده است و متد فقط از طریق خود واسط قابل فراخوانی خواهد بود. یک راه دیگر فراخوانی متد این است که شی را به واسط مورد نظر upcast کنیم یعنی به این صورت

((IDefaultInterfaceMethod)anyClass).DefaultMethod() ;

تا قبل از این ویژگی، در واسط ها نمی توانستیم از access modifier هایی مانند public یا virtual و . . . استفاده کنیم و در صورت استفاده با خطا مواجه می شدیم. اما با اضافه شدن این ویژگی می توان از access modifier هایی مانند protected , internal , public , virtual , abstract در واسط ها استفاده کرد. 

interface IDefaultInterfaceMethod2
    {
        // By default , this method will be virtual , and the virtual keyword can be here used !
        public virtual void DefaultMethod()
        {
            Console.WriteLine(" I am a default method in the interface ! ");
        }

        // By default , this method will be abstract , and the abstract keyword can be here used !
        public abstract void Sum();
    }

همان طور که در کامنت های مثال بالا گفته شده است، چون متد هایی با پیاده سازی اولیه، به صورت پیش فرض virtual هستند نوشتن آن اختیاری است و هم چنین به دلیل این که متد هایی که بدنه ندارند به صورت پیش فرض abstract هستند، نوشتن abstract هم لزومی ندارد.

ویژگی Read Only Members

این ویژگی به نوع struct ها در سی شارپ مربوط می شود. این ویژگی، توانایی تعریف اعضایی را به struct ها می دهد، که به صورت فقط خواندنی یا Read Only باشند. یعنی وضعیت struct را تغییر ندهند. این کار را با اضافه کردن readonly access modifier انجام می دهیم. 

به عنوان مثال نوع struct مستطیل را به صورت زیر تعریف می کنیم که دارای دو خصوصیت طول و عرض بوده (width و height) و متدی برای محاسبه ی مساحت مستطیل دارد (متد Area) و هم چنین متدی جهت نمایش اطلاعات آن (متد ToString).

namespace C_Sharp_8
{
    public struct Rectangle
    {
        public double Height { get ; set ; }
        public double Width { get ; set ; }
        public double Area => (Height * Width);
        public override string ToString() =>
            $" ( Total area for height : { Height } , width : { Width } ) is { Area } ";
    }

    class Program
    {
        static void Main(string[] args)
        {
            var rec = new Rectangle();
            rec.Height = 20.0;
            rec.Width = 25.0;
            Console.WriteLine( rec.ToString() );
            Console.ReadKey();
        }
    }
}

همان طور که می بینید، متد ToString متدی است که وضعیت کلی struct را تغییر نداده و کاری که انجام می دهد، نمایش وضعیت کنونی struct است. پس می توان با قرار دادن کلمه کلیدی readonly قبل از آن، به طور صریح اعلام کرد که این متد قصد تغییر در وضعیت struct را ندارد.

    public struct Rectangle
    {
        public double Height { get ; set ; }
        public double Width { get ; set ; }
        public double Area => (Height * Width);
        public readonly override string ToString() =>
            $" ( Total area for height : { Height } , width : { Width } ) is { Area } ";
    }

با این کار کامپایلر warning زیر را صادر می کند.

.Warning CS8656 Call to non readonly member ' Rectangle.Area.get ' from a ' readonly ' member results in an implicit copy of ' this ' 

همان طور که از متن پیام هشدار کامپایلر پیدا است، کامپایلر سی شارپ به این نکته اشاره می کند که داخل یک عضو فقط خواندنی (متد ToString)، یک عضو دیگری را فراخوانی می کنیم که فقط خواندنی نیست (خصوصیت Area). ممکن است در فراخوانی عضوی که فقط خواندنی نیست، وضعیت struct تغییر کند و در نتیجه با فراخوانی متد ToString وضعیت struct تغییر کند در حالی که با قرار دادن کلمه کلیدی readOnly گفته بودیم که این متد وضعیت struct را تغییر نخواهد داد.

برای حل این مشکل، چون یقین داریم خصوصیت Area، وضعیت struct را تغییر نمی دهد و تنها مساحت مستطیل را محاسبه می کند، پس می توانیم آن را هم به صورت فقط خواندنی در آوریم. پس خواهیم داشت:

    public struct Rectangle
    {
        public double Height { get ; set ; }
        public double Width { get ; set ; }
        public readonly double Area => (Height * Width);
        public readonly override string ToString() =>
            $" ( Total area for height : { Height } , width : { Width } ) is { Area } ";
    }

با این کار هشدار کامپایلر حذف خواهد شد.

نکته ی دیگری که باید به آن توجه داشت این است که اگر کلمه ی کلیدی readonly را قبل از خصوصیتی بیاورید که دارای بخش set است، حتی اگر درون آن چیزی را تغییر نداده باشید، با خطای کامپایل زیر مواجه خواهید شد.

public readonly double Width { get ; set ; }

.Error CS8659 Auto implemented property ' Rectangle.Width ' cannot be marked ' readonly ' because it has a ' set ' accessor 

ویژگی Static local functions

در نسخه ی 7 زبان سی شارپ، ویژگی توابع محلی یا local functions ها معرفی شد. به وسیله ی این ويژگی، درون توابع می توان همانند تعریف متغیر های محلی، توابع محلی تعریف و آن ها فراخوانی کرد. این ویژگی در شرایطی که در یک متد، قطعه کدی باید چند بار فراخوانی شود و آن قطعه کد، مربوط به همین متد است، مفید خواهد بود.

namespace C_Sharp_8
{
    public class Program
    {
        public static void Main(string[] args)
        {
            int Add(int a , int b)
            {
                return a + b;
            }

            Console.WriteLine(Add(3 , 4));
            Console.ReadKey();
        }
    }
}

در مثال بالا متد Add، درون متد Main تعریف شده و همان جا هم فراخوانی شده است. دقت کنید که متد Add، متد محلی بوده و محدوده ی استفاده ی آن در همان متد Main خواهد بود. متد Add به طور پیش فرض به صورت private خواهد بود پس نمی توان از access modifier ها استفاده کرد. در غیر این صورت با خطای زمان کامپایل مواجه خواهیم شد.

 .Error CS0106 The modifier ' private ' is not valid for this item

در نسخه ی 8 زبان سی شارپ این ویژگی بهبود یافته و امکان جدیدی به نام static local functions به آن اضافه شده است. با این ویژگی امکان تعریف متد های محلی static مهیا شده است. محدوده یا scope این نوع متد ها مانند قبل است. یکی از تفاوت های متد های static این است که آن ها به متغیر هایی که بیرون آن تعریف شده اند دسترسی ندارند و تنها به متغیر هایی که درون متد static تعریف شده اند یا به عنوان آرگومان به آن ها پاس داده شده اند، دسترسی دارند.

namespace C_Sharp_8
{
    public class Program
    {
        public static void Main(string[] args)
        {
            string name = " salam . . . ";
            static void PrintName()
            {
                Console.WriteLine(name);
            }

            Console.ReadKey();
        }
    }
}

در مثال بالا، متد PrintName به صورت static تعریف شده است. در نتیجه نمی تواند به متغیر name، که بیرون از متد محلی static تعریف شده است دسترسی پیدا کند پس با خطای کامپایل زیر مواجه می شویم.

 .Error CS8421 A static local function cannot contain a reference to ' name '

ولی کد زیر معتبر است. چون متد Add از آرگومان های a و b که درون خودش تعریف شده اند، استفاده می کند.

namespace C_Sharp_8
{
    public class Program
    {
        public static void Main(string[] args)
        {
            static int Add(int a , int b)
            {
                return a + b ;
            }

            Console.WriteLine(Add(3, 4));
            Console.ReadKey();
        }
    }
}

توجه داشته باشید که درون یک متد می توان چند متد محلی static تعریف کرد. هم چنین در متد های محلی static می توان از modifier های async و unsafe هم استفاده کرد. 

ویژگی Using Declaration

در این نسخه از زبان سی شارپ، می توان اشیایی را ایجاد کرد که قابلیت dispose شدن یا از بین رفتن خودکار را داشته باشند. به عبارتی دیگر در هنگام ایجاد شی، به کامپایلر اعلام می کنیم هر موقع از محدوده یا scope این شی خارج شد، به صورت خودکار متد dispose آن را فراخوانی کند تا آن را از بین ببرد. این کار را به وسیله ی ویژگی جدید Using Declaration انجام می دهیم.

در نسخه های قبل از نسخه ی 8 زبان سی شارپ، هنگامی که با استفاده از دستور using شی ای را تعریف می کنیم، محدوده یا scope ای را برای شی ایجاد می کنیم که در انتهای این محدوده منابعی که در اختیار دارند را آزاد کنند. در مثال زیر فایل Sample.txt را جهت نوشتن باز می کنیم و در انتها می خواهیم برای این که این فایل در اختیار دیگران قرار بگیرد، منابع آن آزاد شود. عبارت using این اطمینان را به ما خواهد داد که حتما این منابع آزاد خواهند شد.

namespace C_Sharp_8
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateTextFileClassic();
            Console.ReadKey();
        }

        static void CreateTextFileClassic()
        {
            using (var file = new System.IO.StreamWriter("Sample.txt"))
            {
                file.WriteLine(" Hello world ");
            } // file object is disposed of here
        }
    }
}

باید به این نکته توجه کنید که نوع های مورد استفاده در عبارت using حتما باید واسط IDisposable را پیاده سازی کرده باشند. در غیر این صورت با خطای کامپایل زیر رو به رو خواهید شد.

 .Error CS1674 ' . . . ' : type used in a using statement must be implicitly convertible to ' System.IDisposable '

با استفاده از ویژگی using declaration می توان این قطعه کد را کوتاه تر و خوانا تر نوشت. قطعه کد بالا را به صورتی که در ادامه می بینید باز نویسی کرده ایم.

namespace C_Sharp_8
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateTextFileModern();
            Console.ReadKey();
        }

        static void CreateTextFileModern()
        {
            using var file = new System.IO.StreamWriter("Sample.txt");
            file.WriteLine(" Hello world ");
            // file object is disposed of here
        }
    }
}

همان طور که می بینید، براکت های باز و بسته ی عبارت using حذف شده و هم چنین تورفتگی اضافه ی کد های داخل آن هم برداشته شده است و جلوی تعریف شی، دستور using به کار برده شده است. با استفاده از این ويژگی، تعریف scope شی را بر عهده ی کامپایلر می گذاریم. کامپایلر scope شی را انتهای متد در نظر می گیرد. پس وقتی برنامه به انتهای متد برسد، متد Dispose فراخوانی شده و منابع آن آزاد خواهد شد.

این ویژگی یکی از قابلیت های کامپایلر است. وقتی قطعه کد بالا را که از ویژگی using declaration استفاده کرده است، کامپایل می کنیم، کامپایلر آن را به صورتی که قبلا نوشته می شد در می آورد. یعنی به این شکل:

private static void CreateTextFileClassic()
        {
            using (StreamWriter file = new StreamWriter("Sample.txt"))
            {
                file.WriteLine("Hello world");
            }
        }

        private static void CreateTextFileModern()
        {
            using (StreamWriter file = new StreamWriter("Sample.txt"))
            {
                file.WriteLine("Hello world");
            }
        }

Nullable reference types

اگر تجربه ی برنامه نویسی با زبان های شی گرا مانند سی شارپ یا جاوا و . . . را داشته باشید که دارای اشیای nullable هستند، حتما با خطای Null Reference Exception رو به رو شده اید. اگر چه راه حل این خطا ساده به نظر می رسد، ولی احتمال این که این خطا در زمان اجرا در جای دیگری رخ بدهد بسیار زیاد است. در نسخه ی 8 زبان سی شارپ، در رابطه با اشیای nullable بهبود هایی انجام شده است که احتمال رخ دادن خطای Null Reference Exception را کمتر می کند. 

فرض کنید کلاس Commit را به صورت زیر تعریف کرده ایم. این کلاس دارای خصوصیت های UserName و Password و CommitMessage است که داده های کلاس را نگه می دارند. در سازنده کلاس تنها خصوصیت های UserName و Password را مقدار می دهیم.

namespace C_Sharp_8
{
    public class Program
    {
        public class Commit
        {
            public string UserName { get ; set ; }
            public string Password { get ; set ; }
            public string CommitMessage { get ; set ; }

            public Commit(string username , string password)
            {
                UserName = userName;
                Password = password;
            }
        }
    }
}

اگر شی ای از کلاس Commit را با استفاده از سازنده ی آن بسازید و سپس بخواهید طول خصوصیت CommitMessage آن را محاسبه کنید، چون مقدار این خصوصیت null است با خطای Null Reference Exception مواجه می شوید.

using System;

namespace C_Sharp_8
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Commit initial = new Commit(" Suresh ", " $trongPassword123 . . . ");
            int messageLength = GetMessageLength(initial);
            Console.WriteLine(messageLength);
        }

        public static int GetMessageLength(Commit person)
        {
            var commitMessage = person.CommitMessage;
            return commitMessage.Length;
        }
    }
}

 .System.NullReferenceException : ' Object reference not set to an instance of an object . ' commitMessage was null

برای رفع خطای Null Reference Exception می توانیم null نبودن خصوصیت CommitMessage را بررسی کنیم. با ویژگی های جدیدی که در رابطه با اشیای nullable در نسخه ی 8 زبان سی شارپ اضافه شده است، می توان رخ دادن این نوع از خطاها را قبل از کامپایل متوجه شد. 

برای استفاده از این ویژگی باید آن را در سطح پروژه فعال کنید. برای این کار فایل پروژه (فایل *.csproj) را ویرایش کنید و تگ Nullable را به آن اضافه کرده و مقدار آن را با enable پر کنید. 

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType> Exe </OutputType>
    <TargetFramework> netcoreapp3.1 </TargetFramework>
    <Nullable> enable </Nullable>
  </PropertyGroup>

</Project>

با ذخیره کردن این تغییرات، کامپایلر هشداری را نمایش خواهد داد. همان طور که از متن هشدار پیدا است، به شما هشدار داده است که در سازنده ی کلاس Commit به خصوصیت CommitMessage مقداری داده نشده است و آن می تواند منشا خطای Null Reference Exception باشد.

 .Warning CS8618   Non - nullable property ' CommitMessage ' must contain a non null value when exiting constructor . Consider declaring the property as nullable 

متاسفانه اکثر توسعه دهنده ها به هشدارهایی که کامپایلر سی شارپ صادر می کند چون باز دارنده نیستند توجهی ندارند و فقط به خطاها توجه می کنند (می توان تغییری در پروژه اعمال کرد که همه ی هشدار ها هم در سطح خطا در نظر گرفته شوند). پس در این جا این هشدار را جدی می گیریم و برای این که مانند کد قبل با خطای زمان کامپایل مواجه نشویم، خصوصیت CommitMessage را با مقدار null مقدار می دهیم.

using System;

namespace C_Sharp_8
{
    public class Program
    {
        public class Commit
        {
            public string UserName { get ; set ; }
            public string Password { get ; set ; }
            public string CommitMessage { get ; set ; }

            public Commit(string username , string password)
            {
                UserName = userName;
                Password = password;
                CommitMessage = null;
            }
        }
    }
}

با اعمال این تغییر، هشدار جدیدی توسط کامپایلر صادر می شود!

.Warning CS8625 Cannot convert null literal to non nullable reference typ

با فعال کردن ویژگی nullable، نوع خصوصیات کلاس به صورت non nullable در نظر گرفته می شوند (نوع string از نوع nullable است) پس وقتی مقدار null را درون آن قرار می دهیم هشدار بالا صادر می شود. پس خصوصیت CommitMessage را به صورت nullable تعریف می کنیم که با این کار هشدار ها از بین می روند.

public string? CommitMessage { get ; set ; }

اکنون در زمان استفاده از خصوصیت CommitMessage با هشدار زیر مواجه می شویم:

.Warning CS8602 Dereference of a possibly null reference

در این جا کامپایلر هشدار می دهد که چون در حال استفاده از یک خصوصیتی هستیم که از نوع nullable است، ممکن است با خطای Null Reference Exception مواجه شویم. برای رفع این هشدار تغییرات زیر را اعمال می کنیم. 

 public static int GetMessageLength(Commit person)
        {
            var commitMessage = person.CommitMessage;
            if (commitMessage is null)
                return 0;
            return commitMessage.Length;
        }

در این قطعه کد بررسی می کنیم که اگر متغیر commitMessage برابر با مقدار null بود مقدار صفر را بر می گردانیم و با این کار از وقوع خطایNull Reference Exception جلوگیری می کنیم. با رفع هشدار هایی که کامپایلر صادر می کند، برنامه در زمان اجرا با خطا مواجه نمی شود.

Switch Expressions And Pattern Matching

عبارت switch از نسخه های اولیه ی زبان سی شارپ وجود داشت و در طول زمان تکامل یافته است. عبارت switch در نسخه ی 8 زبان سی شارپ تغییرات قابل توجهی داشته است. در این نسخه، case های تکراری و کلمه ی کلیدی break حذف شده است. بیایید این تغییرات را با مثال هایی بررسی کنیم. در زیر نمونه ای از عبارت switch سنتی، که از قبل وجود داشت را می بینید. یک enum از خطا ها تعریف می کنیم و در متد GetErrorCode بر اساس نوع خطایی که به آن پاس داده شده است، کد خطای آن را بر می گردانیم.

using System;

namespace C_Sharp_8
{
    public enum ErrorCode
    {
        NotFound ,
        InternalServerError ,
        Unauthorised
    }

    public class ErrorHandler
    {
        public int GetErrorCode(ErrorCode errorCode)
        {
            switch (errorCode)
            {
                case ErrorCode.InternalServerError :
                    return 500;
                case ErrorCode.NotFound :
                    return 404;
                case ErrorCode.Unauthorised :
                    return 401;
                default :
                    return 500;
            }
        }
    }
}

عبارت switch بالا را می توان با استفاده ها از ویژگی های جدید اضافه شده به نسخه ی 8 به صورت زیر، خوانا تر و کوتاه تر باز نویسی کرد.

using System;

namespace C_Sharp_8
{
    public enum ErrorCode
    {
        NotFound ,
        InternalServerError ,
        Unauthorised
    }

    public class ErrorHandler
    {
        public int GetErrorCode(ErrorCode errorCode) =>
            errorCode switch
            {
                ErrorCode.InternalServerError => 500 ,
                ErrorCode.NotFound => 404 ,
                ErrorCode.Unauthorised => 401 ,
                _ => 501
            };
    }
}

همان طور که می بینید، متغیر errorCode  قبل از عبارت switch آمده است. در صورتی که در نسخه ی سنتی ابتدا عبارت switch آورده می شد. تغییر بعدی این است که دیگر خبری از case ها نیست و به جای آن، مقداری خواهد آمد که می خواهیم با متغیر مورد نظر مقایسه شود. هم چنین به جای کلمه ی کلیدی break از علامت underscore (_) استفاده خواهیم کرد پس در این جا اگر خطای پاس داده شده با هیچ کدام برابر نبود، خطای 501 برگشت داده می شود.

ویژگی جدید دیگری که به عبارت switch اضافه شده است، Property pattern match نام دارد. فرض کنید کد زیر را داریم.

using System;

namespace C_Sharp_8
{
    public enum ErrorCode
    {
        NotFound ,
        InternalServerError ,
        Unauthorised
    }

    public class Response
    {
        public ErrorCode ErrorCode { get ; set ; }
    }

    public class ErrorHandler
    {
        public int GetErrorCodeFromResponse(Response response) =>
            response switch
            {
                { ErrorCode : ErrorCode.InternalServerError } => 500 ,
                { ErrorCode : ErrorCode.NotFound } => 404 ,
                { ErrorCode : ErrorCode.Unauthorised } => 401 ,
                _ => 500
            };
    }
}

در این مثال به جای مقایسه ی خطا، می خواهیم براساس خطایی که در پاسخ درخواست (Response) به دست ما رسیده است، تصمیم گیری کنیم که چه کد خطایی را برگشت دهیم. پس نیاز است خصوصیت (خصوصیت ErrorCode) شی مورد نظر (شی response) را بخوانیم و آن را با خطا های مورد نظر تطبیق دهیم. پس این گونه مقایسه می کنیم.

{ ErrorCode : ErrorCode.InternalServerError } => 500

به همین دلیل به آن تطبیق الگوی خصوصیت یا Property pattern match گفته می شود.

یک نوع تطبیق الگوی دیگری که به عبارت switch اضافه شده است Tuple pattern match نام دارد. به وسیله ی این ویژگی می توان چند متغیر را درون یک tuple قرار داد و آن ها از طریق عبارت switch مقایسه کرد. در مثال زیر متدی نوشتیم که سه ورودی می گیرد و براساس مقادیر هر یک از آن ها در مورد احراز هویت آن تصمیم گیری خواهد کرد. 

using System;

namespace C_Sharp_8
{
    class Authorizer
    {
        public bool Authorize(string apiKey , string authToken , bool isValidToken) =>
            (apiKey , authToken , isValidToken) switch
            {
                (" 12345 " , _ , _) => true ,
                (_ , _ , true) => true ,
                (_ , _ , false) => false ,
            };

    }
}

ویژگی دیگری که به عبارت switch اضافه شده است Positional pattern match نام دارد. این ویژگی بر اساس قابلیت deconstruct عمل می کند که در نسخه ی 7 زبان سی شارپ اضافه شده بود. اگر شی ای دارای متد deconstruct باشد، می توان از آن در Positional pattern match استفاده کرد و در غیر این صورت با خطا مواجه خواهیم شد.

در مثال زیر کلاس City را تعریف کرده ایم و متد deconstruct آن را هم پیاده سازی کرده ایم (این متد برعکس متد سازنده عمل می کند و مقدار داده های کلاس را به متغیرهای بیرون کلاس پاس خواهد داد و شی را تجزیه می کند). در عبارت switch می خواهیم براساس جمعیت شهر مورد نظر، تراکم آن را تشخص دهیم. ابتدا با استفاده از متد deconstruct داده های کلاس را تجزیه کرده و آن ها را در یک tuple قرار می دهیم. سپس با استفاده از کلمه ی کلیدی when مقایسه مورد نظر را انجام می دهیم.

using System;

namespace C_Sharp_8
{
    class City
    {
        public string Name { get ; }
        public int Area { get ; }
        public double Population { get ; }

        public City(string name , int area , double population) =>
            (Name , Area , Population) = (name , area , population);

        public void Deconstruct(out string name , out int area , out double population) =>
            (name , area , population) = (Name , Area , Population);
    }

    class CityRecomendation
    {
        string FindDensity(City city) =>
            city switch
            {
                var (name , area , population) when population > 100000 => " High " ,
                var (name , area , population) when population < 50000 => " Medium " ,
                var (name , area , population) when population < 25000 => " Low " ,
                _ => " Unknown "
            };
    }
}

به این نکته توجه کنید که اگر شی مورد نظر دارای متد deconstruct نباشد با خطای زیر مواجه می شویم.

Error CS1061 ' City ' does not contain a definition for ' Deconstruct ' and no accessible extension method ' Deconstruct ' accepting a first argument of type ' City ' could be found (are you missing a using directive or an assembly reference ?)

در این مقاله نگاهی انداختیم به ویژگی هایی که در نسخه‌ی 8 زبان سی شارپ اضافه شده است. نکاتی که در حین استفاده از این ویژگی ها باید رعایت کنید را بررسی و نحوه ی استفاده ی درست از آن ها را بیان کردیم. امیدوارم در نحوه ی صحیح استفاده کردن از این ویژگی ها به شما کمک کرده باشد. 

شما می توانید برای آشنایی با ویژگی های نسخه 9 زبان سی شارپ به مقاله بررسی ويژگی های اضافه شده به نسخه 9 زبان سی شارپ و برای آشنایی با نسخه 10 زبان سی شارپ به مقاله مروری بر ویژگی های اضافه شده به نسخه 10 زبان #C در وبلاگ سکان آکادمی مراجعه کنید.

از بهترین نوشته‌های کاربران سکان آکادمی در سکان پلاس


online-support-icon