显示构建日期

我目前有一个应用程序显示构建号在其标题窗口。这很好,除了它对大多数用户没有任何意义,他们想知道他们是否有最新的版本-他们倾向于将其称为“上周四的”而不是1.0.8.4321版本。

计划是把构建日期放在那里,例如“App构建于21/10/2009”。

我正在努力寻找一种程序化的方法,将构建日期作为文本字符串提取出来,以便像这样使用。

对于版本号,我使用:

Assembly.GetExecutingAssembly().GetName().Version.ToString()

在定义了这些是怎么来的之后。

我希望编译日期(和时间,为了加分)也像这样。

非常感谢这里的指示(如果合适的话,借口双关语),或者更整洁的解决方案……

181064 次浏览

我不确定,但也许构建增量器有帮助。

Jeff Atwood在确定构建日期的艰难方式中对这个问题有几句话要说。

最可靠的方法是从可执行文件中嵌入的PE头中检索链接器时间戳——一些c#代码(由Joe Spivey编写)来自Jeff文章的注释:

public static DateTime GetLinkerTime(this Assembly assembly, TimeZoneInfo target = null)
{
var filePath = assembly.Location;
const int c_PeHeaderOffset = 60;
const int c_LinkerTimestampOffset = 8;


var buffer = new byte[2048];


using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
stream.Read(buffer, 0, 2048);


var offset = BitConverter.ToInt32(buffer, c_PeHeaderOffset);
var secondsSince1970 = BitConverter.ToInt32(buffer, offset + c_LinkerTimestampOffset);
var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);


var linkTimeUtc = epoch.AddSeconds(secondsSince1970);


var tz = target ?? TimeZoneInfo.Local;
var localTime = TimeZoneInfo.ConvertTimeFromUtc(linkTimeUtc, tz);


return localTime;
}

使用的例子:

var linkTimeLocal = Assembly.GetExecutingAssembly().GetLinkerTime();

注意:此方法适用于。net Core 1.0,但在。net Core 1.1之后停止工作 -它给出1900-2020范围内的随机年份。

您可以在构建过程中启动一个额外的步骤,将日期戳写入文件,然后显示该文件。

在项目属性选项卡上,查看构建事件选项卡。有一个选项可以执行构建前或构建后命令。

您可以使用项目构建后事件将文本文件写入具有当前日期时间的目标目录。然后可以在运行时读取值。这有点俗气,但应该有用。

这里没有讨论的选项是将您自己的数据插入到AssemblyInfo.cs中,“AssemblyInformationalVersion”字段似乎是合适的——我们有几个项目,我们在其中做了类似的构建步骤(然而我对这种工作方式并不完全满意,所以真的不想重现我们已经得到的东西)。

有一篇关于codeproject: http://www.codeproject.com/KB/dotnet/Customizing_csproj_files.aspx主题的文章

的方式

正如评论. xml中的@c00000fd所指出的那样。微软正在改变这一点。虽然很多人不使用他们的编译器的最新版本,但我怀疑这个变化使这种方法毫无疑问是糟糕的。虽然这是一个有趣的练习,但如果跟踪二进制文件本身的构建日期很重要,我建议人们通过任何其他必要的方法将构建日期嵌入到二进制文件中。

这可以通过一些简单的代码生成来完成,这可能是构建脚本中的第一步。事实上,ALM/Build/DevOps工具在这方面帮助很大,应该优先于其他任何工具。

我把这个答案的其余部分留在这里,仅用于历史目的。

新方式

我改变了主意,现在使用这个技巧来获得正确的构建日期。

#region Gets the build date and time (by reading the COFF header)


// http://msdn.microsoft.com/en-us/library/ms680313


struct _IMAGE_FILE_HEADER
{
public ushort Machine;
public ushort NumberOfSections;
public uint TimeDateStamp;
public uint PointerToSymbolTable;
public uint NumberOfSymbols;
public ushort SizeOfOptionalHeader;
public ushort Characteristics;
};


