قم بعمل المزيد باستخدام الأنماط في C # 8.0

مرئي Studio 2019 معاينة 2 هو خارج! ومع ذلك ، هناك عدة ميزات أخرى لـ C # 8.0 جاهزة للمحاولة. يتعلق الأمر في الغالب بمطابقة الأنماط ، رغم أنني سأتطرق إلى بعض الأخبار والتغييرات الأخرى في النهاية.


الأصل في المدونة

المزيد من الأنماط في المزيد من الأماكن


عندما قدمت C # 7.0 مطابقة للنمط قلنا أننا توقعنا إضافة المزيد من الأنماط في أماكن أكثر في المستقبل. لقد حان هذا الوقت! نضيف ما نسميه أنماطًا متكررة ، بالإضافة إلى شكل تعبير أكثر إحكاما لبيانات switch تسمى (لقد خمنت!) تبديل التعبيرات .


فيما يلي مثال بسيط للنماذج C # 7.0 لتبدأ بنا:


 class Point { public int X { get; } public int Y { get; } public Point(int x, int y) => (X, Y) = (x, y); public void Deconstruct(out int x, out int y) => (x, y) = (X, Y); } static string Display(object o) { switch (o) { case Point p when pX == 0 && pY == 0: return "origin"; case Point p: return $"({pX}, {pY})"; default: return "unknown"; } } 

تبديل التعبيرات


أولاً ، دعنا نلاحظ أن العديد من عبارات switch لا تؤدي حقًا الكثير من العمل المثير للاهتمام داخل الهيئات. غالبًا ما ينتجون جميعًا قيمة ، إما عن طريق تعيينها لمتغير أو بإعادتها (على النحو الوارد أعلاه). في جميع هذه الحالات ، يكون بيان التبديل صريحًا إلى حد ما. يبدو وكأنه ميزة اللغة منذ خمسة عقود ، مع الكثير من الاحتفالات.


قررنا أن الوقت قد حان لإضافة شكل تعبير من switch . هنا ، يتم تطبيقه على المثال أعلاه:


 static string Display(object o) { return o switch { Point p when pX == 0 && pY == 0 => "origin", Point p => $"({pX}, {pY})", _ => "unknown" }; } 

هناك العديد من الأشياء التي تغيرت هنا من عبارات التبديل. دعنا نذكرهم:


  • الكلمة الأساسية switch هي "infix" بين القيمة المختبرة وقائمة الحالات {...} . هذا يجعله أكثر تعقيدًا مع التعبيرات الأخرى ، كما يسهل التمييز بصريًا من بيان التبديل.
  • الكلمة الأساسية case و : تم استبدالها بسهم lambda => للإيجاز.
  • تم استبدال default بنمط تجاهل _ للإيجاز.
  • الهيئات هي التعبيرات! تصبح نتيجة النص المحدد نتيجة تعبير التبديل.

نظرًا لأن التعبير يحتاج إلى أن يكون له قيمة أو يرمي استثناءً ، فإن تعبير التبديل الذي يصل إلى النهاية دون تطابق يطرح استثناءً. يقوم المحول البرمجي بعمل رائع لتحذيرك عندما يكون هذا هو الحال ، لكنه لن يجبرك على إنهاء جميع تعبيرات المحول بكلمة جذابة: قد تكون على دراية أفضل!


بالطبع ، نظرًا لأن طريقة Display لدينا تتكون الآن من عبارة إرجاع مفردة ، يمكننا تبسيطها لتكون جسدية للتعبير:


  static string Display(object o) => o switch { Point p when pX == 0 && pY == 0 => "origin", Point p => $"({pX}, {pY})", _ => "unknown" }; 

لكي أكون أمينًا ، لست متأكدًا من إرشادات التنسيق التي سنقدمها هنا ، ولكن يجب أن يكون واضحًا أن هذا الأمر أكثر وضوحًا ، خاصة وأن الإيجاز يسمح لك عادة بتنسيق التبديل بطريقة "جدولية" ، كما هو مذكور أعلاه ، مع الأنماط والهيئات على نفس الخط ، و => s تصطف تحت بعضها البعض.


بالمناسبة ، نخطط للسماح بفاصلة زائدة , بعد الحالة الأخيرة تمشيا مع جميع "القوائم مفصولة بفواصل في الأقواس المتعرجة" في C # ، ولكن المعاينة 2 لا تسمح بذلك بعد.


أنماط الملكية


عند الحديث عن الإيجاز ، أصبحت الأنماط فجأة هي أثقل العناصر في تعبير التبديل أعلاه! دعونا نفعل شيئا حيال ذلك.


لاحظ أن تعبير التبديل يستخدم نمط الكتابة Point p (مرتين) ، بالإضافة إلى جملة when لإضافة شروط إضافية للحالة الأولى.


