10 ویژگی جدید 7.0 #C در Visula Studio 2017
چهارشنبه 8 دی 1395بیشتر برنامه نویسان در حال حاضر از انتشار Visual Studio 2017 RC و اینکه با کلی ویژگی های جدید پا به عرصه گذاشت ، با خبر هستند . در این مقاله هدف ما صحبت در مورد ویژگی های 7.0 #C خواهد بود . در این مقاله مطالبی را در مورد 10 ویژگی جدید برتر و محبوب 7.0 #C در Visual Studio 2017 RC مطالبی را ارائه خواهیم داد .
در 7.0 #C تعداد زیادی ویژگی جدید معرفی شد و همچنین یکسری از ویژگی ها بهبود بخشیده شد . در لیستی از مباحثی را مشاهده میکنید که ما قصد بررسی آنها در این مقاله را داریم .
1. Local functions or nested functions
2. Binary Literal
3. Digit Separators
4. Pattern matching
5. ref returns and ref Locals
6. Tuples
7. Throw Expressions
8. Expression Bodied Members
9. Out variables
10. Deconstruction
در لیست بالا ما فقط به 10 ویژگی جدید 7.0 #C اشاره کردیم در اینجا ویژگی های جدید دیگری همانند
Generalized async (Generalized async return), Replace (Partial Class Enhancements) Records (or Record Types) Non-Nullable Reference Types, Immutable Types و ... نیز وجود دارد .
برخی از این ویژگی ها هنوز در Visual Studio 2017 RC پشتیبانی نمی شوند و هنوز در حال توسعه میباشند . و این بدان دلیل است که توسعه 7 #C به طور کامل به اتمام نرسیده است و هنوز در مرحله پیش نمایش قرار دارد . خب ، ما این ویژگی هایی که توسط VS 2017 پشتیبانی نمی شوند را فعلا توجهی بهشون نخواهیم کرد و تمرکز خود راروی ویژگی هایی که توسط VS 2017 RC پشتیبانی میشوند میگذاریم . در تصویر زیر ویژگی هایی از 7.0 #C را مشاهده میکنید که توسط VS 2017 RC پشتیبانی می شوند .
1. Local functions or nested functions :
به استفاده از nested function ها در داخل یک function به اصطلاح Local Function می گویند . برای درک بهتر از local function ها نگاهی به کد زیر داشته باشید و به چگونگی نوشته شدن و فراخوانی متدهای ()Add و ()Multiply توجه داشته باشید .
مثال 1 :
using static System.Console; namespace UseLocalFunctions { class Program { static void Main(string[] args) { void Add(int x, int y) { WriteLine($ "Sum of {x} and {y} is : {x + y}"); } void Multiply(int x, int y) { WriteLine($ "Multiply of {x} and {y} is : {x * y}"); Add(30, 10); } Add(20, 50); Multiply(20, 50); } } }
در مثال بالا ، ما از دو توابع تو در توی Add و Multiply استفاده کردیم و همانطور که مشاهده میکنید ما میتوانیم آنها را در هر جا از متد والد که دوست داریم فراخوانی کنیم .
مثال 2 :
در مثال اول ، همانطور که مشاهده کردید توابع تودرتو مقدار void را باز میگردانند اما در اینجا محدودیتی وجود ندارد و ما توانایی بازگرداندن هر نوع داده ای را داریم . در این مثال ، ما قصد ایجاد یک تابع محلی با نام CalculateMarks را داریم که تمامی نمرات را محاسبه کرده و آنها را در قالب یک مقدار decimal باز خواهد گرداند :
public static void PrintStudentMarks(int studentId, params Subject[] subjects) { WriteLine($ "Student Id {studentId} Total Marks: {CalculateMarks()}"); WriteLine($ "Subject wise marks"); foreach(var subject in subjects) { WriteLine($ "Subject Name: {subject.SubjectName} \t Marks: {subject.Marks}"); } decimal CalculateMarks() { decimal totalMarks = 0; foreach(var subject in subjects) { totalMarks += subject.Marks; } return totalMarks; } }
کد کامل آن به شرح زیر است :
class Program { static void Main(string[] args) { PrintStudentMarks(101, new Subject { SubjectName = "Math", Marks = 96 }, new Subject { SubjectName = "physics", Marks = 88 }, new Subject { SubjectName = "Chem", Marks = 91 }); } public static void PrintStudentMarks(int studentId, params Subject[] subjects) { WriteLine($ "Student Id {studentId} Total Marks: {CalculateMarks()}"); WriteLine($ "Subject wise marks"); foreach(var subject in subjects) { WriteLine($ "Subject Name: {subject.SubjectName} \t Marks: {subject.Marks}"); } decimal CalculateMarks() { decimal totalMarks = 0; foreach(var subject in subjects) { totalMarks += subject.Marks; } return totalMarks; } } public class Subject { public string SubjectName { get; set; } public decimal Marks { get; set; } } }
خروجی آن را میتوانید در تصویر زیر مشاهده فرمایید :
مثال 3 :
خب ، ما استفاده توابع محلی برای چاپ یکسری مقادیر یا برای بازگرداندن یک مقدار محلی از یک تابع محلی را توضیح دادیم اما جدا از این مباحث ، شما توانایی خیلی بیشتری دارید . در این مثال ، ما به بررسی چگونگی استفاده از local function ها در توابع بازگشتی خواهیم پرداخت .
محاسبه فاکتوریل با استفاده از یک تابع بازگشتی :
private static long GetFactorial(int number) { if (number < 0) throw new ArgumentException("negative number", nameof(number)); if (number == 0) return 1; return number * GetFactorial(number - 1); }
محاسبه فاکتوریل با کمک توابع محلی :
private static long GetFactorialUsingLocal(int number) { if (number < 0) throw new ArgumentException("negative number", nameof(number)); if (number == 0) return 1; long result = number; while (number > 1) { Multiply(number - 1); number--; } void Multiply(int x) => result *= x; return result; }
ممکن است این سوال در ذهن شما شکل گرفته باشد که اگر مقدار فاکتوریل بدون استفاده از توابع بازگشتی قابل محاسبه است ، پس دلیل استفاده ما از آنها چیست . هدف استفاده از توابع بازگشتی مقایسه آن با توابع محلی بود ، و اینکه این پیشنهاد را به شما بدهم که در هر مکانی که کارایی پروژه برای شما ارزشمند است ، توابع محلی را با توابع بازگشتی جایگزین کنید .
توابع محلی نیازی به call stack ندارند در حالی که این نیاز در توابع بازگشتی وجود دارد :
شما در تصویر بالا میتوانید استفاده از call stack را مشاهده کنید .
اگر call stack شما خیلی طولانی میباشد ، برنامه شما fail خواهد شد . اجازه دهید برنامه را برای محاسبه فاکتوریل 9000 دوباره اجرا کنیم .
همانطور که در تصویر بالا مشاهده میکنید تابع بازگشتی با خطای System.StackOverflowException مواجه شد و روند اجرای برنامه متوقف شده است .
حال تابع محلی را برای محاسبه فاکتوریل 9000 فراخوانی میکنیم :
در زیر کد کامل آن را مشاهده میکنید :
class Program { static void Main(string[] args) { //BigInteger factorial = GetFactorial(9000); BigInteger factorial = GetFactorialUsingLocal(9000); } private static BigInteger GetFactorialUsingLocal(int number) { if (number < 0) throw new ArgumentException("negative number", nameof(number)); else if (number == 0) return 1; BigInteger result = number; while (number > 1) { Multiply(number - 1); number--; } void Multiply(int x) => result *= x; return result; } private static BigInteger GetFactorial(int number) { if (number < 0) throw new ArgumentException("negative number", nameof(number)); return number == 0 ? 1 : number * GetFactorial(number - 1); } }
شما تست ها و آزمایش های بیشتری را میتوانید در توابع محلی انجام دهید .
2. Binary Literals :
همانطور که میدانید در ورژن های قبل از 7.0 #C ، دو نوع notation پشتیبانی میشد ، یکی decimal literal و دیگری hexadecimal literal .
decimal literal همانند :
int lenght = 50;
Hexadecimal Literal نیز همانند :
int number = 0X3E8;
یا
int number = 0x3E8;
بنابراین ، همانطور که مشاهده میکنید اگر در استفاده از نوعِ داده ی int از هیچ پیشوندی قبل آن استفاده نکنیم ،بصورت پیش فرض از سیستم Decimal پیروی خواهد کرد ، در سوی دیگر اگر نیاز به استفاده از اعداد hexadecimal داشته باشیم باید از پیشوند "0X" یا "0x" استفاده کنیم .
int Int32MaxLengthInDecimal = 2147483647; int Int32MaxLengthInHexaDecimal = 0x7FFFFFFF; WriteLine($ "Int32MaxLengthInDecimal {Int32MaxLengthInDecimal}"); WriteLine($ "Int32MaxLengthInHexaDecimal {Int32MaxLengthInHexaDecimal}");
خروجی :
در 7.0 #C ، با استفاده از همین روش شما میتوانید binary literal تعریف کنید . شما میتوانید برای استفاده از سیستم Binary از "0B" یا "0b" استفاده کنید . بنابراین ، در 7.0 #C برای ارائه integer سه روش وجود دارد - Decimal , Hexadecimal و binary .
Binary Literal int x = 0B110010; or int x = 0b110010; using static System.Console; namespace BinaryLiteral { class Program { static void Main(string[] args) { //Represent 50 in decimal, hexadecimal & binary int a = 50; // decimal representation of 50 int b = 0X32; // hexadecimal representation of 50 int c = 0B110010; //binary representation of 50 //Represent 100 in decimal, hexadecimal & binary int d = 50 * 2; // decimal represenation of 100 int e = 0x32 * 2; // hexadecimal represenation of 100 int f = 0b110010 * 2; //binary represenation of 100 WriteLine($ "a: {a:0000} b: {b:0000} c: {c:0000}"); WriteLine($ "d: {d:0000} e: {e:0000} f: {f:0000}"); } } }
3. Digits Seprators :
جداکننده ارقام یکی دیگر از ویژگی های جدید 7.0 #C میباشد . ما میتوانیم از یک یا چندین کاراکتر Underline "_" برای جدا کردن ارقام استفاده کنیم . گاهی اوقات ، برای خوانایی بهتر ارقام بزرگ این نیاز حس میشود . به کد زیر توجه کنید :
using static System.Console; namespace DigitSeparators { class Program { static void Main(string[] args) {# region Using Digit Separators int binaryData = 0B0010 _0111_0001_0000; // binary value of 10000 int hexaDecimalData = 0X2B _67; //HexaDecimal Value of 11,111 int decimalData = 104 _567_789; int myCustomData = 1 ___________2__________3___4____5_____6; double realdata = 1 _000 .111 _1e1_00; WriteLine($ " binaryData :{binaryData} \n hexaDecimalData: {hexaDecimalData} \n decimalData: {decimalData} \n myCustomData: {myCustomData} \n realdata: {realdata}");# endregion } } }
4. Pattern Matching :
#C از ویژگی های تطابق الگو پشتیبانی میکند . ما استفاده های خیلی بیشتری میتوانیم از این داشته باشیم ، به کد زیر توجه داشته باشید :
using static System.Console; namespace PatternMatching { class Program { static void Main(string[] args) { //Example 1 var myData = "Custom Data"; var myData2 = myData is string ? "String" : "Not a string"; var myData3 = myData is string a ? a : "Not a String"; WriteLine(myData2); WriteLine(myData3); //Example 2 var x = 10; dynamic y = 0b1001; var sum = y is int ? $ "{y * x}" : "Invalid data"; WriteLine(sum); } } }
این یک مثال بسیار ساده میباشد اما تطابق الگو ها از الگوهای زیادی همانند :
Type Pattern, Constant Pattern, Var Pattern, Recursive Pattern, Property Pattern & Property Sub-pattern, Switch Statement, Match Expression, Case expression, Throw expression, Destructuring assignment, Testing Nullable, Arithmetic simplification, Tuple decomposition, Complex Pattern, Wildcard Pattern و ... پشتیبانی میکنند .
5. Out Variables :
قبل از 7.0 #C ، اگر شما مجبور به ارسال یک پارامتر به عنوان پارامتر out بودید ، پارامتر قبل از ارسال به متد حتما باید declare میشد ، اما در 7.0 #C شما در زمان ارسال پارامتر نیز میتوانید declare کنید .
در تصویر بالا ، همانطور که مشاهده میکنید در 7.0 #C ما در هنگام ارسال پارامتر ، آن را declare کرده ایم .
using static System.Console; class Program { static void Main(string[] args) { string s = "26-Nov-2016"; if (DateTime.TryParse(s, out DateTime date)) { WriteLine(date); } WriteLine(date); }
خب ممکن است که این سوال برای شما پیش بیاید که اگر exeption ای رخ دهد ، چه اتفاقی برای Out parameterما می افتد . متغیر ما با یک مقدار پیش فرض مقدار دهی خواهد شد . همانطور که در تصویر زیر مشاهده میکنید ، ما یک مقدار نادرست به متغیر خود دادیم که قابل تبدیل شدن به Date نیست ، اما همانطور که مشاهده میکنید متغیر ما با یک مقدارپیش فرض مقدار دهی شده است .
6. Tuple :
در 7.0 #C ، ما به راحتی امکان استفاده مستقیم از Tuple ها را داریم . به کد زیر توجه کنید :
//returning price & discount (int, int) GetPrice(int itemId) { var product = (500, 100); return product; }
در کد بالا ، هدف این است که ما ItemId را ارسال کنیم و میخواهیم Price و discount را باز گرداند ، اگر شما کد بالا را بصورت مستقیم در VS 2017 RC اجرا کنید ، این کد کار نخواهد کرد . برای کارکرد صحیح آن ، یه تغییر کوچکی باید اعمال کنید . این تغییرات را بصورت مرحله به مرحله اعمال میکنیم .
در تصویر زیر خطاهایی که در مرحله اول از کد ما گرفته میشود را مشاهده میکنید .
حال به سراغ Solution میرویم .
Solution :
• پروژه خود را انتخاب کنید .
• روی آن راست کلیک کنید .
•Manage NuGet Packages را از منوی باز شده انتخاب کنید .
• "System.ValueTuple" را جستجو کرده و آن را نصب کنید .
حال ، پروژه خود را Rebuild کنید . همانطور که مشاهده میکنید ، خطای شما برطرف شده و برنامه با موفقیت buildشده است .
حال ممکن است این سوال برای شما پیش بیاید که چطور این متد را فراخوانی بکنید . این خیلی سادست . یک نگاه به کد زیر داشته باشید .
using static System.Console; using static System.Text.Encoding; namespace TupleExampleByBNarayan { class Program { static void Main(string[] args) { OutputEncoding = UTF8; Program p = new Program(); var price = p.GetPrice(1); WriteLine($ "Price: ₹{price.Item1}/- \nDiscount: ₹{price.Item2}/-"); } //returning price & discount (int, int) GetPrice(int itemId) { var product = (500, 100); return product; } } }
خروجی :
همانطور مشاهده میکنید در کد بالا ، price را با مشخصه ی Item1 و discount را با مشخصه Item2 پردازش میکنیم . اما بهتر است که ما بصورت مستقیم با نام آنها کار کنیم . به تکه کد زیر توجه فرمایید .
using static System.Console; using static System.Text.Encoding; namespace TupleExampleByBNarayan { class Program { static void Main(string[] args) { OutputEncoding = UTF8; Program p = new Program(); var product = p.GetPrice(1); WriteLine($ "Price: ₹{product.price}/- \nDiscount: ₹{product.discount}/-"); } //returning price & discount (int price, int discount) GetPrice(int itemId) { var product = (500, 100); return product; } } }
توجه داشته باشید که ما Item1 و Item2 را با Price و discount عوض کردیم . ذاتا ، شما میتوانید از نام متغیرها برای Tuple استفاده کنید .
(int price, int discount) GetPrice(int itemId) { var product = (500, 100); return product; } And(int price, int discount) GetPrice(int itemId) { var product = (a: 500, b: 100); return product; }
شما کارهای خیلی بیشتری میتوانید با tuple انجام دهید .
7. (Deconstruction (splitting tuples - نقد عقیده :
تا به حال شما دیدی که راه های پردازش و کار با tuple را مشاهده کردید . اما هنوز ما برای دسترسی به price از product.price استفاده میکنیم . هدف ما استفاده مستقیم از price است . یک سوال ممکن است برای شما پیش بیاید - آیا من میتوانم از price بصورت مستقیم استفاده کنم - جواب "بله" هست .
ما میتوانیم این را بیان کنیم که Deconstruction برای استفاده مستقیم از اعضای tuple با استفاده از نامشان وجود دارد .
8. Expression bodied members :
Expression bodied members یک ظرفیت جدید در 7.0 #C نمیباشد . این در 6.0 #C معرفی شد اما در این نسخه جدید ، بهبود پیدا کرد . اگر شما با Expression bodied members آشنایی ندارید ، ما یک توضیح مختصر و کوتاه در این مورد خواهیم داشت .
در زیر تصویری مربوط به متد های ناشناس و جملات lambda مشاهده میکنید .
Expression Bodied Methods :
در 6.0 #C ویژگی جدیدی با نام Expression Bodied Methods ارائه شد که خیلی شبیه و الهام گرفته از
anonymous & lambda expression بود . اما تفاوت هایی میان این دو وجود داشت .
در مورد Expression Bodied Methods ، این باید یک return type ، name و returned expression داشته باشد .
ما میتوانیم از access modifier را با Expression Bodied Methods استفاده کنیم . ما میتوانیم این را virtual , static تعریف کنیم و یا کلاس parent آن را override کنیم . این اگر مقدار void را باز میگرداند میتواند Async باشد .
مثال 1 :
Expression Bodied Method یک Public Acces modifier دارد ویک مقدار string را به عنوان return type باز میگرداند .
کد :
class User { public int UserId { get; set; } public string UserName { get; set; } public string EmailId { get; set; } public string ContactNumber { get; set; } public string GetUserDetails() => $ "{UserId} + {UserName} + {EmailId} + {ContactNumber}"; }
Expression Bodied Properties :
class User { public int UserId { get; } = 1001; public string UserName { get; } = "BNarayan"; public string EmailId { get; } = "bnarayan....@gmail.com"; public string ContactNumber { get; } = "99xxxxxxxx"; public string UserDetails => $ "{UserId} {UserName} {EmailId} {ContactNumber}"; }
Expression bodied constructor :
در 7.0 #C ما میتوانیم یک Constructor تحت عنوان expression bodied members داشته باشیم . به کد زیر توجه داشته باشید .
class Product { public int ProductId { get; } = 1; public string ProductName { get; } public decimal Price => 3000; Product() => ProductName = "Microsoft HoloLens"; }
Expression bodied destructor :
کد :
class Product { public int ProductId { get; } = 1; public string ProductName { get; } public decimal Price => 3000; Product() => ProductName = "Microsoft HoloLens"; ~Product() => WriteLine("Expression bodied destructor"); }
Expression bodied getters & setters :
کد :
class Product { Dictionary < int, decimal > productPriceList = new Dictionary < int, decimal > (); public int ProductId { get; set; } = 1; public string ProductName => "Microsoft HoloLens"; public decimal Price { get => productPriceList[ProductId]; set => productPriceList[ProductId] = value; } }
استفاده از property ها در داخل جملات getter و setter اجباری نیست . ما همچنین میتوانیم در داخل آن از متغیر ها استفاده کنیم . بنابراین کد بالا میتواند بصورت زیر نوشته شود .
class Product { Dictionary < int, decimal > productPriceList = new Dictionary < int, decimal > (); public int _productId; public string ProductName => "Microsoft HoloLens"; public decimal Price { get => productPriceList[_productId]; set => productPriceList[_productId] = value; } }
9. Ref returns and locals
تمام توسعه دهندگان #C با کلمه کلیدی ref و تمام رفتارهای آن آشنا هستند . همانطور که میدانید این به جای بازگرداندن مقدار متغیر یک آدرس به ما میدهد و همچنین این میتواند با reference type ها مورد استفاده قرار بگیرد که یک حافظه جدید در هنگام فراخوانی متد باید استفاده شود .
اما تا 6.0 #C ما از Ref فقط برای ارسال پارامتر برای متد استفاده میکردیم و اما در نسخه 7.0 ما این امکان را داریم که بوسیله آن مقداری را از یک متد باز گردانیم . همچنین ما میتوانیم یک متغیر محلی را با reference ذخیره کنیم .
Ref Return :
کد :
int[] x = { 2, 4, 62, 54, 33, 55, 66, 71, 92 }; public ref int GetFirstOddNumber(int[] numbers) { for (int i = 0; i < numbers.Length; i++) { if (numbers[i] % 2 == 1) { return ref numbers[i]; } } throw new Exception("odd number not found"); } But there are some restrictions and everything cannot be returned as reference like below code will give error: Dictionary < int, decimal > ProductPriceList = new Dictionary < int, decimal > (); public ref decimal GetProductPriceReference(int productId) { return ref ProductPriceList[productId]; }
خطا :
یک عبارت نمیتواند در این محتوا مورد استفاده قرار بگیرد به این دلیل که آن با ref بازگرداننده نشده است .
Ref locals :
همانطور که در مثال قبل مشاهده کردید ، ما به دنبال یک عدد فرد در داخل آرایه هستیم ، اگر آن را پیدا نکرد یک exeption بدهد با این مضمون که متد مقداری باز نمیگرداند اما reference دارد . بنابراین ، شما عددی را که باید به عنوان reference بازگرداننده شود را ذخیره کنید . برای ذخیره سازی این متغیر محلی ، ما میتوانیم از کلمه کلیدی ref استفاده کنیم ، به کد زیر توجه کنید :
int[] x = { 2, 4, 62, 54, 33, 55, 66, 71, 92 }; ref int oddNum = ref GetFirstOddNumber(x);
کد کامل :
using static System.Console; namespace RefReturnsAndRefLocals { class Program { public ref int GetFirstOddNumber(int[] numbers) { for (int i = 0; i < numbers.Length; i++) { if (numbers[i] % 2 == 1) { return ref numbers[i]; //returning as reference } } throw new Exception("odd number not found"); } static void Main(string[] args) { Program p = new Program(); int[] x = { 2, 4, 62, 54, 33, 55, 66, 71, 92 }; ref int oddNum = ref p.GetFirstOddNumber(x); //storing as reference WriteLine($ "\t\t\t\t{oddNum}"); oddNum = 35; for (int i = 0; i < x.Length; i++) { Write($ "{x[i]}\t"); } ReadKey(); } } }
همانطور که در تصویر فوق مشاهده میکنید ، در دفعه اول مقدار 33 در متغیرOddNum ذخیره میشود . اگر OddNum را در خروجی چاپ کنیم در دفه اول این 33 را چاپ خواهد کرد . اما آن را دوباره مقدار دهی میکنم و آن را مساوی با 35 قرار میدهیم . حال اگر اجرا کنید برنامه را مشاهده میکنید که در خروجی عدد 35 چاپ خواهد شد .
10. Throw Expressions
در 7.0 #C ما قادر هستیم که بصورت مستقیم یک exeption ایجاد کنیم . این exeption میتواند بوسیله ی یک عبارت ایجاد شود .
کد :
class Program { static void Main(string[] args) { var a = Divide(10, 0); } public static double Divide(int x, int y) { return y != 0 ? x % y : throw new DivideByZeroException(); } } // This is just a sample script. Paste your real code (javascript or HTML) here. if ('this_is' == /an_example/) { of_beautifier(); } else { var a = b ? (c % d) : e[f]; }
ما قادر به ایجاد هر exeption که میخواهیم هستیم . همانند “IndexOutOfRangeException”, “NullReferenceException”, “OutOfMemoryException”, “StackOverflowException” و ... .
جدا از ویژگی هایی که در بالا ذکر شد ، ویژگی های بسیار دیگری در 7.0 #C وجود دارد که در مقاله های بعدی آنها را بررسی خواهیم کرد .
- VisualStudio
- 3k بازدید
- 9 تشکر