ویژگی های جدید 7.0 #C

یکشنبه 14 شهریور 1395

چیزی که در این مقاله دنبال میکنیم ، ویژگی های 7.0 #C است که همراه با Visual Studio 2015 preview 4 منتشر شده است . ویژگی های دیگری نیز منتشر خواهند شد . حال ، زمانِ مناسبی برای معرفی این ویژگی هاست .

ویژگی های جدید 7.0  #C

در 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 کار نمیکند . 

آموزش سی شارپ

برنامه نویسان

نویسنده 3355 مقاله در برنامه نویسان
  • C#.net
  • 3k بازدید
  • 5 تشکر

کاربرانی که از نویسنده این مقاله تشکر کرده اند

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید