مطابقة النمط في C # 7

تحتوي C # 7 أخيرًا على ميزة طال انتظارها تسمى مطابقة الأنماط. إذا كنت على دراية باللغات الوظيفية ، مثل F # ، فإن هذه الوظيفة في الشكل الموجود حاليًا قد تخيب ظنك قليلاً. ولكن حتى اليوم ، يمكنه تبسيط الرمز في مجموعة متنوعة من الحالات. مزيد من التفاصيل تحت الخفض!



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

يمكن استخدام عينة في C # في تعبير غير ، وكذلك في كتلة الحالة الخاصة ببيان التبديل.
هناك ثلاثة أنواع من العينات:

  • ثابت العينة
  • نوع العينة
  • متغير العينة.

مطابقة النمط في التعبيرات


public void IsExpressions(object o) { // Alternative way checking for null if (o is null) Console.WriteLine("o is null"); // Const pattern can refer to a constant value const double value = double.NaN; if (o is value) Console.WriteLine("o is value"); // Const pattern can use a string literal if (o is "o") Console.WriteLine("o is \"o\""); // Type pattern if (o is int n) Console.WriteLine(n); // Type pattern and compound expressions if (o is string s && s.Trim() != string.Empty) Console.WriteLine("o is not blank"); } 

باستخدام التعبير is ، يمكنك التحقق مما إذا كانت القيمة ثابتة ، وباستخدام فحص النوع ، يمكنك أيضًا تحديد متغير العينة.

عند استخدام مطابقة النمط في التعبيرات ، يجب الانتباه إلى عدة نقاط مثيرة للاهتمام:

  • يتم إرسال المتغير الذي أدخلته عبارة if إلى النطاق الخارجي.
  • يتم تعيين المتغير الذي تم إدخاله بواسطة عبارة if بشكل صريح فقط عندما يتطابق النمط.
  • التنفيذ الحالي لمطابقة النمط في التعبيرات ليس فعالاً للغاية.

أولاً ، خذ بعين الاعتبار الحالتين الأوليين:

 public void ScopeAndDefiniteAssigning(object o) { if (o is string s && s.Length != 0) { Console.WriteLine("o is not empty string"); } // Can't use 's' any more. 's' is already declared in the current scope. if (o is int n || (o is string s2 && int.TryParse(s2, out n))) { Console.WriteLine(n); } } 

تقدم العبارة if الأولى المتغير s ، المرئي داخل الطريقة بأكملها. هذا أمر معقول ، ولكنه يعقد المنطق إذا كانت العبارات الأخرى في نفس الكتلة تحاول إعادة استخدام نفس الاسم. في هذه الحالة ، تأكد من استخدام اسم مختلف لتجنب التعارضات.

يتم تعيين المتغير الذي تم إدخاله في التعبير بشكل صريح فقط عندما يكون المسند صحيحًا. هذا يعني أن المتغير n في الثانية إذا لم يتم تعيين العبارة في المعامل الصحيح ، ولكن نظرًا لأنه تم التصريح عنها بالفعل ، يمكننا استخدامه كمتغير خارج في طريقة int.TryParse.

النقطة الثالثة المذكورة أعلاه هي الأكثر أهمية. خذ بعين الاعتبار المثال التالي:

 public void BoxTwice(int n) { if (n is 42) Console.WriteLine("n is 42"); } 

في معظم الحالات ، يتم تحويل التعبير إلى كائن. Equals (ثابت ، متغير) [على الرغم من أن الخصائص تقول أنه يجب استخدام العامل == للأنواع البسيطة]:

 public void BoxTwice(int n) { if (object.Equals(42, n)) { Console.WriteLine("n is 42"); } } 

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

إذا كان المتغير n من نوع الكائن ، فإن التعبير o هو 42 سيتسبب في عملية "تحويل - تحويل" واحدة (للحرف 42) ، على الرغم من أن رمزًا مشابهًا يستند إلى بيان التبديل لن يؤدي إلى ذلك.

متغير العينة في التعبير


النمط المتغير هو نوع خاص من نوع النمط مع اختلاف كبير واحد: النمط سيطابق أي قيمة ، حتى فارغة.

 public void IsVar(object o) { if (o is var x) Console.WriteLine($"x: {x}"); } 

سيكون التعبير o هو الكائن صحيحًا إذا لم يكن o فارغًا ، لكن التعبير o is var x سيكون دائمًا صحيحًا. لذلك ، يستبعد المحول البرمجي في وضع الإصدار * تمامًا ما إذا كانت العبارات ويترك ببساطة استدعاء أسلوب وحدة التحكم. لسوء الحظ ، لا يحذر المترجم من عدم توفر الشفرة في الحالة التالية: if (! (O is var x)) Console.WriteLine ("Unreachable"). هناك أمل في أن يتم إصلاح ذلك أيضًا.

* من غير الواضح لماذا يختلف السلوك فقط في وضع التحرير. يبدو أن جذر جميع المشاكل هو نفسه: التنفيذ الأولي للوظيفة ليس الأمثل. ومع ذلك ، إذا حكمنا من خلال هذا التعليق بواسطة نيل جاتفر ، فسيتغير كل شيء قريبًا: "سيتم إعادة كتابة كود المطابقة مع العينة من البداية (أيضًا لدعم العينات العودية). أعتقد أن معظم التحسينات التي تتحدث عنها سيتم تنفيذها في الكود الجديد وستكون متاحة مجانًا. لكن هذا سيستغرق بعض الوقت ".

إن عدم وجود علامة فارغة يجعل هذا الموقف خاصًا ومن المحتمل أن يكون خطيرًا. ومع ذلك ، إذا كنت تعرف بالضبط كيف تعمل هذه العينة ، فقد تكون مفيدة. يمكن استخدامه لإدخال متغير مؤقت في التعبير:

 public void VarPattern(IEnumerable<string> s) { if (s.FirstOrDefault(o => o != null) is var v && int.TryParse(v, out var n)) { Console.WriteLine(n); } } 

هو تعبير وبيان الفيس


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

 public void WithNullPropagation(IEnumerable<string> s) { if (s?.FirstOrDefault(str => str.Length > 10)?.Length is int length) { Console.WriteLine(length); } // Similar to if (s?.FirstOrDefault(str => str.Length > 10)?.Length is var length2 && length2 != null) { Console.WriteLine(length2); } // And similar to var length3 = s?.FirstOrDefault(str => str.Length > 10)?.Length; if (length3 != null) { Console.WriteLine(length3); } } 

لاحظ أنه يمكن استخدام نفس النمط لكل من أنواع القيم وأنواع المراجع.

مطابقة النمط في كتل الحالة


تم توسيع وظيفة بيان التبديل في C # 7 بحيث يمكن الآن استخدام الأنماط في عبارات الحالة:

 public static int Count<T>(this IEnumerable<T> e) { switch (e) { case ICollection<T> c: return c.Count; case IReadOnlyCollection<T> c: return c.Count; // Matches concurrent collections case IProducerConsumerCollection<T> pc: return pc.Count; // Matches if e is not null case IEnumerable<T> _: return e.Count(); // Default case is handled when e is null default: return 0; } } 

يوضح هذا المثال أول مجموعة من التغييرات على بيان التبديل.

  1. يمكن استخدام متغير من أي نوع مع بيان التبديل.
  2. يسمح لك بند الحالة بتحديد نمط.
  3. ترتيب بنود الحالة مهم. سوف يرمي المترجم خطأ إذا كانت الجملة السابقة تتوافق مع نوع الأساس ، والقادم بجانب الجملة المشتقة.
  4. يتم فحص العروض المخصصة ضمنيًا بحثًا عن القيمة الخالية **. في المثال أعلاه ، يعتبر شرط الحالة الأخير صالحًا لأنه يتطابق فقط عندما لا تكون الوسيطة خالية.

** تظهر الجملة الأخيرة من الحالة دالة أخرى مضافة في C # 7 - عينات من متغير فارغ. يخبر الاسم الخاص _ المترجم أن المتغير غير مطلوب. تتطلب عينة النوع في بند الحالة اسمًا مستعارًا. ولكن إذا لم تكن بحاجة إليها ، فيمكنك استخدام _.

يعرض المقتطف التالي ميزة أخرى لمطابقة النمط استنادًا إلى بيان التبديل - القدرة على استخدام المسندات:

 public static void FizzBuzz(object o) { switch (o) { case string s when s.Contains("Fizz") || s.Contains("Buzz"): Console.WriteLine(s); break; case int n when n % 5 == 0 && n % 3 == 0: Console.WriteLine("FizzBuzz"); break; case int n when n % 5 == 0: Console.WriteLine("Fizz"); break; case int n when n % 3 == 0: Console.WriteLine("Buzz"); break; case int n: Console.WriteLine(n); break; } } 

هذه نسخة غريبة من مهمة FizzBuzz التي تعالج كائنًا ، وليس مجرد رقم.

يمكن أن يتضمن بيان التبديل عبارات حالة متعددة من نفس النوع. في هذه الحالة ، يجمع المترجم جميع فحوصات النوع لتجنب الحسابات غير الضرورية:

 public static void FizzBuzz(object o) { // All cases can match only if the value is not null if (o != null) { if (o is string s && (s.Contains("Fizz") || s.Contains("Buzz"))) { Console.WriteLine(s); return; } bool isInt = o is int; int num = isInt ? ((int)o) : 0; if (isInt) { // The type check and unboxing happens only once per group if (num % 5 == 0 && num % 3 == 0) { Console.WriteLine("FizzBuzz"); return; } if (num % 5 == 0) { Console.WriteLine("Fizz"); return; } if (num % 3 == 0) { Console.WriteLine("Buzz"); return; } Console.WriteLine(num); } } } 

ولكن هناك شيئان يجب مراعاتهما:

1. يجمع المترجم فقط فحوصات النوع المتسلسلة ، وإذا قمت بخلط عبارات الحالة مع أنواع مختلفة ، فسيتم إنشاء رمز أقل جودة:

 switch (o) { // The generated code is less optimal: // If o is int, then more than one type check and unboxing operation // may happen. case int n when n == 1: return 1; case string s when s == "": return 2; case int n when n == 2: return 3; default: return -1; } 

سيحولها المترجم كما يلي:

إذا كان (o int n && n == 1) ارجع 1 ؛
 if (o is string s && s == "") return 2; if (o is int n2 && n2 == 2) return 3; return -1; 

2. يقوم المترجم بكل ما هو ممكن لتجنب مشاكل التسلسل النموذجية.

 switch (o) { case int n: return 1; // Error: The switch case has already been handled by a previous case. case int n when n == 1: return 2; } 

ومع ذلك ، لا يستطيع المترجم تحديد أن أحد المسندات أقوى من الآخر ، ويستبدل بشكل فعال شروط الحالة التالية:

 switch (o) { case int n when n > 0: return 1; // Will never match, but the compiler won't warn you about it case int n when n > 1: return 2; } 

موجز مطابقة النمط


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

حدث الوحدة في موسكو - Unity Moscow Meetup 2018.1


يوم الخميس 11 أكتوبر ، سيعقد اجتماع Unity Moscow Meetup 2018.1 في المدرسة العليا للاقتصاد. هذا هو الاجتماع الأول لمطوري الوحدة في موسكو هذا الموسم. سيكون موضوع الصورة الأولى هو AR / VR. ستجد تقارير مثيرة للاهتمام ، والتواصل مع المتخصصين في هذا المجال ، بالإضافة إلى منطقة تجريبية خاصة من MSI.

التفاصيل

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


All Articles