为什么要使用 Func<string> 而不是只用 string?

5

为什么会使用Func<string>而不是只用string

我的问题特别涉及这个仓库。

具体是第22行:

    private static Func<string> getToken = () => Environment.GetEnvironmentVariable("GitHubToken", EnvironmentVariableTarget.Process);

getToken封装了一个没有参数并返回string的方法。 为什么不能直接将该变量定义为字符串?

为什么要使用Func<string>而不是只使用string


1
为什么要使用Func<string>而不是只用string?这是基于个人观点的问题...建议去问作者他为什么使用。 - Selvin
1
在容器环境中,所有无服务器应用程序都是如此,传播配置的最简单方法是通过环境变量。这些变量显然是可以更改的。 - Panagiotis Kanavos
@PanagiotisKanavos 在链接的代码中,Func<string>及其使用都在private static字段中 - 这里不应该有任何区别,对吧?据我理解,在这种特定情况下,该函数只会被调用一次(每个上下文)。 - germi
1
大家冷静一点。也许我们可以在上面的代码特定范围内回答问题,这似乎是某种容器。然而,问题的提出方式让我认为它的范围比上述更加普遍。在这种情况下,不可能给出一个涵盖所有情况的合适答案。 - MakePeaceGreatAgain
1
委托会在下一个声明中立即被调用。因此,避免使用字符串是没有意义的。这不是一个好的实践,特别是对于配置代码而言,这位程序员不习惯调试他的代码。 - Hans Passant
显示剩余9条评论
6个回答

5
我能看出有五个原因会这样做:
  1. 当您想允许一个不仅会随时间改变的值(您仍然可以通过属性实现!),但是您需要检查以找到它的位置可能会改变。例如,默认情况下,您检查环境变量,但是某个用户选项将在配置文件、Web 服务、数据库表等中查找。现在,您可以交换函数调用,而不是在必须知道如何执行所有这些操作的函数内每次都检查该配置选项。
  2. 这使得在 async 上下文中使用它稍微容易一些,您可以直接在任务中等待函数,而无需将属性作为可等待方法包装。
  3. 他们想要函数语义而不是属性语义。这是一个选择,他们希望将函数调用作为类型的公共 API 而不是属性。然后,lambda(真正的:expression bodied member)只是一种更短的编写方法。
  4. 这是在 C# 8 中新的 default interface implementations in C# 8 之前编写的,其中代码提供了默认实现,但实际上希望您“覆盖”它并替换方法,以便可以查看您需要的任何值。即使他们正在使用 C# 8,额外的接口也被认为不值得努力。
  5. 在依赖注入场景中更容易更改实现,但希望这样做的人会使用接口而不是单个方法。通常注入整个类型,而不是单个方法。

这个程序的DIMs...嗯嗯嗯。那是一个的特性。它应该被添加到DIM标签的示例中。 - Panagiotis Kanavos

2
通过将其存储为 Func<string>,每次访问变量时都会调用 Environment.GetEnvironmentVariable。这意味着,如果 Environment.GetEnvironmentVariable 在后续调用中返回不同的值,则您将获得新值。
据我所知,Environment.GetEnvironmentVariable 对于相同的输入返回不同值的唯一方法是调用 Environment.SetEnvironmentVariable

这个问题涉及到 Azure Functions,它是一个基于容器的环境,在多个主机上运行多个实例。使用环境变量可能是分发配置最常见的方式。 - Panagiotis Kanavos
实际上这是唯一的方法来解释为什么它是Func<string>...因为Azure Function的主机使用1)相同的进程和2)在其他情况下使用 Environment.SetEnvironmentVariable,简单地说,static string envvar = Environment.GetEnvironmentVariable("GitHubToken");就足够了。 - Selvin
您也可以将检查环境变量的代码放在属性getter中,而无需使用Func<string> - Joel Coehoorn
@JoelCoehoorn 是的,你可以这样做。然而,我倾向于不将可能引发异常的内容放入属性中。根据文档,GetEnvironmentVariable 可能会抛出 SecurityException 异常。虽然这种情况可能性很小,但在这种情况下差异还是比较小的。 - mason
1
你说得对。我将仓库限制为环境变量,以限制我使用的Azure服务数量。如果用Azure KeyVault替换它,情况可能会有所不同。想象一下API密钥的更改和某人只需更新keyvault。 - Maxime Rouiller

2

这里是存储库的作者。

使用此方法的主要原因是类是静态的,属性也是静态的。尽管环境变量可能会更改,但在实际应用程序重新启动之前不会反映出来。

我这样做的主要原因是它永远不应该在环境变量中。它应该保存在类似于Azure KeyVault(AWS HSM?)的秘密服务存储中。该值可能会在没有通知的情况下更改,而无需重新启动应用程序,并且会继续使用旧令牌,直到发生错误。

在这种情况下需要做的就是通过一个正确保护数据的实现函数来替换原实现函数。


1
你说“环境变量可能会改变,但在应用程序重新启动之前不会反映出来” - 你确定吗?如果有人调用Environment.SetEnvironmentVariable,那么即使进程没有重新启动,该值也会更改(因为应用程序看到了它),根据文档。 - mason
@maxime,你是怎么发现这篇文章的? - Alex Gordon
1
@mason 问题在于环境变量可能会因为除了.NET运行时之外的外部原因而发生改变。通过门户设置变量等方式,99%的情况下静态变量是可以的。如果你想要覆盖密钥过期和替换,你需要更加动态的东西。 - Maxime Rouiller
@l--''''''---------'''''''''''' https://twitter.com/pkanavos/status/1204794336820580353 - Maxime Rouiller
那么就是我的答案中的第四个,;) - Joel Coehoorn

1

通常情况下,如果您的函数产生的值可能在程序执行期间发生变化,您会使用 Func<T> 而不是 T

在依赖注入的场景中也很有用,因为函数的使用者并不关心(或者不应该知道)值来自哪里。它只需要能够生成当前正确的值。

然而,在您的例子中似乎不是这种情况。在那里,您可以只使用 private static string token = ... 也能得到相同的结果。


这个忽略了从Environment.GetEnvironmentVariable获取的值会改变的情况,这可能是为什么没有硬编码的原因。 - mason

1
在容器环境中,使用环境变量进行配置是一种常见的模式。这些变量预计会发生更改,因此使用 Func<string> 来提取最新的变量是有意义的。容器本身不需要了解配置存储或如何进行更改或传播的任何信息。
更改由编排程序或主机传播到需要该特定设置的所有容器。这将允许不同组的容器使用不同的设置,为不同的租户提供服务,或者……也许使用不同的 Github 账户工作?
但是容器应用程序对所有这些都毫不知情。
AWS Lambda 和 Azure Functions 基于容器,因此这种模式是有意义的。
为什么要使用 Func 而不是字符串属性?
这略带主观色彩。如果一个人喜欢函数式编程,则传递和注入单一用途的函数比传递具有单个属性或更糟糕的多个可能不相关属性的对象更好。

呃,你仍然可以使用属性来实现这个。 - Joel Coehoorn
@JoelCoehoorn的问题是为什么要使用Func<string>而不是静态的string。并不是如何访问环境变量的问题。我猜作者更喜欢函数式编程。单个函数比对象+属性容易注入得多。 - Panagiotis Kanavos

-1

我猜测它试图传达意图。

执行方法时,您更加清楚可能会抛出错误或者有一些可以改变值的事情,这种情况下是环境配置。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接