2016-03-02 15 views
7

Tôi có thể tạo các phương thức tiện ích mở rộng theo bất kỳ loại nào. Một khi loại đó là Func of int chẳng hạn.Phương pháp mở rộng cho hàm

Tôi muốn viết các phương thức mở rộng cho các hàm, không phải kiểu trả về của hàm.

tôi có thể làm điều đó một cách hacky:

Func<int> getUserId =() => GetUserId("Email"); 
int userId = getUserId.Retry(2); 

Trường hợp chức năng Thử lại là một phương pháp mở rộng định nghĩa là:

public static T Retry<T>(this Func<T> func, int maxAttempts) 
{ 
    for (int i = 0; i < maxAttempts; i++) 
    { 
     try 
     { 
      return func(); 
     } 
     catch 
     { 

     } 
    } 

    throw new Exception("Retries failed."); 
} 

Những gì tôi thực sự muốn làm là:

var userId = (() => GetUserId("Email")).Retry(2); 

Nhưng trình biên dịch không hòa hợp chức năng dưới dạng Func của T.

tôi biết các tĩnh kể cả trong Roslyn, vì vậy tôi có thể làm điều gì đó như:

Retry(() => GetUserId("Email"), 2); 

Nhưng tôi tìm thấy điều này khó khăn hơn để đọc. Tôi thực sự muốn chức năng trợ giúp mà tôi tạo ra. Có các mẫu khác trên đó có thể cho tôi kết quả tương tự, như biểu thức đơn thuần, hoặc sử dụng chuỗi (tức là chuyển T thành kiểu chuỗi, nội bộ có T, và sau đó tôi viết phương thức mở rộng cho Chuỗi T). Vấn đề tôi có với cách tiếp cận này là bạn phải bắt đầu biểu thức bằng cách truyền tới một Chuỗi T, và sau đó kết thúc biểu thức bằng cách truyền tới T, rất nhiều tiếng ồn kéo sự chú ý của người đọc ra khỏi logic nghiệp vụ của tôi.

Tôi biết tôi có thể sử dụng phép đúc ngầm trên Chuỗi T đến T, nhưng điều này có vẻ như nó đang thực hiện một số phép thuật đằng sau hậu trường.

Vì vậy, có thể lấy tham chiếu đến một hàm, mà không thực hiện nó trước, với ít hoặc không có mã mảng nồi hơi?

End của ngày nào đó tôi muốn viết những điều sau đây cho bất kỳ loại Func/Hành động:

var settings = LoadSettingsFromDatabase().Retry(2); 
+0

thạo API không phù hợp với yêu cầu của bạn? – alsafoo

+0

Bạn * biết * rằng bạn không thể tạo phương thức tiện ích mở rộng cho lambda trực tiếp, bởi vì bạn đã thực hiện và thấy lỗi. Tại thời điểm đó nó chỉ đơn giản là đi xuống đến sở thích cá nhân của những gì có nghĩa là gõ lambda như một đại biểu mà bạn cá nhân thích, mà chỉ đơn giản là một vấn đề của ý kiến. Có rất nhiều cách để làm điều đó; bạn đã cung cấp một số. – Servy

+0

@alsafoo Fluent API sử dụng mẫu chuỗi mà tôi biết nhưng muốn tránh. Tôi sử dụng API thông thạo để xác thực đầu vào, nhưng sẽ không muốn sử dụng nó cho logic nghiệp vụ. – user1909158

Trả lời

6

mỗi this question, tôi nghĩ rằng câu trả lời là "không".

tôi sẽ tư vấn cho bạn đi với một tĩnh bao gồm các Retry, như bạn đề nghị:

Retry(() => GetUserId("Email"), 2); 

Nó làm cho ý định rõ ràng, nó không phức tạp, nó đủ có thể đọc được, và đó là thành ngữ C#.

Một ý tưởng rằng tôi không thích:

Nếu bạn đã sẵn sàng để đảo ngược đối số phương pháp của bạn, sau đây sẽ làm việc (nhưng tôi nghĩ rằng hầu hết mọi người sẽ nghĩ nó khá khủng khiếp):

public static T AttemptsAt<T>(this int maxAttempts, Func<T> func) 
{ 
    for (int i = 0; i < maxAttempts; i++) 
    { 
     try 
     { 
      return func(); 
     } 
     catch 
     { 

     } 
    } 
    throw new Exception("Retries failed."); 
} 

cách sử dụng:

var userId = 2.AttemptsAt(() => GetUserId("Email")); 
+0

Đây là một giải pháp tốt đẹp (ý tưởng bạn không thích), tôi sẽ để đề xuất nó nhưng bạn đánh bại tôi với nó –

+0

Lần đầu tiên tôi xem xét giải pháp thứ hai (không được khuyến nghị) này. +1 –

0

tôi không biết nếu nó trả lời câu hỏi của bạn nhưng có vẻ như có thể xác định một phương pháp mở rộng của Func (hoặc hành động), xem:

http://www.codeproject.com/Articles/1104555/The-Function-Decorator-Pattern-Reanimation-of-Func. Để báo mr Jordan:

public static Func<TArg, TResult> RetryIfFailed<TArg, TResult> 
           (this Func<TArg, TResult> func, int maxRetry) { 
return (arg) => { 
    int t = 0; 
    do { 
     try { 
      return func(arg); 
     } 
     catch (Exception) { 
      if (++t > maxRetry) { 
       throw; 
      } 
     } 
    } while (true); 
}; 
} 

    .... 
    // get the method we are going to retry with 
    Func<DateTime, string> getMyDate = client.GetMyDate; 
    // intercept it with RetryIfFailed interceptor, which retries once at most 
    getMyDate = getMyDate.RetryIfFailed(1); 
    for (var i = 0; i < TimesToInvoke; i++) { 
    try { 
     // call the intercepted method instead of client.GetMyDate 
     getMyDate(DateTime.Today.AddDays(i % 30)); 
     counter.TotalSuccess++; 
    } 
    catch (Exception ex) {counter.TotalError++; } 
    .... 
0

Tất nhiên nếu bạn cần một lớp lót, bạn sẽ phải bỏ một cách rõ ràng để loại đại biểu mong muốn:

var userId = ((Func<int>)(() => GetUserId("Email"))).Retry(2); 
Các vấn đề liên quan