diff --git a/UnitsNet.Xp.SrcGen/Directory.Build.props b/UnitsNet.Xp.SrcGen/Directory.Build.props
new file mode 100644
index 0000000000..44ac0bb5cd
--- /dev/null
+++ b/UnitsNet.Xp.SrcGen/Directory.Build.props
@@ -0,0 +1,3 @@
+
+
+
diff --git a/UnitsNet.Xp.SrcGen/Directory.Packages.props b/UnitsNet.Xp.SrcGen/Directory.Packages.props
new file mode 100644
index 0000000000..e1b79cd0b3
--- /dev/null
+++ b/UnitsNet.Xp.SrcGen/Directory.Packages.props
@@ -0,0 +1,6 @@
+
+
+
+ false
+
+
\ No newline at end of file
diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.sln b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.sln
new file mode 100644
index 0000000000..75e39acb0e
--- /dev/null
+++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.sln
@@ -0,0 +1,34 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitsNet.Xp.SrcGenDemo", "UnitsNet.Xp.SrcGenDemo\UnitsNet.Xp.SrcGenDemo.csproj", "{B30706F0-AA00-41BD-84AA-6E2A96DCAEB5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitsNet.Xp.SrcGen", "UnitsNet.Xp.SrcGen\UnitsNet.Xp.SrcGen.csproj", "{D635D0D1-D745-43A7-A04A-75995801753E}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Files", "_Files", "{8775A1CE-645B-45F2-82B2-1C94D56FA134}"
+ ProjectSection(SolutionItems) = preProject
+ Directory.Build.props = Directory.Build.props
+ Directory.Packages.props = Directory.Packages.props
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitsNet.Xp.SrcGenDummySource", "UnitsNet.Xp.SrcGenDummySource\UnitsNet.Xp.SrcGenDummySource.csproj", "{8DBEADB1-CDEF-4841-9AAA-C4D36C6C541A}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {B30706F0-AA00-41BD-84AA-6E2A96DCAEB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B30706F0-AA00-41BD-84AA-6E2A96DCAEB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B30706F0-AA00-41BD-84AA-6E2A96DCAEB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B30706F0-AA00-41BD-84AA-6E2A96DCAEB5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D635D0D1-D745-43A7-A04A-75995801753E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D635D0D1-D745-43A7-A04A-75995801753E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D635D0D1-D745-43A7-A04A-75995801753E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D635D0D1-D745-43A7-A04A-75995801753E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8DBEADB1-CDEF-4841-9AAA-C4D36C6C541A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8DBEADB1-CDEF-4841-9AAA-C4D36C6C541A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8DBEADB1-CDEF-4841-9AAA-C4D36C6C541A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8DBEADB1-CDEF-4841-9AAA-C4D36C6C541A}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen/IsExternalInit.cs b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen/IsExternalInit.cs
new file mode 100644
index 0000000000..c270d04a19
--- /dev/null
+++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen/IsExternalInit.cs
@@ -0,0 +1,10 @@
+// using System.ComponentModel;
+//
+// // ReSharper disable once CheckNamespace
+// namespace System.Runtime.CompilerServices;
+//
+// ///
+// /// Workaround for netstandard2.0 and record types and init properties: https://stackoverflow.com/a/62656145/134761
+// ///
+// [EditorBrowsable(EditorBrowsableState.Never)]
+// internal class IsExternalInit{}
diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen/QuantitySourceGenerator.cs b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen/QuantitySourceGenerator.cs
new file mode 100644
index 0000000000..6c4d8c18ff
--- /dev/null
+++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen/QuantitySourceGenerator.cs
@@ -0,0 +1,134 @@
+// Licensed under MIT No Attribution, see LICENSE file at the root.
+// Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet.
+
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace UnitsNet.Xp.SrcGen
+{
+ [Generator]
+ public class QuantitySourceGenerator : ISourceGenerator
+ {
+ private const string SrcQuantitySource =
+ """
+ namespace UnitsNetSrcGen
+ {
+ public interface ISrcQuantity where TUnitEnum : System.Enum
+ {
+ double Value { get; }
+ TUnitEnum Unit { get; }
+ }
+ }
+ """;
+
+ private const string LengthSource = """
+ namespace UnitsNetSrcGen
+ {
+ public enum LengthUnit
+ {
+ Centimeter,
+ Meter,
+ }
+
+ public struct Length : ISrcQuantity
+ {
+ public Length(double value, LengthUnit unit)
+ {
+ Value = value;
+ Unit = unit;
+ }
+
+ public required double Value { get; init; }
+ public required LengthUnit Unit { get; init; }
+ }
+ }
+ """;
+
+ private const string MassSource = """
+ namespace UnitsNetSrcGen
+ {
+ public enum MassUnit
+ {
+ Gram,
+ Kilogram,
+ }
+
+ public struct Mass : ISrcQuantity
+ {
+ public Mass(double value, MassUnit unit)
+ {
+ Value = value;
+ Unit = unit;
+ }
+
+ public required double Value { get; init; }
+ public required MassUnit Unit { get; init; }
+ }
+ }
+ """;
+
+ private const string AttributeSource =
+ """
+ [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = false)]
+ internal sealed class UnitsNetSrcGenInitAttribute : System.Attribute
+ {
+ public string[] QuantityNames { get; }
+
+ public UnitsNetSrcGenInitAttribute(string quantityNames)
+ => (QuantityNames) = (quantityNames.Split(',').Select(str => str.Trim()).ToArray());
+ }
+ """;
+
+ public void Execute(GeneratorExecutionContext context)
+ {
+ SyntaxReceiver rx = (SyntaxReceiver)context.SyntaxContextReceiver!;
+ foreach (string quantityName in rx.QuantityNames)
+ {
+ string source = quantityName switch
+ {
+ "Length" => LengthSource,
+ "Mass" => MassSource,
+ _ => "",
+ };
+
+ if (source != "")
+ {
+ context.AddSource($"UnitsNetSrc_{quantityName}.g.cs", source);
+ }
+ }
+ }
+
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ context.RegisterForPostInitialization((pi) =>
+ {
+ pi.AddSource("UnitsNetSrcGenInit.g.cs", AttributeSource);
+ pi.AddSource("UnitsNetSrc_ISrcQuantity.g.cs", SrcQuantitySource);
+ });
+ context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
+ }
+
+ private class SyntaxReceiver : ISyntaxContextReceiver
+ {
+ public List QuantityNames = new();
+
+ public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
+ {
+ // find all valid mustache attributes
+ if (context.Node is AttributeSyntax attrib
+ && context.SemanticModel.GetTypeInfo(attrib).Type?.ToDisplayString() == "UnitsNetSrcGenInitAttribute")
+ {
+ // string[] quantityNames = (string[])context.SemanticModel.GetConstantValue(attrib.ArgumentList.Arguments[0].Expression).Value;
+ IList quantityNames = ((string)context.SemanticModel.GetConstantValue(attrib.ArgumentList!.Arguments[0].Expression).Value).Split(',').Select(str => str.Trim()).ToList();
+
+ // string template = context.SemanticModel.GetConstantValue(attrib.ArgumentList.Arguments[1].Expression).ToString();
+ // string hash = context.SemanticModel.GetConstantValue(attrib.ArgumentList.Arguments[2].Expression).ToString();
+
+ QuantityNames.AddRange(quantityNames);
+ }
+ }
+ }
+ }
+}
diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.csproj b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.csproj
new file mode 100644
index 0000000000..f1f2564bba
--- /dev/null
+++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.csproj
@@ -0,0 +1,18 @@
+
+
+
+ netstandard2.0
+ latest
+
+
+ true
+ latest
+
+
+
+
+
+
+
+
+
diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrcGenInit.g.cs b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrcGenInit.g.cs
new file mode 100644
index 0000000000..231cd9edb0
--- /dev/null
+++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrcGenInit.g.cs
@@ -0,0 +1,8 @@
+[System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = false)]
+internal sealed class UnitsNetSrcGenInitAttribute : System.Attribute
+{
+ public string[] QuantityNames { get; }
+
+ public UnitsNetSrcGenInitAttribute(string quantityNames)
+ => (QuantityNames) = (quantityNames.Split(',').Select(str => str.Trim()).ToArray());
+}
\ No newline at end of file
diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrc_ISrcQuantity.g.cs b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrc_ISrcQuantity.g.cs
new file mode 100644
index 0000000000..3cba4bee12
--- /dev/null
+++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrc_ISrcQuantity.g.cs
@@ -0,0 +1,8 @@
+namespace UnitsNetSrcGen
+{
+ public interface ISrcQuantity where TUnitEnum : System.Enum
+ {
+ double Value { get; }
+ TUnitEnum Unit { get; }
+ }
+}
\ No newline at end of file
diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrc_Length.g.cs b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrc_Length.g.cs
new file mode 100644
index 0000000000..4de5fd644b
--- /dev/null
+++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrc_Length.g.cs
@@ -0,0 +1,20 @@
+namespace UnitsNetSrcGen
+{
+ public enum LengthUnit
+ {
+ Centimeter,
+ Meter,
+ }
+
+ public struct Length : ISrcQuantity
+ {
+ public Length(double value, LengthUnit unit)
+ {
+ Value = value;
+ Unit = unit;
+ }
+
+ public required double Value { get; init; }
+ public required LengthUnit Unit { get; init; }
+ }
+ }
\ No newline at end of file
diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrc_Mass.g.cs b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrc_Mass.g.cs
new file mode 100644
index 0000000000..820342ff9a
--- /dev/null
+++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrc_Mass.g.cs
@@ -0,0 +1,20 @@
+namespace UnitsNetSrcGen
+{
+ public enum MassUnit
+ {
+ Gram,
+ Kilogram,
+ }
+
+ public struct Mass : ISrcQuantity
+ {
+ public Mass(double value, MassUnit unit)
+ {
+ Value = value;
+ Unit = unit;
+ }
+
+ public required double Value { get; init; }
+ public required MassUnit Unit { get; init; }
+ }
+}
\ No newline at end of file
diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Program.Attributes.cs b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Program.Attributes.cs
new file mode 100644
index 0000000000..c39ede56ef
--- /dev/null
+++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Program.Attributes.cs
@@ -0,0 +1 @@
+[assembly: UnitsNetSrcGenInit("Length,Mass")]
diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Program.cs b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Program.cs
new file mode 100644
index 0000000000..1d9d8440ce
--- /dev/null
+++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Program.cs
@@ -0,0 +1,7 @@
+using UnitsNetSrcGen;
+
+var l = new Length {Value = 10, Unit = LengthUnit.Centimeter};
+var m = new Mass { Value = 20, Unit = MassUnit.Kilogram };
+
+Console.WriteLine($"Length: {l.Value} {l.Unit}");
+Console.WriteLine($"Mass: {m.Value} {m.Unit}");
diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/UnitsNet.Xp.SrcGenDemo.csproj b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/UnitsNet.Xp.SrcGenDemo.csproj
new file mode 100644
index 0000000000..a5ea82266a
--- /dev/null
+++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/UnitsNet.Xp.SrcGenDemo.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+ true
+
+ Generated
+
+ $(GeneratedFolder)\$(TargetFramework)
+
+
+
+
+
+
+
diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDummySource/DummySources.cs b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDummySource/DummySources.cs
new file mode 100644
index 0000000000..b3efd85bd9
--- /dev/null
+++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDummySource/DummySources.cs
@@ -0,0 +1,52 @@
+
+/*
+ * Dummy sources for copy & paste into c# source generator strings in UnitsNet.Xp.SrcGen/QuantitySourceGenerator.cs.
+ */
+// ReSharper disable once CheckNamespace
+namespace UnitsNet.Xp.SrcGen;
+
+///
+/// Represents a quantity.
+///
+public interface ISrcQuantity where TUnitEnum : System.Enum
+{
+ double Value { get; }
+ TUnitEnum Unit { get; }
+}
+
+public enum LengthUnit
+{
+ Centimeter,
+ Meter,
+}
+
+public struct Length : ISrcQuantity
+{
+ public Length(double value, LengthUnit unit)
+ {
+ Value = value;
+ Unit = unit;
+ }
+
+ public required double Value { get; init; }
+ public required LengthUnit Unit { get; init; }
+}
+
+public enum MassUnit
+{
+ Gram,
+ Kilogram,
+}
+
+public struct Mass : ISrcQuantity
+{
+ public Mass(double value, MassUnit unit)
+ {
+ Value = value;
+ Unit = unit;
+ }
+
+ public required double Value { get; init; }
+ public required MassUnit Unit { get; init; }
+}
+
diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDummySource/UnitsNet.Xp.SrcGenDummySource.csproj b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDummySource/UnitsNet.Xp.SrcGenDummySource.csproj
new file mode 100644
index 0000000000..997e50b032
--- /dev/null
+++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDummySource/UnitsNet.Xp.SrcGenDummySource.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net8.0
+ disable
+ enable
+
+
+