static DateTime GetBuildDateTime(Assembly assembly)
{
var path = assembly.GetName().CodeBase;
if (File.Exists(path))
{
var buffer = new byte[Math.Max(Marshal.SizeOf(typeof(_IMAGE_FILE_HEADER)), 4)];
using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read))
{
fileStream.Position = 0x3C;
fileStream.Read(buffer, 0, 4);
fileStream.Position = BitConverter.ToUInt32(buffer, 0); // COFF header offset
fileStream.Read(buffer, 0, 4); // "PE\0\0"
fileStream.Read(buffer, 0, buffer.Length);
}
var pinnedBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned);
try
{
var coffHeader = (_IMAGE_FILE_HEADER)Marshal.PtrToStructure(pinnedBuffer.AddrOfPinnedObject(), typeof(_IMAGE_FILE_HEADER));


return TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1) + new TimeSpan(coffHeader.TimeDateStamp * TimeSpan.TicksPerSecond));
}
finally
{
pinnedBuffer.Free();
}
}
return new DateTime();
}


#endregion

老办法

那么,如何生成构建号呢?如果你将AssemblyVersion属性更改为例如1.0.*, Visual Studio(或c#编译器)实际上会提供自动构建和修订号

将要发生的情况是,构建将等于自当地时间2000年1月1日以来的天数,而对于修订,则等于自当地时间午夜以来的秒数,除以2。

参见社区内容,自动生成和修订编号

例如AssemblyInfo.cs

[assembly: AssemblyVersion("1.0.*")] // important: use wildcard for build and revision numbers!

SampleCode.cs

var version = Assembly.GetEntryAssembly().GetName().Version;
var buildDateTime = new DateTime(2000, 1, 1).Add(new TimeSpan(
TimeSpan.TicksPerDay * version.Build + // days since 1 January 2000
TimeSpan.TicksPerSecond * 2 * version.Revision)); // seconds since midnight, (multiply by 2 to get original)

我只是一个c#新手,所以我的回答可能听起来很傻——我从可执行文件最后写入的日期开始显示构建日期:

string w_file = "MyProgram.exe";
string w_directory = Directory.GetCurrentDirectory();


DateTime c3 =  File.GetLastWriteTime(System.IO.Path.Combine(w_directory, w_file));
RTB_info.AppendText("Program created at: " + c3.ToString());

我试着用File。GetCreationTime方法,但得到了奇怪的结果:命令的日期是2012-05-29,但窗口资源管理器的日期显示为2012-05-23。在搜索这个差异后,我发现该文件可能是在2012-05-23创建的(如Windows资源管理器所示),但在2012-05-29复制到当前文件夹(如文件所示)。GetCreationTime命令)-所以为了安全起见,我使用文件。GetLastWriteTime命令。

Zalek

将以下内容添加到预构建事件命令行:

echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"
将此文件添加为资源 现在你在你的资源中有了'BuildDate'字符串

要创建资源,请参见如何在。net中创建和使用资源

对于任何需要在Windows 8 / Windows Phone 8中获得编译时间的人:

    public static async Task<DateTimeOffset?> RetrieveLinkerTimestamp(Assembly assembly)
{
var pkg = Windows.ApplicationModel.Package.Current;
if (null == pkg)
{
return null;
}


var assemblyFile = await pkg.InstalledLocation.GetFileAsync(assembly.ManifestModule.Name);
if (null == assemblyFile)
{
return null;
}


using (var stream = await assemblyFile.OpenSequentialReadAsync())
{
using (var reader = new DataReader(stream))
{
const int PeHeaderOffset = 60;
const int LinkerTimestampOffset = 8;


//read first 2048 bytes from the assembly file.
byte[] b = new byte[2048];
await reader.LoadAsync((uint)b.Length);
reader.ReadBytes(b);
reader.DetachStream();


//get the pe header offset
int i = System.BitConverter.ToInt32(b, PeHeaderOffset);


//read the linker timestamp from the PE header
int secondsSince1970 = System.BitConverter.ToInt32(b, i + LinkerTimestampOffset);


var dt = new DateTimeOffset(1970, 1, 1, 0, 0, 0, DateTimeOffset.Now.Offset) + DateTimeOffset.Now.Offset;
return dt.AddSeconds(secondsSince1970);
}
}
}

