【问题标题】:Can I make a constant from a compile-time env variable in csharp?我可以从 csharp 中的编译时环境变量中创建一个常量吗?
【发布时间】:2025-12-13 03:10:02
【问题描述】:

我们使用Hudson 来构建我们的项目,并且Hudson 在编译时方便地定义了诸如“%BUILD_NUMBER%”之类的环境变量。

我想在代码中使用这个变量,这样我们就可以在运行时记录下构建的内容。但是我不能做 System.Environment.GetEnvironmentVariable 因为那是访问运行时环境,我想要的是这样的:

#define BUILD_NUM = %BUILD_NUMBER%

const string BUILD_NUM = %BUILD_NUMBER%

除非我不知道语法。有人可以指出我正确的方向吗?谢谢!

【问题讨论】:

  • 您可以使用预构建操作/宏来更改数字,这是一种选择吗?
  • 我希望不需要每次都修改文件,输入源文件保持不变,并将当前值插入到编译输出中。

标签: c# environment-variables compile-time


【解决方案1】:

好的,这就是我最后要做的。它不是很优雅,但它确实有效。我创建了一个如下所示的预构建步骤:

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'"))"; &gt;&gt; "$(ProjectDir)\CiInfo.cs"
【解决方案2】:

一种方法是在编译之前添加一个构建步骤,该步骤在 %BUILD_NUMBER% 的相应源文件中进行正则表达式替换。

【讨论】:

  • +1,虽然在过去我这样做时,我只是将.config 文件作为设置目标,然后其余代码只是从那里访​​问它。
  • 这大致是我们目前所做的(虽然是针对配置文件,而不是源文件),但我希望我可以直接在代码中访问它,因为它相当简单,不易出错,并且赢得了'不显示为与 svn 存储库的更改。
【解决方案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】:

    我也遇到过类似的问题。

    我正在开发一个带有 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 似乎可以完成这项工作,但事实并非如此。

    【讨论】: