سؤال

لدي فئة "الحالة" في C#، تُستخدم على النحو التالي:

Status MyFunction()
{
   if(...) // something bad
     return new Status(false, "Something went wrong")
   else
     return new Status(true, "OK");
}

انت وجدت الفكرة.جميع المتصلين بـ MyFunction يجب التحقق من الحالة التي تم إرجاعها:

Status myStatus = MyFunction();
if ( ! myStatus.IsOK() )
   // handle it, show a message,...

ومع ذلك، يمكن للمتصلين الكسالى تجاهل الحالة.

MyFunction(); // call function and ignore returned Status

أو

{
  Status myStatus = MyFunction(); 
} // lose all references to myStatus, without calling IsOK() on it

هل من الممكن جعل هذا مستحيلا؟على سبيل المثالاستثناء رمي

على العموم:هل من الممكن أن تكتب فئة C# التي يمكنك يملك لاستدعاء وظيفة معينة؟

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

ما هو الخيار المعادل في C#؟قرأت في مكان ما أن "أنت لا تريد مدمرًا في فئة C# الخاصة بك"

هل تعتبر طريقة التخلص من واجهة IDisposable خيارًا؟

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

هل كانت مفيدة؟

المحلول

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

using System;

public class Example
{
    public class Toy
    {
        private bool inCupboard = false;
        public void Play() { Console.WriteLine("Playing."); }
        public void PutAway() { inCupboard = true; }
        public bool IsInCupboard { get { return inCupboard; } }
    }

    public delegate void ToyUseCallback(Toy toy);

    public class Parent
    {
        public static void RequestToy(ToyUseCallback callback)
        {
            Toy toy = new Toy();
            callback(toy);
            if (!toy.IsInCupboard)
            {
                throw new Exception("You didn't put your toy in the cupboard!");
            }
        }
    }

    public class Child
    {
        public static void Play()
        {
            Parent.RequestToy(delegate(Toy toy)
            {
                toy.Play();
                // Oops! Forgot to put the toy away!
            });
        }
    }

    public static void Main()
    {
        Child.Play();
        Console.ReadLine();
    }
}

في المثال البسيط للغاية، يمكنك الحصول على مثيل لـ Toy عن طريق استدعاء Parent.RequestToy، وتمريره مندوبا.بدلاً من إرجاع اللعبة، تقوم الطريقة على الفور باستدعاء المفوض باللعبة، والذي يجب عليه استدعاء PutAway قبل إرجاعها، وإلا فإن الطريقة RequestToy ستطرح استثناءً.لا أدعي أي حكمة في استخدام هذه التقنية - في الواقع، في جميع أمثلة "حدث خطأ ما"، يكاد يكون من المؤكد أن الاستثناء هو الرهان الأفضل - لكنني أعتقد أنه يقترب قدر الإمكان من طلبك الأصلي.

نصائح أخرى

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

ثم اترك الأمر للمتصل لالتقاط هذا الاستثناء والتعامل معه إذا استطاع، أو السماح له بالانتشار إذا كان المتصل غير قادر على التعامل مع الموقف.

يمكن أن يكون الاستثناء الذي تم طرحه من نوع مخصص إذا كان ذلك مناسبًا.

ل مُتوقع نتائج بديلة، وأنا أتفق مع اقتراح @Jon Limjap.أنا مغرم بنوع الإرجاع المنطقي وبادئة اسم الطريقة بـ "Try"، على النحو التالي:

bool TryMyFunction(out Status status)
{
}

إذا كنت تريد حقًا أن تطلب من المستخدم استرداد نتيجة MyFunction، فقد ترغب في إلغائها بدلاً من ذلك واستخدام متغير out أو ref، على سبيل المثال،

void MyFunction(out Status status)
{
}

قد يبدو الأمر قبيحًا ولكنه على الأقل يضمن تمرير متغير إلى الوظيفة التي ستلتقط النتيجة التي تحتاجها لالتقاطها.

@ إيان،

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

حتى System.Net.WebRequest يطرح استثناءً عندما يكون رمز حالة HTTP الذي تم إرجاعه رمز خطأ.الطريقة النموذجية للتعامل معها هي لف محاولة/التقاط حولها.لا يزال بإمكانك تجاهل رمز الحالة الموجود في كتلة الالتقاط.

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