对于任何需要在Windows Phone 7中获得编译时间的人:

    public static async Task<DateTimeOffset?> RetrieveLinkerTimestampAsync(Assembly assembly)
{
const int PeHeaderOffset = 60;
const int LinkerTimestampOffset = 8;
byte[] b = new byte[2048];


try
{
var rs = Application.GetResourceStream(new Uri(assembly.ManifestModule.Name, UriKind.Relative));
using (var s = rs.Stream)
{
var asyncResult = s.BeginRead(b, 0, b.Length, null, null);
int bytesRead = await Task.Factory.FromAsync<int>(asyncResult, s.EndRead);
}
}
catch (System.IO.IOException)
{
return null;
}


int i = System.BitConverter.ToInt32(b, PeHeaderOffset);
int secondsSince1970 = System.BitConverter.ToInt32(b, i + LinkerTimestampOffset);
var dt = new DateTimeOffset(1970, 1, 1, 0, 0, 0, DateTimeOffset.Now.Offset) + DateTimeOffset.Now.Offset;
dt = dt.AddSeconds(secondsSince1970);
return dt;
}

注意:在所有情况下,你都运行在沙箱中,所以你只能获得你部署应用程序的程序集的编译时间。(也就是说,这对GAC中的任何东西都无效)。

将以下内容添加到预构建事件命令行:

echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"

添加这个文件作为资源,现在你有'BuildDate'字符串在你的资源。

在将文件插入资源(作为公共文本文件)后,我通过

string strCompTime = Properties.Resources.BuildDate;

要创建资源,请参见如何在。net中创建和使用资源

如果这是一个windows应用程序,你可以只使用应用程序可执行路径: 新System.IO.FileInfo (Application.ExecutablePath) .LastWriteTime.ToString (yyyy.MM.dd) < / p >

另一种不同的pcl友好的方法是使用MSBuild内联任务将构建时间替换为应用程序上的属性返回的字符串。我们在一个具有Xamarin的应用程序中成功地使用了这种方法。形式,Xamarin的。Android和Xamarin。iOS项目。

编辑:

通过将所有逻辑移动到SetBuildDate.targets文件中来简化,并使用Regex而不是简单的字符串替换,以便每次构建时都可以修改该文件而无需“重置”。

MSBuild内联任务定义(保存在SetBuildDate中。目标文件本地到Xamarin。本例中的表单项目):

<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' ToolsVersion="12.0">


<UsingTask TaskName="SetBuildDate" TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v12.0.dll">
<ParameterGroup>
<FilePath ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs"><![CDATA[


DateTime now = DateTime.UtcNow;
string buildDate = now.ToString("F");
string replacement = string.Format("BuildDate => \"{0}\"", buildDate);
string pattern = @"BuildDate => ""([^""]*)""";
string content = File.ReadAllText(FilePath);
System.Text.RegularExpressions.Regex rgx = new System.Text.RegularExpressions.Regex(pattern);
content = rgx.Replace(content, replacement);
File.WriteAllText(FilePath, content);
File.SetLastWriteTimeUtc(FilePath, now);


]]></Code>
</Task>
</UsingTask>


</Project>

在Xamarin中调用上述内联任务。在目标BeforeBuild中形成csproj文件:

  <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.  -->
<Import Project="SetBuildDate.targets" />
<Target Name="BeforeBuild">
<SetBuildDate FilePath="$(MSBuildProjectDirectory)\BuildMetadata.cs" />
</Target>

FilePath属性被设置为Xamarin中的BuildMetadata.cs文件。包含一个具有字符串属性BuildDate的简单类的表单项目,构建时间将被替换为:

public class BuildMetadata
{
public static string BuildDate => "This can be any arbitrary string";
}

将这个文件BuildMetadata.cs添加到项目中。它将在每次构建时被修改,但是以一种允许重复构建的方式(重复替换),因此您可以根据需要在源代码控制中包含或省略它。