في الإصدار C # 8.0 ، نقوم بإضافة المزيد من العناصر الاختيارية إلى نمط الكتابة ، مما يسمح للنموذج نفسه بالقيام بمزيد من البحث في القيمة المطابقة للنمط. يمكنك جعله نمط خاصية عن طريق إضافة {...} نماذج تحتوي على أنماط متداخلة لتطبيقها على الخصائص أو حقول الوصول للقيمة. هذا دعونا نعيد كتابة تعبير التبديل كما يلي:


 static string Display(object o) => o switch { Point { X: 0, Y: 0 } p => "origin", Point { X: var x, Y: var y } p => $"({x}, {y})", _ => "unknown" }; 

كلتا الحالتين لا تزال تحقق من أن Point . تطبق الحالة الأولى النموذج الثابت 0 بشكل متكرر على خواص X و Y لـ p ، والتحقق مما إذا كانت هذه القيمة لها. وبالتالي يمكننا القضاء على جملة when في هذا والعديد من الحالات الشائعة.


تطبق الحالة الثانية نمط var على كل من X و Y تذكر أن نمط var في C # 7.0 ينجح دائمًا ، ويعلن ببساطة عن متغير جديد للاحتفاظ بالقيمة. وبالتالي ، تحصل x و y على قيم int pX و pY .


نحن لا نستخدم p مطلقًا ، ويمكننا في الواقع حذفها هنا:


  Point { X: 0, Y: 0 } => "origin", Point { X: var x, Y: var y } => $"({x}, {y})", _ => "unknown" 

الشيء الوحيد الذي لا يزال صحيحًا لجميع أنماط الكتابة بما في ذلك أنماط الخصائص ، هو أنها تتطلب أن تكون القيمة خالية. يفتح ذلك إمكانية استخدام نمط الخاصية "الفارغ" {} كنموذج مضغوط "غير فارغ". على سبيل المثال ، يمكننا استبدال الحالة الاحتياطية بالحالتين التاليتين:


  {} => o.ToString(), null => "null" 

يتعامل {} مع الكائنات غير الفارغة المتبقية ، ويحصل null على القيم الخالية ، لذلك يكون المحول شاملًا ولن يشتكي المترجم من القيم المتساقطة.


أنماط الموقف


نمط الملكية لا يجعل حالة Point الثانية أقصر تمامًا ، ولا يبدو أنها تستحق المشكلة هناك ، ولكن هناك الكثير الذي يمكن القيام به.


لاحظ أن فئة Point تحتوي على طريقة Deconstruct ، تسمى deconstructor . في C # 7.0 ، سمح deconstructors لفك قيمة عند التعيين ، بحيث يمكنك كتابة على سبيل المثال:


 (int x, int y) = GetPoint(); // split up the Point according to its deconstructor 

لم C # 7.0 دمج تفكيك مع الأنماط. يتغير ذلك مع أنماط الموضعية التي تعد طريقة إضافية لتمديد أنماط الكتابة في C # 8.0. إذا كان النوع المطابق هو نوع tuple أو كان يحتوي على مُنحل ، فيمكننا استخدام الأنماط الموضعية كوسيلة مدمجة لتطبيق الأنماط العودية دون الحاجة إلى تسمية الخصائص:


 static string Display(object o) => o switch { Point(0, 0) => "origin", Point(var x, var y) => $"({x}, {y})", _ => "unknown" }; 

بمجرد مطابقة الكائن كنقطة ، يتم تطبيق وحدة فك التشفير ، ويتم تطبيق الأنماط المتداخلة على القيم الناتجة.


المفككون ليست دائما مناسبة. يجب إضافتها فقط إلى الأنواع التي يكون من الواضح فيها أي القيم هي. بالنسبة لفئة Point ، على سبيل المثال ، من الآمن والبديهي افتراض أن القيمة الأولى هي X والثانية هي Y ، لذلك فإن تعبير المحول أعلاه سهل وبديهي.


أنماط Tuple


وهناك حالة خاصة مفيدة للغاية من أنماط الموضعية هي عندما يتم تطبيقها على tuples. إذا تم تطبيق عبارة رمز التبديل على تعبير tuple مباشرة ، فنحن نسمح أيضًا بحذف المجموعة الإضافية من الأقواس ، كما في switch (x, y, z) بدلاً من switch ((x, y, z)) .


تعد أنماط Tuple رائعة لاختبار أجزاء متعددة من المدخلات في نفس الوقت. هنا تطبيق بسيط لجهاز الدولة:


 static State ChangeState(State current, Transition transition, bool hasKey) => (current, transition) switch { (Opened, Close) => Closed, (Closed, Open) => Opened, (Closed, Lock) when hasKey => Locked, (Locked, Unlock) when hasKey => Closed, _ => throw new InvalidOperationException($"Invalid transition") }; 