void MyFunction(Action<Status> callback)
 { bool errorHappened = false;

   if (somethingBadHappend) errorHappened = true;

   Status status = (errorHappend)
                     ? new Status(false, "Something went wrong")
                     : new Status(true, "OK");
   callback(status)

   if (!status.isOkWasCalled) 
     throw new Exception("Please call IsOK() on Status"). 
 }

MyFunction(status => if (!status.IsOK()) onerror());

إذا كنت قلقًا بشأن اتصالهم بـ IsOK() دون القيام بأي شيء، فاستخدم Expression< Func< Status,bool>> بدلاً من ذلك، ثم يمكنك تحليل lambda لمعرفة ما يفعلونه بالحالة:

void MyFunction(Expression<Func<Status,bool>> callback)
 { if (!visitCallbackExpressionTreeAndCheckForIsOKHandlingPattern(callback))
     throw new Exception
                ("Please handle any error statuses in your callback");


   bool errorHappened = false;

   if (somethingBadHappend) errorHappened = true;

   Status status = (errorHappend)
                     ? new Status(false, "Something went wrong")
                     : new Status(true, "OK");

   callback.Compile()(status);
 }

MyFunction(status => status.IsOK() ? true : onerror());

أو تخلى عن فئة الحالة تمامًا واجعلها تمر في مندوب واحد للنجاح وآخر للخطأ:

void MyFunction(Action success, Action error)
 { if (somethingBadHappened) error(); else success();
 }

MyFunction(()=>;,()=>handleError()); 

استخدام الحالة كقيمة إرجاع يذكرني بـ "الأيام الخوالي" لبرمجة لغة C، عندما قمت بإرجاع عدد صحيح أقل من 0 إذا لم ينجح شيء ما.

ألن يكون من الأفضل أن تطرح استثناءً عندما (على حد تعبيرك) هناك خطأ ما؟إذا لم تتمكن بعض "الرموز البطيئة" من اكتشاف الاستثناء الخاص بك، فستعرف ذلك بالتأكيد.

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

سيكون من الجيد بالتأكيد أن يقوم المترجم بالتحقق من ذلك بدلاً من التعبير.:/ لا ترى أي طريقة للقيام بذلك رغم ...

يمكنك طرح استثناء عن طريق:

throw MyException;


[global::System.Serializable]
        public class MyException : Exception
        {
        //
        // For guidelines regarding the creation of new exception types, see
        //    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp
        // and
        //    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp
        //

        public MyException () { }
        public MyException ( string message ) : base( message ) { }
        public MyException ( string message, Exception inner ) : base( message, inner ) { }
        protected MyException (
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context )
            : base( info, context ) { }
    }

الاستثناء أعلاه قابل للتخصيص بالكامل وفقًا لمتطلباتك.

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

@Paul يمكنك القيام بذلك في وقت الترجمة باستخدام لغة C# قابلة للتوسيع.

دول مجلس التعاون الخليجي لديها warn_unused_result السمة المثالية لهذا النوع من الأشياء.ربما يكون لدى مترجمي Microsoft شيء مماثل.

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

(*) إذا كان سيتم الوصول إلى المورد من خلال عدة سلاسل رسائل، فقم بإنشاء كائن مجمّع لكل مؤشر ترابط، واجعل طلبات كل مؤشر ترابط تمر عبر غلافها الخاص.

هذا النمط قابل للاستخدام حتى في السياقات التي لا توجد فيها استثناءات، وقد يكون في بعض الأحيان عمليًا جدًا في مثل هذه الحالات.بشكل عام، ومع ذلك، فإن بعض الاختلافات في نمط المحاولة/التنفيذ عادة ما تكون أفضل.اجعل الأساليب تطرح استثناءً عند الفشل ما لم يشير المتصل صراحةً (باستخدام ملف TryXX الطريقة) التي من المتوقع حدوث فشل فيها.إذا قال المتصلون أن الفشل متوقع ولكنهم لم يتعاملوا معه، فهذه مشكلتهم.يمكن للمرء الجمع بين المحاولة/التنفيذ مع طبقة ثانية من الحماية باستخدام المخطط أعلاه، لكنني لست متأكدًا مما إذا كان الأمر يستحق التكلفة.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top