在一些开发环境和运行环境分离的情境中,不可避免的会遇到以下的情况(例如我这里的例子是在写QQ机器人的时候):“为什么我刚才修改的代码没有生效?这是哪个版本?我有没有正确地将刚刚编译的版本复制到服务器上?”
在部署步骤由他人负责的时候,这种情况更加常见:“你到底有没有覆盖最新的文件?你确定你覆盖的是我最新发给你的文件?你确定你没覆盖错文件?”
血压,upupup。
显然我们并不可能每次修改一两条语句就更新一次程序集版本,在这种情况下,能够将程序的编译时间打包进程序里,并在程序启动时输出无疑可以避免很多不必要的折磨。
.NET是没有提供这个功能的。所以我们需要自己来实现。
以下内容以.NET 6.0为基准,但是在.NET 7.0等后续版本中也适用。
首先,为了避免绕弯路,我们先问问chatGPT——
我:C#中,我如何在程序中获取到自己的编译时间并输出呢?
ChatGPT:在C#中,获取程序的编译时间并输出的方法是使用
我:我的程序是是用.net 7.0编译的,那么我应该如何实现它呢?是否应该通过自定义生成脚本,在每次编译的时候将一个变量更新?
ChatGPT:是的,在.NET Core, .NET 5及以上版本(包括.NET 7.0)中,使用自定义生成脚本在每次编译时更新一个变量是比较好的解决方案。以下是一个使用MSBuild的示例………………
ChatGPT:在C#中,获取程序的编译时间并输出的方法是使用
System.Reflection
命名空间。具体操作如下:………………请注意,这种方法仅适用于.NET Framework。在.NET Core和.NET 5及以上版本中,版本信息可能不会自动更新,因此这种方法可能不会返回正确的编译时间。在这些情况下,你可能需要查找其他方法来获取编译时间,例如使用预处理器指令或自定义生成脚本。我:我的程序是是用.net 7.0编译的,那么我应该如何实现它呢?是否应该通过自定义生成脚本,在每次编译的时候将一个变量更新?
ChatGPT:是的,在.NET Core, .NET 5及以上版本(包括.NET 7.0)中,使用自定义生成脚本在每次编译时更新一个变量是比较好的解决方案。以下是一个使用MSBuild的示例………………
好的,恭喜ChatGPT选手成功杀死了比赛。
要实现这个功能,我们需要在编译代码时告诉.NET编译器将System.DateTime.Now
或System.DateTime.UtcNow
写入程序集的SourceRevisionId
标签。
打开程序的csproj
文件,添加SourceRevisionId
内容如下。
<Project Sdk="Microsoft.NET.Sdk">
<!--原有的内容-->
<PropertyGroup>
<SourceRevisionId>build$([System.DateTime]::UtcNow.ToString("yyyyMMddHHmmssZ"))</SourceRevisionId>
</PropertyGroup>
</Project>
然后在程序中使用
public static DateTime GetBuildTime(Assembly assembly)
{
const string buildVersionMetadataPrefix = "+build";
var attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
var value = attribute?.InformationalVersion;
var index = value?.IndexOf(buildVersionMetadataPrefix, StringComparison.Ordinal);
if (index > 0 && DateTime.TryParseExact(value[(index.Value + buildVersionMetadataPrefix.Length)..], "yyyyMMddHHmmssZ", CultureInfo.InvariantCulture, DateTimeStyles.None, out var result))
return result;
return default;
}
获取编译时间对应的DateTime对象。
这里其实用了C#的语法糖,利用了当int?类型变量index为null时,index>0也为false的特性,而这并不是什么好的习惯,代码的逻辑可读性变得极差。把这段代码按照规范的方式写的话应该是下面这样
public static DateTime GetBuildTime(Assembly assembly)
{
const string buildVersionMetadataPrefix = "+build";
var attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
if (attribute?.InformationalVersion != null)
{
var value = attribute.InformationalVersion;
var index = value.IndexOf(buildVersionMetadataPrefix, StringComparison.Ordinal);
if (index > 0)
{
value = value[(index + buildVersionMetadataPrefix.Length)..];
if (DateTime.TryParseExact(value, "yyyyMMddHHmmssZ", CultureInfo.InvariantCulture, DateTimeStyles.None, out var result))
return result;
}
}
return default;
}
这里的时间字符串结尾添加了字母Z,表示这是一个UTC时间。
如果不写上这个Z的话,DateTime.TryParseExact默认会按照本地时间而不是UTC时间解析。
以北京时间为例,最终结果会和实际相差8个小时。
如果不写上这个Z的话,DateTime.TryParseExact默认会按照本地时间而不是UTC时间解析。
以北京时间为例,最终结果会和实际相差8个小时。
现在,在程序启动时,调用此方法即可输出编译版本
var buildTime = GetBuildTime(Assembly.GetEntryAssembly());
TimeSpan timeSinceBuild = DateTime.Now - buildTime;
Console.WriteLine($"编译时间:{buildTime}");
Console.WriteLine($"已经过了: {timeSinceBuild.Days} 天 {timeSinceBuild.Hours} 小时 {timeSinceBuild.Minutes} 分钟 {timeSinceBuild.Seconds} 秒");
牛逼