有一种方法是使用T4文本模板来生成代码,这让我感到惊讶。

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System" #>
<#@ output extension=".g.cs" #>
using System;
namespace Foo.Bar
{
public static partial class Constants
{
public static DateTime CompilationTimestampUtc { get { return new DateTime(<# Write(DateTime.UtcNow.Ticks.ToString()); #>L, DateTimeKind.Utc); } }
}
}

优点:

  • Locale-independent
  • 允许的不仅仅是编译时间

缺点:

上面的方法可以通过使用内存中的文件映像(而不是从存储中重新读取它)来调整程序集已经在进程中加载:

using System;
using System.Runtime.InteropServices;
using Assembly = System.Reflection.Assembly;


static class Utils
{
public static DateTime GetLinkerDateTime(this Assembly assembly, TimeZoneInfo tzi = null)
{
// Constants related to the Windows PE file format.
const int PE_HEADER_OFFSET = 60;
const int LINKER_TIMESTAMP_OFFSET = 8;


// Discover the base memory address where our assembly is loaded
var entryModule = assembly.ManifestModule;
var hMod = Marshal.GetHINSTANCE(entryModule);
if (hMod == IntPtr.Zero - 1) throw new Exception("Failed to get HINSTANCE.");


// Read the linker timestamp
var offset = Marshal.ReadInt32(hMod, PE_HEADER_OFFSET);
var secondsSince1970 = Marshal.ReadInt32(hMod, offset + LINKER_TIMESTAMP_OFFSET);


// Convert the timestamp to a DateTime
var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var linkTimeUtc = epoch.AddSeconds(secondsSince1970);
var dt = TimeZoneInfo.ConvertTimeFromUtc(linkTimeUtc, tzi ?? TimeZoneInfo.Local);
return dt;
}
}

我采纳了阿卜杜勒拉希姆的建议。然而,它似乎给出了一个奇怪的时间格式,还添加了一天的缩写作为构建日期的一部分;例:太阳12/24/2017 13:21:05.43。我只需要日期,所以我必须消除其余使用子字符串。

在将__abc0添加到预构建事件后,我只做了以下操作:

string strBuildDate = YourNamespace.Properties.Resources.BuildDate;
string strTrimBuildDate = strBuildDate.Substring(4).Remove(10);

好消息是,它起作用了。

关于从程序集PE头的字节中提取构建日期/版本信息的技术,Microsoft已经从Visual Studio 15.4开始更改了默认的构建参数。新的默认包含确定性编译,这使得有效的时间戳和自动递增的版本号成为过去式。时间戳字段仍然存在,但它被一个永久值填充,该值是某个东西的哈希值,而不是任何构建时间的指示。

一些详细的背景在这里

对于那些将有用的时间戳优先于确定性编译的人来说,有一种方法可以覆盖新的默认值。您可以在感兴趣的程序集的.csproj文件中包含一个标记,如下所示:

  <PropertyGroup>
...
<Deterministic>false</Deterministic>
</PropertyGroup>
< p >更新: 我赞同T4文本模板解决方案,在这里的另一个答案中描述。我使用它干净地解决了我的问题,同时又不失确定性编译的好处。需要注意的是,Visual Studio只在保存.tt文件时运行T4编译器,而不是在构建时运行。如果您从源代码控制中排除了.cs结果(因为您希望生成它),而另一个开发人员签出了代码,这可能会很尴尬。如果不重新保存,他们就不会有。cs文件。在nuget上有一个包(我想叫AutoT4),它使T4编译成为每次构建的一部分。在生产部署期间,我还没有遇到过这个问题的解决方案,但我希望有类似的解决方案。< / p >

在2018年,上面的一些解决方案不再工作,或者不能与。net Core一起工作。

我使用下面的方法,它很简单,适用于我的。net Core 2.0项目。

将以下内容添加到PropertyGroup中的.csproj中:

    <Today>$([System.DateTime]::Now)</Today>

这定义了一个PropertyFunction,你可以在你的预构建命令中访问它。

您的预构建看起来像这样

echo $(today) > $(ProjectDir)BuildTimeStamp.txt

将BuildTimeStamp.txt的属性设置为嵌入式资源。

现在你可以像这样读时间戳

public static class BuildTimeStamp
{
public static string GetTimestamp()
{
var assembly = Assembly.GetEntryAssembly();


var stream = assembly.GetManifestResourceStream("NamespaceGoesHere.BuildTimeStamp.txt");


using (var reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
}
}

约翰对“新方法”的回答有一个小小的更新。

在使用ASP时,您需要构建路径,而不是使用CodeBase字符串。NET/MVC

    var codeBase = assembly.GetName().CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);

我需要一个通用的解决方案,可以在任何平台(iOS、Android和Windows)上与NETStandard项目一起工作。为了实现这一点,我决定通过PowerShell脚本自动生成CS文件。下面是PowerShell脚本:

param($outputFile="BuildDate.cs")


$buildDate = Get-Date -date (Get-Date).ToUniversalTime() -Format o
$class =
"using System;
using System.Globalization;


namespace MyNamespace
{
public static class BuildDate
{
public const string BuildDateString = `"$buildDate`";
public static readonly DateTime BuildDateUtc = DateTime.Parse(BuildDateString, null, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
}
}"


Set-Content -Path $outputFile -Value $class

将PowerScript文件保存为GenBuildDate。Ps1,并添加到你的项目。最后,添加以下行到您的预构建事件:

powershell -File $(ProjectDir)GenBuildDate.ps1 -outputFile $(ProjectDir)BuildDate.cs

确保项目中包含BuildDate.cs。在任何操作系统上都像冠军一样!

对于. net Core项目,我修改了Postlagerkarte的答案,用构建日期更新程序集版权字段。

直接编辑csproj

以下内容可以直接添加到csproj中的第一个PropertyGroup:

<Copyright>Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))</Copyright>

替代方案:Visual Studio项目属性

或者直接将内部表达式粘贴到Visual Studio中项目属性Package部分的Copyright字段中:

Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))

