2011-03-23 26 views
25

Về cơ bản tôi có một phương pháp vô danh mà tôi sử dụng cho BackgroundWorker tôi:Làm cách nào để mang lại lợi nhuận trong các phương thức ẩn danh?

worker.DoWork += (sender, e) => 
{ 
    foreach (var effect in GlobalGraph.Effects) 
    { 
     // Returns EffectResult 
     yield return image.Apply (effect); 
    } 
}; 

Khi tôi làm điều này trình biên dịch nói với tôi:

"Tuyên bố năng suất không thể được sử dụng bên trong một phương pháp vô danh hoặc lambda biểu thức "

Vì vậy, trong trường hợp này, cách thanh lịch nhất để làm điều này là gì? Btw Phương thức DoWork này nằm trong một phương thức tĩnh, trong trường hợp có vấn đề đối với giải pháp.

+0

Nhân viên nền tạo 'ảnh' hoặc điền vào 'GlobalGraph.Effects' có đếm được không? – Enigmativity

+0

Có BW đang tạo ra hình ảnh nhưng EffectResult có trạng thái về hiệu ứng, không phải dữ liệu hình ảnh hay bất cứ thứ gì như thế. –

+0

bản sao có thể có của [Trong C#, tại sao một phương thức nặc danh không thể chứa một tuyên bố về lợi nhuận?] (Http://stackoverflow.com/questions/1217729/in-c-why-cant-an-anonymous-method-contain- a-yield-statement) –

Trả lời

1

Ok vì vậy tôi đã làm một cái gì đó như thế này mà làm những gì tôi muốn (một số biến bỏ qua):

public static void Run (Action<float, EffectResult> action) 
{ 
    worker.DoWork += (sender, e) => 
    { 
     foreach (var effect in GlobalGraph.Effects) 
     { 
      var result = image.Apply (effect); 

      action (100 * (index/count), result); 
     } 
    } 
}; 

và sau đó trong trang web gọi:

GlobalGraph.Run ((p, r) => 
    { 
     this.Progress = p; 
     this.EffectResults.Add (r); 
    }); 
9

Có lẽ chỉ trả lại biểu thức LINQ và trì hoãn việc thực hiện như năng suất:

return GlobalGraph.Effects.Select(x => image.Apply(x)); 
13

Đáng tiếc là bạn không thể.

Trình biên dịch không cho phép bạn kết hợp hai đoạn mã "ma thuật". Cả hai liên quan đến việc viết lại mã của bạn để hỗ trợ những gì bạn muốn làm:

  1. Một phương pháp vô danh được thực hiện bằng cách di chuyển mã đến một phương pháp thích hợp, và nâng biến địa phương để các lĩnh vực trên lớp học với phương pháp mà
  2. Một iterator phương pháp được viết lại như một máy nhà nước

bạn có thể, tuy nhiên, viết lại đoạn code để trả lại bộ sưu tập, vì vậy trong trường hợp cụ thể của bạn tôi sẽ làm điều này:

worker.DoWork += (sender, e) => 
{ 
    return GlobalGraph.Effects 
     .Select(effect => image.Apply(effect)); 
}; 

mặc dù nó trông kỳ lạ cho một sự kiện (sender, e) để trả lại bất cứ điều gì cả. Bạn có chắc chắn rằng bạn đang hiển thị một kịch bản thực sự cho chúng tôi?


Sửa Ok, tôi nghĩ tôi thấy những gì bạn đang cố gắng làm ở đây.

Bạn có một cuộc gọi phương thức tĩnh và sau đó bạn muốn thực thi mã trong nền và sau đó trả về dữ liệu từ phương thức tĩnh đó khi cuộc gọi nền hoàn thành.

Đây là, trong khi có thể, không phải là giải pháp tốt vì bạn đang tạm dừng một chuỗi để chờ một chuỗi khác, bắt đầu ngay trước khi bạn tạm dừng chuỗi. Nói cách khác, tất cả những gì bạn đang làm là thêm phí chuyển đổi ngữ cảnh.

Thay vào đó bạn chỉ cần khởi động công việc nền và sau đó khi công việc đó được hoàn thành, hãy xử lý dữ liệu kết quả.

+0

Cảm ơn Lasse. Trên thực tế bạn là đúng, tôi không chắc chắn nếu tôi đang làm điều đúng về điều này. Về cơ bản, nhân viên này nằm bên trong một phương thức tĩnh gọi là Run, được cho là sẽ trả về IEnumerable . Tôi nên làm như thế nào? Bên ngoài DoWork? Tôi chỉ làm điều đó vì áp dụng một loạt các hiệu ứng là tất cả phương pháp này đang làm (và trả về kết quả). –

+0

Ngoài ra mặc dù tôi đã cho thấy nó như thế này, tôi có một vòng lặp for làm cái gì khác trước khi image.Apply được gọi, cho mỗi lần lặp lại, tôi có thể đặt một vòng lặp for bên trong một biểu thức lambda? –

+0

Bạn không thể làm một 'return' bên trong một lambda có nghĩa là để trở về từ phương pháp kèm theo. Một 'return' trong lambda trả về từ chính lambda. – Enigmativity

1

DoWork thuộc loại DoWorkEventHandler không trả về gì (void), vì vậy hoàn toàn không thể xảy ra trong trường hợp của bạn.

1

Nhân viên nên đặt thuộc tính Kết quả của DoWorkEventArgs.

worker.DoWork += (s, e) => e.Result = GlobalGraph.Effects.Select(x => image.Apply(x)); 
+0

Tôi có thể "nhấn vào" kết quả này như một IEnumerable không? Bởi vì tôi muốn cập nhật giao diện người dùng của mình vì các hiệu ứng được áp dụng, liên quan đến kết quả của chúng. –

+0

Bạn nên thiết lập trình xử lý cho RunWorkerCompleted. Sau đó, foreach (hiệu ứng var trong (GlobalGraph.Effects) e.Result) ... –

+0

Vì vậy, bạn có nghĩa là tôi nên thêm cập nhật tiến độ và cập nhật giao diện người dùng cho RunWorkerCompleted? Bởi vì tôi có một số tách biệt giữa lớp này và giao diện người dùng, với ViewModels, v.v. –

4

Trừ khi tôi bỏ lỡ điều gì đó, bạn không thể làm những gì bạn đang yêu cầu.

(Tôi không có một câu trả lời cho bạn, vì vậy hãy đọc qua lời giải thích của tôi về lý do tại sao bạn không thể làm những gì bạn đang làm đầu tiên, và sau đó đọc trên.)

Bạn đầy đủ phương pháp sẽ giống như thế này:

public static IEnumerable<EffectResult> GetSomeValues() 
{ 
    // code to set up worker etc 
    worker.DoWork += (sender, e) => 
    { 
     foreach (var effect in GlobalGraph.Effects) 
     { 
      // Returns EffectResult 
      yield return image.Apply (effect); 
     } 
    }; 
} 

Nếu chúng ta giả định rằng mã của bạn là "hợp pháp" sau đó khi GetSomeValues được gọi là, mặc dù các handler DoWork được thêm vào worker, khái niệm lambda không được thực hiện cho đến khi sự kiện DoWork là bắn. Vì vậy, các cuộc gọi đến GetSomeValues hoàn thành mà không trả lại bất kỳ kết quả và lamdba có thể hoặc có thể không nhận được gọi là ở giai đoạn sau - đó là sau đó quá muộn cho người gọi của phương pháp GetSomeValues anyway.

Câu trả lời hay nhất của bạn là sử dụng Rx.

Rx quay IEnumerable<T> trên đầu. Thay vì yêu cầu các giá trị từ một số đếm, Rx có các giá trị được đẩy tới bạn từ một số IObservable<T>.

Vì bạn đang sử dụng nhân viên hỗ trợ nền và trả lời sự kiện bạn đang có hiệu quả với các giá trị được đẩy vào bạn. Với Rx nó trở nên dễ dàng để làm những gì bạn đang cố gắng làm.

Bạn có một vài tùy chọn. Có lẽ là đơn giản nhất là để làm điều này:

public static IObservable<IEnumerable<EffectResult>> GetSomeValues() 
{ 
    // code to set up worker etc 
    return from e in Observable.FromEvent<DoWorkEventArgs>(worker, "DoWork") 
      select (
       from effect in GlobalGraph.Effects 
       select image.Apply(effect) 
      ); 
} 

Bây giờ người gọi của phương pháp GetSomeValues của bạn sẽ làm điều này:

GetSomeValues().Subscribe(ers => 
{ 
    foreach (var er in ers) 
    { 
     // process each er 
    } 
}); 

Nếu bạn biết rằng DoWork chỉ sẽ bắn một lần, sau đó phương pháp này có thể là một tốt hơn một chút:

public static IObservable<EffectResult> GetSomeValues() 
{ 
    // code to set up worker etc 
    return Observable 
     .FromEvent<DoWorkEventArgs>(worker, "DoWork") 
     .Take(1) 
     .Select(effect => from effect in GlobalGraph.Effects.ToObservable() 
          select image.Apply(effect)) 
     .Switch(); 
} 

Mã này có vẻ phức tạp hơn một chút, nhưng nó chỉ biến một sự kiện làm việc thành luồng EffectResult các đối tượng.

Sau đó, mã gọi trông như thế này:

GetSomeValues().Subscribe(er => 
{ 
    // process each er 
}); 

Rx thậm chí có thể được sử dụng để thay thế người lao động nền. Đây có thể là lựa chọn tốt nhất cho bạn:

public static IObservable<EffectResult> GetSomeValues() 
{ 
    // set up code etc 
    return Observable 
     .Start(() => from effect in GlobalGraph.Effects.ToObservable() 
        select image.Apply(effect), Scheduler.ThreadPool) 
     .Switch(); 
} 

Mã gọi giống như ví dụ trước. Scheduler.ThreadPool cho Rx biết cách "lên lịch" việc xử lý đăng ký cho người quan sát.

Tôi hy vọng điều này sẽ hữu ích.

+0

Cảm ơn rằng có vẻ như là một giải pháp tốt. Bạn cũng biết một giải pháp hoàn toàn bằng cách sử dụng BW? Tôi muốn sử dụng Rx, nhưng hiện tại tôi không muốn thêm vào các phụ thuộc mới, nếu có thể. Sau đó tôi có thể làm việc để làm đẹp mã. –

+0

@Janan - Bạn không thể sử dụng BW để làm những gì bạn muốn. Nó không có ý nghĩa để sử dụng nó. Mã gọi của bạn đang nhận được một 'IEnumerable ' mà nó phải chặn trong khi BW đang làm việc của nó. Bạn cũng có thể tránh BW hoàn toàn vì điều đó. Bạn cần phải sử dụng một cái gì đó như Rx hoặc TPL. – Enigmativity

+0

OK thì làm cách nào để cập nhật giao diện người dùng trong khi sử dụng BW? Dường như nó phải tầm thường để làm điều này ít nhất. Tôi đang sử dụng WPF và dần dần muốn cập nhật các bộ sưu tập giao diện người dùng được ràng buộc với vì vậy đó là lý do tại sao tôi muốn sử dụng 'IEnumerable '. –

0

Đối với độc giả mới: thanh lịch nhất cách để thực hiện 'trình vòng lặp ẩn danh' (tức làlồng trong các phương pháp khác) trong C# 5 có lẽ là một cái gì đó giống như this cool trick with async/await (không bị nhầm lẫn bởi các từ khóa, mã dưới đây được tính toán hoàn toàn đồng bộ - xem chi tiết tại trang liên kết):

 public IEnumerable<int> Numbers() 
     { 
      return EnumeratorMonad.Build<int>(async Yield => 
      { 
       await Yield(11); 
       await Yield(22); 
       await Yield(33); 
      }); 
     } 

     [Microsoft.VisualStudio.TestTools.UnitTesting.TestMethod] 
     public void TestEnum() 
     { 
      var v = Numbers(); 
      var e = v.GetEnumerator(); 

      int[] expected = { 11, 22, 33 }; 

      Numbers().Should().ContainInOrder(expected); 

     } 

C# 7 (hiện có sẵn trong Visual Studio 15 Preview) hỗ trợ local functions, which allow yield return:

public IEnumerable<T> Filter<T>(IEnumerable<T> source, Func<T, bool> filter) 
{ 
    if (source == null) throw new ArgumentNullException(nameof(source)); 
    if (filter == null) throw new ArgumentNullException(nameof(filter)); 

    return Iterator(); 

    IEnumerable<T> Iterator() 
    { 
     foreach (var element in source) 
     { 
      if (filter(element)) { yield return element; } 
     } 
    } 
} 
Các vấn đề liên quan