ویژگی های جدید 7.0 #C
یکشنبه 14 شهریور 1395چیزی که در این مقاله دنبال میکنیم ، ویژگی های 7.0 #C است که همراه با Visual Studio 2015 preview 4 منتشر شده است . ویژگی های دیگری نیز منتشر خواهند شد . حال ، زمانِ مناسبی برای معرفی این ویژگی هاست .
در 7.0 #C ، تعدادی ویژگی جدید به آن اضافه شده است و تمرکز بیشتری روی میزان مصرف داده ، ساده سازی کد و کارایی آن شده است . شاید بزرگترین ویژگی ها بتوان به Tuples ، که کار را برای داشتن چندین Result آسان میکند ، و pattern matching که کدی را که مشروط به حالت کد است را ، ساده میکند . اما در اینجا کُلی ویژگی بزرگ و کوچک دیگر نیز وجود دارد . امید داریم که با استفاده از این ویژگی ها کدهای ما کارآمدتر و واضح تر باشد .
در ادامه ، در مورد ویژگی هایی بحث خواهیم کرد که زمانی که نسخه نهایی منتشر شود ، قابل استفاده هستند و در حال حاضر فقط در حد تست و نقشه ، قابل استفاده هستند . بسیاری از ویژگی هایی که در اینجا بیان میشود ممکن است که در نسخه نهایی اصلا استفاده نشوند و به کل حذف شوند .
• Out variables :
در حال حاضر در #C ، استفاده از پارامترهای out به آن اندازه که ما توقع داریم انعطاف پذیر نیستند . قبلا شما توانایی فراخوانی متد با پارامترهای out را داشتید ولی حتما باید آن پارامتر ها را قبلا declare میکردید . معمولا آن متغیر ها را مقدار دهی اولیه نمیکردیم ، همچنین شما نمیتوانستید از var برای تعریف آنها استفاده کنید و باید نوع دقیق آنها را مشخص میکردید :
public void PrintCoordinates(Point p) { int x, y; // have to "predeclare" p.GetCoordinates(out x, out y); WriteLine($"({x}, {y})"); }
در 7.0 #C ما متغیرهای out را اضافه میکنیم ; این توانایی را داریم که متغیرها را در زمان ارسال آنها به متد تعریف کنیم :
public void PrintCoordinates(Point p) { p.GetCoordinates(out int x, out int y); WriteLine($"({x}, {y})"); }
توجه داشته باشید که ، متغیرها در محدوده ی block هستند . پس زیر شاخه های آن بلاک میتوانند از آنها استفاده کنند . اکثر جملات اجازه دسترسی به محدوده ی خود را از بیرون نمیدهند ، بنابراین تعریف متغیرهای out معمولا درون blockها صورت میگیرد .
توجه داشته باشید که ، در Preview 4 ، قوانین مربوط به Scope (محدوده) ، بسیار دست و پا گیر هستند :
متغیرهای out فقط محدود به محدوده ای می شوند که در آن تعریف شده اند . پس مثال بالا در نسخه های فعلی بصورت درست کار نخواهد کرد .
زمانی که متغیر out را به عنوان پارامتر در متد تعریف میکنیم ، Compiler تعیین میکند که پارامترهای ارسالی چه چیزی میتواند باشد ، بنابراین بهتر است که به جای استفاده از یک نوع داده مشخص از var استفاده کنید :
p.GetCoordinates(out var x, out var y);
یکی از استفاده های معمول از متغیرهای out استفاده در Try...pattern است ، جایی که یک مقدار boolean که نشان دهنده موفقیت یا عدم موفقیت است بازگردانده میشود ، که پارامتر out مقدار بدست آمده را نگه میدارد :
public void PrintStars(string s) { if (int.TryParse(s, out var i)) { WriteLine(new string('*', i)); } else { WriteLine("Cloudy - no stars tonight!"); } }
توجه داشته باشید که i در اینجا فقط در if ای که در آن تعریف شده است ، مورد استفاده قرار میگیرد . که Preview 4 این امر را به خوبی مدیریت میکند .
از * زمانی که یک پارامتر out برای ما مهم نیست استفاده میکنیم :
p.GetCoordinates(out int x, out *); // I only care about x
• Pattern matching :
7.0 #C به معرفی notion of patterns میپردازد ، که ، اگر بصورت انتزاعی بیان کنیم بدین معناست که ،
مولفه های نحوی هستند که میتوانند داشتن شکل صحیحی از مقادیر را تست کنند ، و همچنین در زمان انجام ، گزیده ای از اطلاعات را به ما میدهد .
مثال هایی که از patternها در 7.0 #C میتوانیم بیان کنیم ، عبارت اند از :
• Constany Patternof the form c : که مساوی بودن ورودی با c را تست میکند .
• Type pattern of the form T x : این را تست می کند که ورودی نوع T را داشته باشد ، و اگر دارد ، مقدار آن را در متغیر جدیدی با نام x و نوع T ذخیره می کند .
• var pattern of the form var x : که همیشه درست است ، مقدار را در متغیر جدیدی با نام x ، با همان نوع داده ورودی قرار میدهد
این شروع کار است - patternها نوع جدید از مولفه ها در زبان #C هستند .
در 7.0 #C ما با استفاده از patternها دوتا از ساختارهای زبان های موجود را افزایش میدهیم :
1. expressionsها هستند که حال میتوانند به جای داشتن فقط یه نوع داده در سمت راست ، یک pattern داشته باشند .
2. Case Clauseها در جمله switch حال میتوانند با pattern ها match باشد نه فقط با یک مقدار ثابت .
Is-expression با استفاده از Patternها :
در اینجا مثالی برای is-expressions با constant patternها و type patternها ، ارائه میدهیم :
public void PrintStars(object o) { if (o is null) return; // constant pattern "null" if (!(o is int i)) return; // type pattern "int i" WriteLine(new string('*', i)); }
همانطور که مشاهده می کنید ، متغیرهای pattern - متغیرها با pattern معرفی شده اند - مشابه پارامترهای out هستند که پیش تر با آنها آشنا شدیم ، در این ، متغیرها میتوانند در وسط یک expression تعریف شوند .
همانند متغیرهای out ، متغیرهای pattern نیز تغییر پذیر هستند .
Patternها و Try-mthodها معمولا با هم مشکلی ندارند :
if (o is int i || (o is string s && int.TryParse(s, out i)) { /* use i */ }
Switch Statementها با استفاده از Patternها :
بصورت کلی Switch statementها را اینگونه تعریف میکنیم :
• شما می توانید بر روی هر نوع تغییر دهید .
• patternها میتوانند درcase clause ها مورد استفاده قرار بگیرند .
• case clauseها می توانند شرایط اضافی بر روی آنها داشته باشند .
در زیر یک مثال ساده را مشاهده میکنید :
switch(shape) { case Circle c: WriteLine($"circle with radius {c.Radius}"); break; case Rectangle s when (s.Length == s.Height): WriteLine($"{s.Length} x {s.Height} square"); break; case Rectangle r: WriteLine($"{r.Length} x {r.Height} rectangle"); break; default: WriteLine("<unknown shape>"); break; case null: throw new ArgumentNullException(nameof(shape)); }
در اینجا ، چندین نکته در مورد این جملات گسترش یافته و جدید switch هست :
• اهمیت ترتیب در case clause :
اولین case clause ای که منطبق باشد برداشته میشود . برای همین این مهم است که درمثال بالا square case قبل از rectangel case آمده است .
• Default clause همیشه در ارزیابی آخر قرار میگیرد .
• null clause در اینجا اهمیتی ندارد .
متغیرهای Pattern با case ... معرفی میشود : labelها در دامنه در بخش سوئیچ های مربوطه می باشد .
Tuples :
این یک امر طبیعی است که گاهی بیش از یک مقدار را بخواهیم از یک متد بازگردانیم T اما راهکار های امروزی برای این کار زیاد مطلوب نیستند :
• out parameters : تقریبا استفاده از اینها دیگر قدیمی شده است ، و اینها با متد های async کار نمیکنند .
• System.Tuple<...>
return types : بیش از نیاز مورد استفاده قرار میگیرد و نیازمند تشخیص یک
tuple object است .
• ساخت نوع داده های سفارشی برای هر متد : مقدار بسیار زیادی کد برای یک type وجود دارد فقط برای این هدف که فقط موقتا گروهی از مقادیر را در خودش ذخیره کند .
• نوع های ناشناخته بازگردانده شده از یک dynamic return type :
کارایی بالایی دارد و هیچ static type checking ای ندارد .
برای انجام هر چه بهتر این کار ها ، 7.0 #C نوع های tuples و tuple literals را اضافه کرد .
(string, string, string) LookupName(long id) // tuple return type { ... // retrieve first, middle and last from data storage return (first, middle, last); // tuple literal }
حال ، متد سه مقدار را بر میگرداند . حال فراخوان کننده متد نیز یک tuple را دریافت میکند ، و میتواند بصورت فردی از مولفه ها استفاده کند .
var names = LookupName(id); WriteLine($"found {names.Item1} {names.Item3}.");
Item1 و ... نام های یش فرض برای tupleها هستند ، و همیشه میتوان از آنها استفاده کرد . اما نام های زیاد مناسبی نمیباشند ، پس ، شما بصورت اختیاری و دلبخواه میتوانید چیز های بهتری اضافه کنید :
(string first, string middle, string last) LookupName(long id) // tuple elements have names
حال دریافت کننده متد نام های بهتری را برای کار کردن دارد :
var names = LookupName(id); WriteLine($"found {names.first} {names.last}.");
شما همچنین میتوانید بصورت مستقیم نام ها را در tuple literals نمایش دهید :
return (first: first, middle: middle, last: last); // named tuple elements in a literal
در کل شما میتوانید بی اعتنا به نام tuple ها آن ها را به یکدیگر منتقل کنید : همانطور که مولفه های فردی قابلیت assign شدن را دارند ، tuple typeها نیز به راحتی به tuple typeهای دیگر تبدیل میشوند . اینجا یکسری محدودیت وجود دارد ، مخصوصا برای tuple literalsها ، در مشکلات معمولی این یک هشدار یا خطا میدهد ، مثلا تصادفا نام مولفه ها را عوض شده باشد .
نکته : این محدودیت ها هنوز در preview 4 پیاده سازی نشده اند .
tupleها value type هستند ، و مولفه های آنها public است . آنها دارای کیفیت مقدار هستند ، بدین معنی که ، دو tuple با هم زمانی که مولفه هاشون با هم برابر باشند ، برابر هستند .
این یکی دیگر از ویژگی های tuple است که بعد از بازگرداندن چند مقدار ، آن را بسیار مفید میکند . برای مثال ، اگر شما به یک دایرکتوری با چند مقدار نیاز دارید ، برای کلید ها از یک tuple استفاده کنید و بقیه چیز ها درست کار خواهد کرد . اگر شما به یک لیست با چند مقدار احتیاج داشتید در هر وضعیت ، از tuple ها استفاده کنید و خواهید دید که لیست درست کار خواهد کرد .
نکته : tupleها به مجموعه ای از uderlying Typeها وابسته هستند که در preview 4 وجود ندارد . برای اینکه این ویژگی کار کند آن را از NuGet به راحتی میتوانید اضافه کنید .
• روی نام پروژه در solution explorer کلیک راست کرده و Manage NuGet Package را انتخاب کنید .
• قسمت Browse را انتخاب کنید ، تیک Include prerelease را بزنید و nuget.org را به عنوان Package source انتخاب کنید .
• System.ValueTuple را جستجو کنید و آن را نصب کنید .
Deconstruction :
راهی دیگر برای از بین بردن tuple ها ، Deconstruction است . deconstructing declaration یک syntax برای تقسیم یک tuple به قسمت های مختلف است و هر قسمت را بصورت فردی به متغیر های تازه assing میکند .
(string first, string middle, string last) = LookupName(id1); // deconstructing declaration WriteLine($"found {first} {last}.");
در deconstructing declaration شما میتوانید از var برای declare کردن متغیر ها استفاده کنید :
(var first, var middle, var last) = LookupName(id1); // var inside
یا اینکه یک var در پشت پرانتز متن بگذارید :
var (first, middle, last) = LookupName(id1); // var outside
شما همچنین میتوانید با deconstructing assignment یک متغیر موجود را deconstruct کنید :
(first, middle, last) = LookupName(id2); // deconstructing assignment
Deconstruction فقط برای tuple ها نیست . هر typeی میتواند deconstruct شود ، که یک
deconstructor method دارد .
public void Deconstruct(out T1 x1, ..., out Tn xn) { ... }
پارامتر out مقداری که از deconstruction حاصل میشود را ، برمی گزیند .
به چه دلیل به جای استفاده از tuple از out استفاده میکند ؟ - به این دلیل که شما میتوانید چندین overload برای مقادیر مختلف داشته باشید .
class Point { public int X { get; } public int Y { get; } public Point(int x, int y) { X = x; Y = y; } public void Deconstruct(out int x, out int y) { x = X; y = Y; } } (var myX, var myY) = GetPoint(); // calls Deconstruct(out myX, out myY);
این یک الگوی معمول و مناسب برای داشتن constructorها و deconstructorهای متناسب در این راه است .
همانند متغیرهای out ، برای چیزهایی که برای ما اهمیت ندارند ، داریم که :
(var myX, *) = GetPoint(); // I only care about myX
نکته : هنوز استفاده از ویژگی بالا در 7.0 #C ، قطعی نیست .
Local functions :
گاهی اوقات یک تابع کمکی در درون یک متد که از آن استفاده کرده فقط حسی ایجاد میکند . حال شما میتوانید تابعی را در درونِ بدنه توابع دیگر تحت عنوان توابع محلی تغریف کنید .
public int Fibonacci(int x) { if (x < 0) throw new ArgumentException("Less negativity please!", nameof(x)); return Fib(x).current; (int current, int previous) Fib(int i) { if (i == 0) return (1, 0); var (p, pp) = Fib(i - 1); return (p + pp, p); } }
پارامترها و متغیر های محلیِ یک محدوده برای توابع محلی در دسترس میباشند ، فقط آنها در lambda expression هستند .
برای مثال ، برای پیاده سازی iterators نیاز به non-iterator wrapper method داریم . استفاده
local fucntion ها در این جا عالی است ، داریم :
public IEnumerable<T> Filter<T>(IEnumerable<T> source, Func<T, bool> filter) { if (source == null) throw new ArgumentNullException(nameof(source)); if (filter == null) throw new ArgumentNullException(nameof(filter)); return Iterator(); IEnumerable<T> Iterator() { foreach (var element in source) { if (filter(element)) { yield return element; } } } }
اگر Iterator
یک متد private بود ، بهتر بود که برای اعضای دیگر برای استفاده تصادفی ، در دسترس باشد . همچنین آن به گرفتن آرگومان های یکسان تحت عنوان فیلتر به جای داشتن آنها در محدوده خود ، نیاز دارد .
نکته : در Preview 4 ، توابع محلی قبل از فراخوانی باید پیاده سازی شوند . این محدودیت رفع خواهد شد ، و همانند متغیر های محلی میتوانند فراخوانی شوند .
Literal improvements :
7.0 #C این اجازه را میدهد که "_" همانند یک جداکننده ارقام رفتار کند .
var d = 123_456; var x = 0xAB_CD_EF;
شما هر جا که نیاز بود میتوانید آن را در بین ارقام استفاده کنید ، برای بالا بردن خوانایی آنها هیچ تاثیری بر روی مقدار اعداد ندارند .
همچنین ، 7.0 #C الفبای binary تولید میکند ، به همین دلیل شما میتوانید bit pattern تعریف کنید به جای اینکه شما hexadecimal را از پایه بلد باشید .
var b = 0b1010_1011_1100_1101_1110_1111;
Ref returns and locals :
همانطور که شما میتوانستید چیز ها را با reference در #C ارسال کنید ، حال میتوانید آنها را با reference بازگردانید ، همچنین آنها را با reference در یک متغیر محلی ذخیره کنید .
public ref int Find(int number, int[] numbers) { for (int i = 0; i < numbers.Length; i++) { if (numbers[i] == number) { return ref numbers[i]; // return the storage location, not the value } } throw new IndexOutOfRangeException($"{nameof(number)} not found"); } int[] array = { 1, 15, -39, 0, 7, 14, -12 }; ref int place = ref Find(7, array); // aliases 7's place in the array place = 9; // replaces 7 with 9 in the array WriteLine(array[4]); // prints 9
این یک روش ارسال بسیار مفید در ساختارهای big data است . برای مثال ، یک بازی باید اطلاعاتش را در یک ارایه بزرگ از ساختارها ذخیره کند . متدها حال میتوانند بصورت مستقیم یک referenceرا تحت عنوان یک ساختار بازگردانند ، بطوری که فراخوان کننده میتواند آنرا بخواند و تغییر دهد .
یکسری محدودیت برای اطمینان از امن بودن آن وجود دارد :
• شما فقط refهایی که safe to return هستند را میتوانید بازگردانید .
• ref location به یک جای مشخص از حافظه اشاره دارد و نمیتواند به مکانی دیگر اشاره کند .
Generalized async return types :
تا الآن ، متد async باید void بازمی گرداند ، Task یا <Task<T . در 7.0 #C این اجازه به نوع های دیگر داده میشود که طوری تعریف شوند که توانایی بازگردانده شدن از یک متد async را داشته باشند .
برای مثال ما تصمیم میگیریم که یک <ValueTask<T داشته باشیم . آن برای جلوگیری از تخصیص یک شی <Task<T درمواردی که نتیجه یک عملیات async در زمان منتظر بودن ، در دسترس است ، ساخته شده است . برای بسیاری از حالات async که بافر پیچیده است برای مثال ، این می تواند به شدت تعداد تخصیص را کاهش دهد و منجر به عملکرد مفید قابل توجهی شود .
نکته : Generalized async return types هنوز در Preview 4 در دسترس نیستند .
More expression bodied members :
متدهای Expression bodied ، ضعف بزرگی در 6.0 #C بود ، اما ما اجازه آن را به همه کاربران نمی دادیم . در 7.0 #C به لیستها accessors, constructors و finalizers اضافه شده است که میتوانند expression bodies داشته باشند :
class Person { private static ConcurrentDictionary<int, string> names = new ConcurrentDictionary<int, string>(); private int id = GetId(); public Person(string name) => names.TryAdd(id, name); // constructors ~Person() => names.TryRemove(id, out *); // destructors public string Name { get => names[id]; // getters set => names[id] = value; // setters } }
این یک نمونه از یک ویژگی است که توسط جامعه کمک شد .
Throw expressions :
این بسیار کار آسانی است که یک exception در وسط یک expression بیندازیم : فقط کافیست که متدی که این کار را برای شما انجام میداد را فراخوانی کنید ، اما در 7.0#C ما مستقیما exception را میندازیم .
class Person { public string Name { get; } public Person(string name) => Name = name ?? throw new ArgumentNullException(name); public string GetFirstName() { var parts = Name.Split(" "); return (parts.Length > 0) ? parts[0] : throw new InvalidOperationException("No name!"); } public string GetLastName() => throw new NotImplementedException(); }
توجه داشته باشید که ، Throw Expression هنوز در Preview 4 کار نمیکند .
آموزش سی شارپ
- C#.net
- 3k بازدید
- 5 تشکر