我可以从 csharp 中的编译时环境变量中创建一个常量吗?
Posted
技术标签:
【中文标题】我可以从 csharp 中的编译时环境变量中创建一个常量吗?【英文标题】:Can I make a constant from a compile-time env variable in csharp? 【发布时间】:2011-05-25 21:56:26 【问题描述】:我们使用Hudson 来构建我们的项目,并且Hudson 在编译时方便地定义了诸如“%BUILD_NUMBER%”之类的环境变量。
我想在代码中使用这个变量,这样我们就可以在运行时记录下构建的内容。但是我不能做 System.Environment.GetEnvironmentVariable 因为那是访问运行时环境,我想要的是这样的:
#define BUILD_NUM = %BUILD_NUMBER%
或
const string BUILD_NUM = %BUILD_NUMBER%
除非我不知道语法。有人可以指出我正确的方向吗?谢谢!
【问题讨论】:
您可以使用预构建操作/宏来更改数字,这是一种选择吗? 我希望不需要每次都修改文件,输入源文件保持不变,并将当前值插入到编译输出中。 【参考方案1】:我也遇到过类似的问题。
我正在开发一个带有 ASP.Net 后端的 Xamarin 移动应用程序。我有一个包含后端服务器 URL 的设置类:
namespace Company.Mobile
public static class Settings
#if DEBUG
const string WebApplicationBaseUrl = "https://local-pc:44335/";
#else
const string WebApplicationBaseUrl = "https://company.com/";
#endif
它对调试和发布配置有不同的值。但是当几个开发人员开始从事该项目时,这并没有奏效。每台开发机器都有自己的 IP 地址,手机需要使用唯一的 IP 地址进行连接。
我需要在每台开发机器上从文件或环境变量中设置常量值。这就是Fody 适合的地方。我用它来创建in solution weaver。这里是the details。
我将 Settings
类放在 Xamarin 应用项目中。该项目必须包含 Fody Nuget 包:
<ItemGroup>
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Fody" Version="6.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<WeaverFiles Include="$(SolutionDir)Company.Mobile.Models\bin\Debug\netstandard2.0\Company.Mobile.Models.dll" WeaverClassNames="SetDevServerUrlWeaver" />
</ItemGroup>
我只在调试配置上进行设置,因为我不希望在发布版本上发生替换。
weaver 类被放置在移动项目所依赖的类库项目 (Company.Mobile.Models) 中(您不需要也不应该有这种依赖关系,但 Fody 文档清楚地表明该项目包含weaver 必须在发出编织程序集的项目之前构建)。这个库项目包括 FodyHelpers Nuget 包:
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<PackageReference Include="FodyHelpers" Version="6.2.0" />
</ItemGroup>
weaver类定义如下:
#if DEBUG
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Fody;
namespace Company.Mobile.Models
public class SetDevServerUrlWeaver : BaseModuleWeaver
private const string SettingsClassName = "Settings",
DevServerUrlFieldName = "WebApplicationBaseUrl",
DevServerUrlSettingFileName = "devServerUrl.txt";
public override void Execute()
var target = this.ModuleDefinition.Types.SingleOrDefault(t => t.IsClass && t.Name == SettingsClassName);
var targetField = target.Fields.Single(f => f.Name == DevServerUrlFieldName);
try
targetField.Constant = File.ReadAllText(Path.Combine(this.ProjectDirectoryPath, DevServerUrlSettingFileName));
catch
this.WriteError($"Place a file named DevServerUrlSettingFileName and place in it the dev server URL");
throw;
public override IEnumerable<string> GetAssembliesForScanning()
yield return "Company.Mobile";
#endif
这是放置在移动应用项目中的 FodyWeavers.xml 文件:
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<SetDevServerUrlWeaver />
</Weavers>
devServerUrl.txt 只包含我的本地 IP:
https://192.168.1.111:44335/
。不得将此文件添加到源代码管理中。将其添加到您的源代码管理忽略文件中,以便每个开发人员都有自己的版本。
您可以轻松地从环境变量 (System.Environment.GetEnvironmentVariable
) 或任何地方而不是文件中读取替换值。
我希望有更好的方法来做到这一点,比如 Roslyn 或 this attribute 似乎可以完成这项工作,但事实并非如此。
【讨论】:
【参考方案2】:好的,这就是我最后要做的。它不是很优雅,但它确实有效。我创建了一个如下所示的预构建步骤:
echo namespace Some.Namespace > "$(ProjectDir)\CiInfo.cs"
echo >> "$(ProjectDir)\CiInfo.cs"
echo ///^<summary^>Info about the continuous integration server build that produced this binary.^</summary^> >> "$(ProjectDir)\CiInfo.cs"
echo public static class CiInfo >> "$(ProjectDir)\CiInfo.cs"
echo >> "$(ProjectDir)\CiInfo.cs"
echo ///^<summary^>The current build number, such as "153"^</summary^> >> "$(ProjectDir)\CiInfo.cs"
echo public const string BuildNumber = ("%BUILD_NUMBER%" == "" ? @"Unknown" : "%BUILD_NUMBER%"); >> "$(ProjectDir)\CiInfo.cs"
echo ///^<summary^>String of the build number and build date/time, and other useful info.^</summary^> >> "$(ProjectDir)\CiInfo.cs"
echo public const string BuildTag = ("%BUILD_TAG%" == "" ? @"nohudson" : "%BUILD_TAG%") + " built: %DATE%-%TIME%"; >> "$(ProjectDir)\CiInfo.cs"
echo >> "$(ProjectDir)\CiInfo.cs"
echo >> "$(ProjectDir)\CiInfo.cs"
然后我将“CiInfo.cs”添加到项目中,但从版本控制中忽略了它。这样我就不必编辑或提交它,并且该项目始终有一个可用的常量,即最新的内部版本号和时间。
【讨论】:
谢谢。我会把它做成一个批处理文件,然后从批处理中调用它,并通过路径传入文件名并使用 %1 进行重定向,如果大部分可以批量处理,可能更容易维护:$(ProjectDir)\Tools\BuildCI.bat "$(ProjectDir)\CiInfo.cs"
我喜欢这个。如此简单,无需插件!谢谢。
我一直在寻找这个问题的标题,因为我想做与这个答案所说明的完全相同的事情。 +1
这让我对它的独创性微笑。谢谢。如果您正在解析 %DATE% 值,请务必使用 CultureInfo("en-AU")
(或类似名称)以确保“17/12/2017”不会对美国人造成影响。可能值得在类的顶部添加一条通常的注释,“此代码是由工具创建的......更改将丢失......”等,以防有人想知道......
对于那些寻找更可定制日期的人,您可以尝试echo public const string BuildTimestamp = "$([System.DateTime]::UtcNow.ToString("yyyy-MM-ddTHH:mm:ss 'GMT'"))"; >> "$(ProjectDir)\CiInfo.cs"
【参考方案3】:
一种可能性是使用 T4 生成您的配置类,并实例化所有常量。 T4 已很好地集成到 MSVS 中,无需您自己的自定义构建步骤。
【讨论】:
非常感谢!我用 Visual Studio 开发了将近 20 年,从来没有这样的新东西存在 :)【参考方案4】:define 不允许您像在 C/C++ 中那样在 C# 中定义内容。
来自this page:
#define 指令不能像在 C 和 C++ 中那样用于声明常量值。 C# 中的常量最好定义为类或结构的静态成员。如果您有多个此类常量,请考虑创建一个单独的“常量”类来保存它们。
如果您希望在您的 AssemblyInfo 类中反映内部版本号,大多数构建工具都支持在构建时生成该类。 MSBuild has a task 为它。 As does NAnt。不确定 Hudson 是如何做到的。
【讨论】:
【参考方案5】:一种方法是在编译之前添加一个构建步骤,该步骤在 %BUILD_NUMBER% 的相应源文件中进行正则表达式替换。
【讨论】:
+1,虽然在过去我这样做时,我只是将.config
文件作为设置目标,然后其余代码只是从那里访问它。
这大致是我们目前所做的(虽然是针对配置文件,而不是源文件),但我希望我可以直接在代码中访问它,因为它相当简单,不易出错,并且赢得了'不显示为与 svn 存储库的更改。以上是关于我可以从 csharp 中的编译时环境变量中创建一个常量吗?的主要内容,如果未能解决你的问题,请参考以下文章