| Framework | .NET 10 | .NET 9 | .NET 8 | .NET Standard / 4.6.2+ |
|---|---|---|---|---|
| Supported | ✅ | ✅ | ✅ | ✅ |
A Roslyn incremental source generator that embeds build-time metadata - timestamp, date and time components - as compile-time constants directly into your assembly. Zero runtime overhead. No reflection. No configuration required.
- Why AssemblyMetadata?
- Installation
- Quick Start
- API Reference
- Usage Examples
- Target Frameworks
- License
Knowing when an assembly was built is useful for diagnostics, "About" screens, deployment validation, and telemetry. The traditional approaches all have trade-offs:
| Approach | Runtime cost | Dependency | Works with AOT? |
|---|---|---|---|
Read AssemblyInformationalVersion attribute |
Reflection at runtime | None | |
| Embed a resource file with the date | Resource deserialization | Build task | |
| AssemblyMetadata (this package) | Zero - values are const |
None (analyzer only) | ✅ Yes |
AssemblyMetadata solves this differently:
- Compile-time constants - values are
const, so the JIT can inline and dead-code-eliminate them - Zero-cost access - reading the timestamp costs nothing beyond a register load
- No dependencies at runtime - the NuGet package ships as a Roslyn source generator; nothing is added to your runtime dependency graph
- NativeAOT-compatible -
constfields have no reflection or dynamic dispatch - Incremental generator - uses the modern Roslyn
IIncrementalGeneratorAPI, so the generator only re-runs when the compilation changes, keeping build times fast
Add the package to any project that needs build metadata:
<PackageReference Include="AssemblyMetadata"
Version="LATEST_VERSION"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
OutputItemType="Analyzer"andReferenceOutputAssembly="false"are required. They instruct MSBuild to load the package as a Roslyn source generator (not a regular assembly reference), producing zero runtime dependencies.
After adding the package, the generated class AssemblyMetadataInfo is immediately available
anywhere in your project under the BenjaminAbt.AssemblyMetadata namespace:
using BenjaminAbt.AssemblyMetadata;
// ISO 8601 UTC timestamp of the build
string timestamp = AssemblyMetadataInfo.BuildInfo.BuildTimestamp;
// → "2026-03-02T14:35:07.1234567+00:00"
Console.WriteLine($"Built on {AssemblyMetadataInfo.BuildInfo.BuildDateYear}-"
+ $"{AssemblyMetadataInfo.BuildInfo.BuildDateMonth:D2}-"
+ $"{AssemblyMetadataInfo.BuildInfo.BuildDateDay:D2} "
+ $"at {AssemblyMetadataInfo.BuildInfo.BuildTimeHour:D2}:"
+ $"{AssemblyMetadataInfo.BuildInfo.BuildTimeMinute:D2}:"
+ $"{AssemblyMetadataInfo.BuildInfo.BuildTimeSecond:D2} UTC");No additional configuration, properties, or attributes are required.
The generator produces a single file (AssemblyMetadataInfo.gen.cs) in the
BenjaminAbt.AssemblyMetadata namespace. All members are public const.
| Member | Type | Description |
|---|---|---|
BuildTimestamp |
string |
Build time as a UTC ISO 8601 round-trip string ("o" format specifier) |
BuildFileTimeUtc |
long |
Build time as a Windows FileTime - 100-nanosecond intervals since 1601-01-01T00:00:00Z |
BuildDateYear |
int |
Year component of the UTC build date |
BuildDateMonth |
int |
Month component of the UTC build date (1–12) |
BuildDateDay |
int |
Day component of the UTC build date (1–31) |
BuildTimeHour |
int |
Hour component of the UTC build time (0–23) |
BuildTimeMinute |
int |
Minute component of the UTC build time (0–59) |
BuildTimeSecond |
int |
Second component of the UTC build time (0–59) |
using BenjaminAbt.AssemblyMetadata;
Console.WriteLine(AssemblyMetadataInfo.BuildInfo.BuildTimestamp);
// → 2026-03-02T14:35:07.1234567+00:00Use the "o" round-trip format specifier to parse the stored constant back into a
DateTimeOffset - the same format used by the generator:
using System;
using BenjaminAbt.AssemblyMetadata;
DateTimeOffset buildOn = DateTimeOffset.ParseExact(
AssemblyMetadataInfo.BuildInfo.BuildTimestamp, "o", null);
Console.WriteLine($"Built {(DateTimeOffset.UtcNow - buildOn).Days} days ago.");BuildFileTimeUtc lets you reconstruct a DateTimeOffset without any string parsing:
using System;
using BenjaminAbt.AssemblyMetadata;
DateTimeOffset buildOn =
DateTimeOffset.FromFileTime(AssemblyMetadataInfo.BuildInfo.BuildFileTimeUtc);This is the fastest way to get a DateTimeOffset representation of the build time.
The integer constants allow zero-allocation formatting and direct numeric comparison:
using BenjaminAbt.AssemblyMetadata;
// Compose a date string without DateTimeOffset overhead
string buildDate =
$"{AssemblyMetadataInfo.BuildInfo.BuildDateYear}-"
+ $"{AssemblyMetadataInfo.BuildInfo.BuildDateMonth:D2}-"
+ $"{AssemblyMetadataInfo.BuildInfo.BuildDateDay:D2}";
// Direct year comparison - no parsing, no allocation
if (AssemblyMetadataInfo.BuildInfo.BuildDateYear < 2025)
Console.WriteLine("Assembly was built before 2025.");using System;
using BenjaminAbt.AssemblyMetadata;
TimeSpan age = DateTimeOffset.UtcNow
- DateTimeOffset.FromFileTime(AssemblyMetadataInfo.BuildInfo.BuildFileTimeUtc);
if (age.TotalDays > 30)
Console.WriteLine($"Warning: this build is {(int)age.TotalDays} days old.");Expose the build timestamp in an ASP.NET Core health or info endpoint:
using BenjaminAbt.AssemblyMetadata;
app.MapGet("/info", () => new
{
BuildTimestamp = AssemblyMetadataInfo.BuildInfo.BuildTimestamp,
BuildYear = AssemblyMetadataInfo.BuildInfo.BuildDateYear,
BuildMonth = AssemblyMetadataInfo.BuildInfo.BuildDateMonth,
BuildDay = AssemblyMetadataInfo.BuildInfo.BuildDateDay,
});Please donate - if possible - to institutions of your choice such as child cancer aid, children's hospices, etc. Thanks!