بالطبع يمكننا اختيار تضمين hasKey في المجموعة التي تم تشغيلها بدلاً من استخدامها when استخدام الجمل - إنها بالفعل مسألة ذوق:


 static State ChangeState(State current, Transition transition, bool hasKey) => (current, transition, hasKey) switch { (Opened, Close, _) => Closed, (Closed, Open, _) => Opened, (Closed, Lock, true) => Locked, (Locked, Unlock, true) => Closed, _ => throw new InvalidOperationException($"Invalid transition") }; 

إجمالاً ، آمل أن تتمكن من رؤية أن الأنماط العودية والتعبيرات التبديل يمكن أن تؤدي إلى منطق برنامج أكثر وضوحًا وتوضيحًا.


ميزات C # 8.0 الأخرى في المعاينة 2


على الرغم من أن ميزات الأنماط هي الميزات الرئيسية التي يمكن الوصول إليها عبر الإنترنت في VS 2019 Preview 2 ، إلا أن هناك بعض الميزات الأصغر التي آمل أن تجدها مفيدة وممتعة أيضًا. لن أخوض في التفاصيل هنا ، ولكن فقط أعطيك وصفًا موجزًا ​​لكل منها.


باستخدام التصريحات


في C # ، يؤدي using العبارات دائمًا إلى مستوى من التعشيش ، مما قد يكون مزعجًا للغاية ويؤذي قابلية القراءة. بالنسبة للحالات البسيطة التي تريد فقط تنظيف مورد في نهاية نطاق ما ، فأنت الآن تستخدم التصريحات بدلاً من ذلك. إن استخدام التعريفات هو مجرد إعلانات متغيرة محلية مع using كلمة أساسية في المقدمة ، ويتم التخلص من محتوياتها في نهاية كتلة البيان الحالية. لذلك بدلا من:


 static void Main(string[] args) { using (var options = Parse(args)) { if (options["verbose"]) { WriteLine("Logging..."); } ... } // options disposed here } 

يمكنك ببساطة الكتابة


 static void Main(string[] args) { using var options = Parse(args); if (options["verbose"]) { WriteLine("Logging..."); } } // options disposed here 

هياكل المرجع المتاح


تم تقديم هياكل Ref في C # 7.2 ، وهذا ليس هو المكان المناسب لتكرار فائدتها ، ولكن في المقابل تأتي مع بعض القيود الشديدة ، مثل عدم القدرة على تنفيذ واجهات. يمكن أن تكون هياكل Ref الآن قابلة للتصرف دون تطبيق واجهة IDisposable ، وذلك ببساطة عن طريق وجود طريقة Dispose منها.


وظائف محلية ثابتة


إذا كنت تريد التأكد من أن وظيفتك المحلية لا تتحمل تكاليف وقت التشغيل المرتبطة بـ "التقاط" (الرجوع إلى) المتغيرات من النطاق المغلق ، فيمكنك إعلانها على أنها static . عندئذٍ سيمنع المترجم الإشارة إلى أي شيء يتم الإعلان عنه في الدالات المغلقة - باستثناء الوظائف المحلية الثابتة الأخرى!


التغييرات منذ المعاينة 1


الملامح الرئيسية للمعاينة 1 كانت أنواع مرجعية لاغية وتدفقات غير متزامنة. تطور كلاهما قليلاً في Preview 2 ، لذلك إذا كنت قد بدأت في استخدامهما ، فمن المفيد أن تكون على دراية بما يلي.


أنواع مرجعية لاغية


لقد أضفنا المزيد من الخيارات للتحكم في التحذيرات #nullable في المصدر (من خلال توجيهات #pragma warning #nullable و #pragma warning ) وعلى مستوى المشروع. لقد غيرنا أيضًا تمكين ملف المشروع ليتم <NullableContextOptions>enable</NullableContextOptions> .


تيارات متزامنة


لقد قمنا بتغيير شكل واجهة IAsyncEnumerable<T> يتوقعها المترجم! يؤدي ذلك إلى إخراج المترجم من المزامنة مع الواجهة المتوفرة في .NET Core 3.0 Preview 1 ، والتي يمكن أن تسبب لك بعض المشكلات. ومع ذلك ، فإن .NET Core 3.0 Preview 2 ستنتهي صلاحيتها قريبًا ، وبذلك تعيد واجهات المزامنة.


لديك في ذلك!


كما هو الحال دائما ، ونحن حريصون على ملاحظاتك! يرجى اللعب في جميع أنحاء مع ميزات نمط جديد على وجه الخصوص. هل تصطدم بجدران من الطوب؟ هل هناك شيء مزعج؟ ما هي بعض السيناريوهات الرائعة والمفيدة التي تجدها لهم؟ اضغط على زر التعليقات وعلينا أن نعرف!


القرصنة سعيد


مادس Torgersen ، يؤدي تصميم ل C #

Source: https://habr.com/ru/post/ar438256/


All Articles