سؤال

تحرير: تم إصلاح العديد من مشكلات بناء الجملة والاتساق لجعل الكود أكثر وضوحًا وقريبة مما أقوم به بالفعل.

لدي بعض التعليمات البرمجية التي تبدو هكذا:

SomeClass someClass;
var finalResult = 
  DoSomething(() => 
  {
    var result = SomeThingHappensHere();
    someClass = result.Data;
    return result;
  })
  .DoSomething(() => return SomeOtherThingHappensHere(someClass))
  .DoSomething(() => return AndYetAnotherThing())
  .DoSomething(() => return AndOneMoreThing(someClass))
  .Result;

HandleTheFinalResultHere(finalResult);

أين ال DoSomething الطريقة هي طريقة تمديد ، وتتوقع تمريرها. لذلك ، فإن كل طريقة من الطريقة تستدعي في كل من dosomething => يعود Lambda لنوع النتيجة.

هذا مشابه ل ربما موناد. باستثناء بدلاً من التحقق من الفارغ ، أتحقق من حالة فئة النتائج ، وإما استدعاء Func التي تم نقلها إلى dosomething أو إعادة النتيجة السابقة دون الاتصال بـ func

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

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

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

المحلول

كما لوحظ بالفعل ، لقد قمت بتنفيذ موناد هنا تقريبًا.

الكود الخاص بك غير متاح بعض الشيء من حيث أن lambdas لها آثار جانبية. Monads حل هذا أكثر أناقة.

فلماذا لا تحول التعليمات البرمجية إلى موناد مناسب؟

المكافأة: يمكنك استخدام بناء جملة LINQ!


أقدم:

LINQ إلى النتائج

 
مثال:

var result =
    from a in SomeThingHappensHere()
    let someData = a.Data
    from b in SomeOtherThingHappensHere(someData)
    from c in AndYetAnotherThing()
    from d in AndOneMoreThing(someData)
    select d;

HandleTheFinalResultHere(result.Value);

مع LINQ إلى النتائج, ، هذا ينفذ أولاً SomeThingHappensHere. إذا نجح ذلك ، فإنه يحصل على قيمة Data خاصية النتيجة وتنفذ SomeOtherThingHappensHere. إذا نجح ذلك ، فإنه ينفذ AndYetAnotherThing, ، وهلم جرا.

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

ال from x in بت أن كل سطر صاخب بعض الشيء ، لكن IMO لا شيء من التعقيد المماثل لن يصبح قابلاً للقراءة أكثر من هذا!


كيف نجعل هذا العمل؟

يتكون Monads في C# من ثلاثة أجزاء:

  • نوع شيء من T.,

  • Select/SelectMany طرق التمديد لذلك ، و

  • طريقة لتحويل أ ر الى شيء من T..

كل ما عليك فعله هو إنشاء شيء يشبه الموناد ، ويشعر وكأنه موناد ورائحة مثل موناد ، وسيعمل كل شيء بشكل تلقائي.


أنواع وطرق LINQ إلى النتائج هم كالآتي.

نتيجةu003CT> يكتب:

فئة مباشرة تمثل نتيجة. والنتيجة هي إما قيمة النوع ر, أو خطأ. يمكن بناء نتيجة من أ ر أو من استثناء.

class Result<T>
{
    private readonly Exception error;
    private readonly T value;

    public Result(Exception error)
    {
        if (error == null) throw new ArgumentNullException("error");
        this.error = error;
    }

    public Result(T value) { this.value = value; }

    public Exception Error
    {
        get { return this.error; }
    }

    public bool IsError
    {
        get { return this.error != null; }
    }

    public T Value
    {
        get
        {
            if (this.error != null) throw this.error;
            return this.value;
        }
    }
}

طرق التمديد:

تطبيقات ل Select و SelectMany طُرق. وترد توقيعات الطريقة في المواصفات C# ، لذلك كل ما يجب أن تقلق بشأنه هو تطبيقاتها. هذه تأتي بشكل طبيعي تمامًا إذا حاولت الجمع بين جميع وسيطات الطريقة بطريقة هادفة.

static class ResultExtensions
{
    public static Result<TResult> Select<TSource, TResult>(this Result<TSource> source, Func<TSource, TResult> selector)
    {
        if (source.IsError) return new Result<TResult>(source.Error);
        return new Result<TResult>(selector(source.Value));
    }

    public static Result<TResult> SelectMany<TSource, TResult>(this Result<TSource> source, Func<TSource, Result<TResult>> selector)
    {
        if (source.IsError) return new Result<TResult>(source.Error);
        return selector(source.Value);
    }

    public static Result<TResult> SelectMany<TSource, TIntermediate, TResult>(this Result<TSource> source, Func<TSource, Result<TIntermediate>> intermediateSelector, Func<TSource, TIntermediate, TResult> resultSelector)
    {
        if (source.IsError) return new Result<TResult>(source.Error);
        var intermediate = intermediateSelector(source.Value);
        if (intermediate.IsError) return new Result<TResult>(intermediate.Error);
        return new Result<TResult>(resultSelector(source.Value, intermediate.Value));
    }
}

يمكنك تعديل النتيجة بحريةu003CT> فئة وطرق التمديد ، على سبيل المثال ، لتنفيذ قواعد أكثر تعقيدًا. يجب أن تكون توقيعات طرق التمديد فقط كما هو مذكور تمامًا.

نصائح أخرى

يبدو لي أنك قمت ببناء شيء مشابه جدًا لموناد هنا.

يمكنك أن تجعلها موناد مناسبة عن طريق جعل مندوبتك من النوع أ Func<SomeClass, SomeClass>, ، لديك طريقة ما لإعداد الأولي SomeClass قيمة لتمريرها ، ولديها DoSomething تمرير قيمة الإرجاع لأحدها كمعلمة أخرى - وهذا من شأنه أن يجعل التسلسل صريحًا بدلاً من الاعتماد على الحالة المشتركة للمخططة المعجمية.

ضعف هذا الرمز هو الاقتران الضمني بين lambdas الأولى والثانية. لست متأكدًا من أفضل طريقة لإصلاحها.

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