8

Chỉnh sửa: cố định một số cú pháp và các vấn đề nhất quán để làm cho mã rõ ràng hơn một chút và gần với những gì tôi thực sự đang làm.Lạm dụng đóng cửa? Vi phạm các nguyên tắc khác nhau? Hay ok?

Tôi đã có một số mã trông như thế này:

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); 

nơi phương pháp DoSomething là một phương pháp khuyến nông, và họ hy vọng một Func thông qua vào nó. Vì vậy, mỗi phương thức gọi trong mỗi DoSomething => lambda trả về một kiểu kết quả.

điều này tương tự như Maybe monad. Ngoại trừ thay vì kiểm tra giá trị rỗng, tôi kiểm tra trạng thái của lớp Kết quả và gọi hàm Func đã được chuyển vào DoSomething hoặc trả về Kết quả trước đó mà không cần gọi đến Func

. loại thành phần trong mã của tôi, nhưng tôi cũng cần phải có khả năng truyền dữ liệu từ một trong các kết quả cuộc gọi được soạn vào cuộc gọi của người khác, như bạn có thể thấy với biến số someClass.

Câu hỏi của tôi không phải là điều này có đúng về mặt kỹ thuật hay không ... tôi biết điều này có hiệu quả, bởi vì tôi hiện đang làm việc đó. Câu hỏi của tôi là liệu đây có phải là sự lạm dụng đóng cửa hay tách biệt truy vấn lệnh hay bất kỳ nguyên tắc nào khác ... và sau đó hỏi mô hình nào tốt hơn để xử lý tình huống này, bởi vì tôi khá chắc chắn rằng tôi bị mắc kẹt trong chế độ "búa mới sáng bóng" với loại mã này, ngay bây giờ.

+4

Nó không phải là rất dễ dàng để đọc .... –

+0

100% đồng ý. thật đáng buồn, nó vẫn là một cải tiến đáng kể so với những gì tôi bắt đầu. một ít định dạng hơn có thể giúp ... có thể ... –

+2

Ouch, mắt tôi .... –

Trả lời

11

Như đã lưu ý, bạn gần như đã triển khai một Monad tại đây.

Mã của bạn hơi không thích hợp trong đó các lambdas có tác dụng phụ. Monads giải quyết điều này một cách tao nhã hơn.

Vì vậy, tại sao không biến mã của bạn thành một Monad phù hợp?

Phần thưởng: bạn có thể sử dụng cú pháp LINQ!


tôi trình bày:

LINQ to Kết quả

 
Ví dụ:

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); 

Với LINQ to Kết quả, điều này đầu tiên thực hiện SomeThingHappensHere. Nếu thành công, nó sẽ nhận được giá trị của thuộc tính Data của kết quả và thực hiện SomeOtherThingHappensHere.Nếu thành công, nó thực hiện AndYetAnotherThing, v.v.

Như bạn có thể thấy, bạn có thể dễ dàng chuỗi hoạt động và tham khảo kết quả của các hoạt động trước đó. Mỗi hoạt động sẽ được thực hiện cái khác, và thực hiện sẽ dừng lại khi gặp lỗi.

Các bit from x in mỗi dòng là một chút ồn ào, nhưng IMO không có gì phức tạp so sánh sẽ nhận được dễ đọc hơn này!


Làm cách nào để chúng tôi thực hiện công việc này?

Monads trong C# bao gồm ba phần:

  • một loại Something-of-T,

  • Select/SelectMany phương pháp khuyến nông cho nó, và

  • một để chuyển đổi một số T thành một số Cái gì đó của T.

Tất cả những gì bạn cần làm là tạo ra thứ gì đó giống như Monad, cảm giác như Monad và có mùi giống Monad và mọi thứ sẽ hoạt động tự động.


Các loại và phương pháp cho LINQ cho kết quả như sau.

quả <T> loại:

Một lớp đơn giản mà đại diện cho một kết quả. Kết quả là giá trị loại T hoặc lỗi. Kết quả có thể được tạo từ T hoặc từ Ngoại lệ.

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; 
     } 
    } 
} 

phương pháp mở rộng:

Triển khai cho SelectSelectMany phương pháp. Chữ ký của phương thức được đưa ra trong thông số C#, vì vậy tất cả những gì bạn phải lo lắng là việc triển khai chúng. Những điều này khá tự nhiên nếu bạn cố gắng kết hợp tất cả các đối số phương thức một cách có ý nghĩa.

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)); 
    } 
} 

Bạn có thể tự do thay đổi kết quả <T> lớp và các phương pháp khuyến nông, ví dụ, để thực hiện các quy định phức tạp hơn. Chỉ chữ ký của các phương thức mở rộng phải chính xác như đã nêu.

+0

Son-of-a ... Tôi lãng phí một giờ làm việc về điều này. Đồ tốt. –

+0

Vâng, tôi thích nó. –

+0

Tuyệt vời. Đã làm cho các ông bố bối rối một thời gian và điều này tạo ra rất nhiều ý nghĩa và chắc chắn đã giúp tôi hiểu. Bạn không thể di chuyển nhiều logic trong các phần mở rộng Select/SelectMany thành một Bind (Func ) hay tương tự? Không hoàn toàn chắc chắn về cú pháp nhưng nó sẽ loại bỏ rất nhiều sự trùng lặp trong các phương thức mở rộng. – Neal

2

Trông với tôi như bạn đã xây dựng một cái gì đó rất giống với một đơn nguyên ở đây.

Bạn có thể biến nó thành một đơn vị thích hợp bằng cách đặt loại đại biểu là Func<SomeClass, SomeClass>, có một số cách để thiết lập giá trị SomeClass ban đầu để chuyển vào và có DoSomething chuyển giá trị trả về của một làm tham số tiếp theo - điều này sẽ làm cho chuỗi rõ ràng hơn là dựa vào trạng thái chia sẻ lexically scoped.

+0

vâng, tôi đã học/học monads gần đây và tôi đã cố gắng để tạo ra một cái gì đó dọc theo những dòng. vấn đề với đề xuất của bạn là tôi cần phải luôn luôn trả về "kết quả" lớp học từ chức năng, và trong một trường hợp tôi cần phải cũng có được "someClass" dữ liệu ra khỏi một trong những chức năng tiếp theo. –

+0

+1 Các tác dụng phụ khó có thể giải thích và thử nghiệm. Bạn sẽ cảm ơn bản thân sau này nếu bạn cấu trúc lại thành 'Func' thay vì' Hành động' và các hoạt động chuỗi rõ ràng. –

+0

Tại sao rời khỏi loại kết quả quá quan trọng? Có phải nó đang được truyền qua mã của người khác mà bạn không thể sửa đổi trước khi quay trở lại để có một liên kết khác trong chuỗi thành phần được khắc phục không? –

0

Điểm yếu của mã này là liên kết ngầm giữa lambdas thứ nhất và thứ hai. Tôi không chắc chắn cách tốt nhất để sửa chữa nó.

+0

chắc chắn. đó là thiết kế xấu và khớp nối ngữ nghĩa - bạn phải biết những gì đang xảy ra dưới mui xe để hiểu các nhánh của việc kéo giá trị ra khỏi cái này và cái kia. –