【问题标题】:Extension method for a function函数的扩展方法
【发布时间】:2021-12-18 20:14:34
【问题描述】:

我可以创建任何类型的扩展方法。 例如,一旦这种类型是 int 的 Func。

我想为函数编写扩展方法,而不是函数的返回类型。

我可以用一种 hacky 的方式做到这一点:

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

其中Retry函数是一个扩展方法,定义为:

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

我真正想做的是:

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

但编译器不会将该函数协调为 T 的 Func。

我知道包括 Roslyn 在内的静态,所以我可以这样做:

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

但我发现这很难阅读。我真的希望我创建的辅助函数不碍事。

还有其他模式可以给我类似的结果,例如一元表达式,或使用链接(即将 T 转换为链类型,内部有一个 T,然后我为 T 链编写扩展方法) .我使用这种方法遇到的问题是,您必须通过转换为 T 链来开始表达式,然后通过转换为 T 来结束表达式,这会导致读者的注意力从我的业务逻辑上移开。

我知道我可以在 Chain of T to T 上使用隐式强制转换,但这感觉就像在幕后施展魔法一样。

那么是否有可能在不先执行函数的情况下获得对函数的引用,几乎没有样板代码?

一天结束时,我想为任何类型的 Func/Action 写以下内容:

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

【问题讨论】:

  • Fluent API 不符合您的要求?
  • 知道无法直接为 lambda 创建扩展方法,因为您已经完成并看到了错误。在这一点上,它只是归结为个人偏好将 lambda 键入为您个人喜欢的代表的方式,这只是一个意见问题。有几十种方法可以做到;你已经提供了几个。
  • @alsafoo Fluent API 使用链接模式,我知道但宁愿避免。我确实使用 Fluent API 进行输入验证,但不想将其用于业务逻辑。
  • 这是不可能的,因为(() =&gt; GetUserId("Email")) 不能被隐式解析。它在使用Retry(...) 时有效,因为编译器能够根据Retry 的参数将其匹配到可能的类型。简而言之 - 不,这是不可能的,除非你像这样包装它:var userID = new Func&lt;int&gt;(() =&gt; GetUserId("Email")).Retry(2)
  • @Rob 我认为这应该是一个答案

标签: c#


【解决方案1】:

根据this question,我认为答案是“不”。

我建议您按照您的建议使用 Retry 的静态包含:

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

它使意图清晰,简单,可读性强,并且是惯用的 C#。

我不喜欢的想法:

如果您愿意颠倒您的方法参数,以下方法会起作用(但我认为大多数人会认为这很糟糕):

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

用法:

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

【讨论】:

  • 这是一个不错的解决方案(你不喜欢的想法),我本来打算建议但你打败了我
  • 我第一次考虑第二个(不推荐)解决方案。 +1
【解决方案2】:

当然,如果您需要单线,则必须显式转换为所需的委托类型:

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

【讨论】:

    【解决方案3】:

    我不知道它是否回答了您的问题,但似乎可以定义 Func(或 Action)的扩展方法,请参阅:

    http://www.codeproject.com/Articles/1104555/The-Function-Decorator-Pattern-Reanimation-of-Func。引用乔丹先生的话:

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

    【讨论】:

      【解决方案4】:

      问题是成员方法不能将自己隐式转换为 Func - 在我看来这很奇怪,但也许有一个很好的解释:)。

      无论如何,这就是我如何调用 Func 扩展:

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

      【讨论】:

        【解决方案5】:

        我的建议是编写一个返回 Func 的扩展方法:

        public static Func<T, TResult> WithRetries<T, TResult>(this Func<T, TResult> func, int maxRetries)
        

        并按以下方式使用:

        var getUserId = GetUserId;
        int userId = getUserId.WithRetries(3)("Email");
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-02-13
          • 1970-01-01
          • 2014-06-11
          • 2019-03-02
          • 2016-08-26
          • 2023-03-10
          • 1970-01-01
          相关资源
          最近更新 更多