这可能有点令人困惑,因为Visual Studio将计算表达式并在窗口中显示当前值,但它也将在幕后适当地更新项目文件。

通过Directory.Build.props实现解决方案范围

你可以将上面的<Copyright>元素放入解决方案根目录下的Directory.Build.props文件中,并让它自动应用于该目录中的所有项目,假设每个项目不提供自己的版权值。

<Project>
<PropertyGroup>
<Copyright>Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))</Copyright>
</PropertyGroup>
</Project>

Directory.Build.props: 自定义构建

输出

示例表达式将为您提供如下版权:

Copyright © 2018 Travis Troyer (2018-05-30T14:46:23)

检索

你可以在Windows中从文件属性中查看版权信息,或者在运行时获取它:

var version = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location);


Console.WriteLine(version.LegalCopyright);

这里有很多很棒的答案,但我觉得我可以添加我自己的,因为简单,性能(与资源相关的解决方案相比)跨平台(也适用于Net Core)和避免任何第三方工具。只需将msbuild目标添加到csproj。

<Target Name="Date" BeforeTargets="BeforeBuild">
<WriteLinesToFile File="$(IntermediateOutputPath)gen.cs" Lines="static partial class Builtin { public static long CompileTime = $([System.DateTime]::UtcNow.Ticks) %3B }" Overwrite="true" />
<ItemGroup>
<Compile Include="$(IntermediateOutputPath)gen.cs" />
</ItemGroup>
</Target>

现在你在这个项目中有Builtin.CompileTime,例如:

var compileTime = new DateTime(Builtin.CompileTime, DateTimeKind.Utc);

ReSharper不会喜欢的。你可以忽略他,也可以向项目中添加一个partial类,但无论如何都可以工作。

UPD:现在ReSharper在选项的第一页有一个选项:“MSBuild访问”,“每次编译后从MSBuild获取数据”。这有助于生成代码的可见性。

你可以使用这个项目:https://github.com/dwcullop/BuildInfo

它利用T4来自动化构建日期时间戳。有几个版本(不同的分支),包括一个给你当前签出分支的Git哈希,如果你喜欢那类东西的话。

披露:模块是我写的。

我只会:

File.GetCreationTime(GetType().Assembly.Location)

可能是

Assembly execAssembly = Assembly.GetExecutingAssembly();
var creationTime = new FileInfo(execAssembly.Location).CreationTime;
// "2019-09-08T14:29:12.2286642-04:00"

我刚刚添加了预构建事件命令:

powershell -Command Get-Date -Format 'yyyy-MM-ddTHH:mm:sszzz' > Resources\BuildDateTime.txt

在项目属性中生成一个易于从代码中读取的资源文件。

我的项目是一个。net Core 2.1 web应用程序,在使用建议的解决方案时遇到了困难。我结合了上面的各种建议并进行了简化,并将日期转换为我所需的格式。

echo命令:

echo Build %DATE:~-4%/%DATE:~-10,2%/%DATE:~-7,2% %time% > "$(ProjectDir)\BuildDate.txt"

代码:

Logger.Info(File.ReadAllText(@"./BuildDate.txt").Trim());

这似乎很有效。输出:

2021-03-25 18:41:40,877 [1] INFO Config - Build 2021/03/25 18:41:37.58

没什么特别的,我只是结合了这里的建议和其他相关问题,进行了简化。

Visual Studio 2019的完整解决方案,就像我几年前开始时希望找到的那样。

添加一个文本资源文件

访问项目的属性:从解决方案资源管理器中,选择项目,然后右键单击->,或Alt+Enter。在“资源”选项卡中选择“文件”(Ctrl+5)。然后添加资源/添加新的文本文件。在弹出的消息中,输入资源的名称,例如BuildDate:这将在你的项目/资源文件夹中创建一个新的文本文件BuildDate.txt,包括它作为项目文件,并将其注册为资源,然后可以通过c#中的Properties.Resources或VB中的My.Resources访问它。

每次构建时自动更新资源文件

现在,您可以告诉Visual Studio在每次构建或重新构建项目时将日期写入该文件。为此,转到“项目属性”的“编译”选项卡,选择“生成事件”,并将以下内容复制/粘贴到“预生成事件命令行”中。文本框:

powershell -Command "((Get-Date).ToUniversalTime()).ToString(\"s\") | Out-File '$(ProjectDir)Resources\BuildDate.txt'"

这一行将定位BuildDate.txt并在ISO8601格式下写入today/NowUtc的日期和时间,例如2021-09-07T16:08:35

通过读取文件在运行时获得构建日期

然后你可以在运行时通过下面的助手(c#)从你的代码中检索这个日期:

DateTime CurrentBuildDate = DateTime.Parse(Properties.Resources.BuildDate, null, System.Globalization.DateTimeStyles.RoundtripKind);

学分

对于。net 5,我已经成功地使用了这种方法。(发现在这里)。

把这个添加到.csproj文件中:

<SourceRevisionId>build$([System.DateTime]::UtcNow.ToString("yyyyMMddHHmmss"))</SourceRevisionId>

获取构建日期的方法:

private static DateTime GetBuildDate(Assembly assembly)
{
const string BuildVersionMetadataPrefix = "+build";


var attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
if (attribute?.InformationalVersion != null)
{
var value = attribute.InformationalVersion;
var index = value.IndexOf(BuildVersionMetadataPrefix);
if (index > 0)
{
value = value.Substring(index + BuildVersionMetadataPrefix.Length);
if (DateTime.TryParseExact(value, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var result))
{
return result;
}
}
}


return default;
}

用法:

 var buildTime = GetBuildDate(Assembly.GetExecutingAssembly());
buildTime = buildTime.ToLocalTime();

如果将程序集复制到另一个位置,GetLastWriteTime不会更改。

public static class AssemblyExtensions
{
public static DateTime GetLinkerTime(this Assembly assembly)
{
return File.GetLastWriteTime(assembly.Location).ToLocalTime();
}
}

对于. net Core(。NET 5+),可以这样做。它的优点在于不需要添加或嵌入文件,没有T4,也没有预构建脚本。

在你的项目中添加这样一个类:

namespace SuperDuper
{
[AttributeUsage(AttributeTargets.Assembly)]
public class BuildDateTimeAttribute : Attribute
{
public string Date { get; set; }
public BuildDateTimeAttribute(string date)
{
Date = date;
}
}
}

更新你的项目的.csproj以包括如下内容:

<ItemGroup>
<AssemblyAttribute Include="SuperDuper.BuildDateTime">
<_Parameter1>$([System.DateTime]::Now.ToString("s"))</_Parameter1>
</AssemblyAttribute>
</ItemGroup>

注意,_Parameter1是一个神奇的名字——它意味着BuildDateTime属性类构造函数的第一个(也是唯一一个)参数。

这就是在程序集中记录构建日期时间所需的全部内容。

然后读取程序集的构建日期时间,执行如下操作:

private static DateTime? getAssemblyBuildDateTime()
{
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
var attr = Attribute.GetCustomAttribute(assembly, typeof(BuildDateTimeAttribute)) as BuildDateTimeAttribute;
if (DateTime.TryParse(attr?.Date, out DateTime dt))
return dt;
else
return null;
}

注意(根据注释中的Flydog57),如果你的.csproj中列出了属性GenerateAssemblyInfo并设置为,构建将不会生成程序集信息,并且你将不会在程序集中获得BuildDateTime信息。因此,要么不要在你的.csproj中提到GenerateAssemblyInfo(这是新项目的默认行为,如果没有特别设置为GenerateAssemblyInfo默认为真正的),要么显式地将其设置为真正的