📖 English | 简体中文
XFEExtension.NetCore.AutoConfig is a .NET library powered by Roslyn incremental source generators. It automatically generates static properties, load/save methods, and persistence logic for any partial class that inherits from XFEProfile, eliminating the need to write boilerplate configuration code.
dotnet add package XFEExtension.NetCore.AutoConfigAnnotate fields with [ProfileProperty]. The source generator will create a corresponding static property that automatically saves whenever it is assigned:
// Define a profile class[AutoLoadProfile]partialclassSystemProfile:XFEProfile{[ProfileProperty]stringname=string.Empty;[ProfileProperty]int_age;}// Use the profileclassProgram{staticvoidMain(string[]args){SystemProfile.Name="Test";// Automatically saved on assignmentConsole.WriteLine(SystemProfile.Name);Console.WriteLine(SystemProfile.Age);// Restored from disk on next run}}Note: The
[AutoLoadProfile]attribute instructs the framework to callLoadProfile()inside the static constructor, so the configuration is restored automatically when the program starts.Automatic saves are coalesced over a 100 ms window by default. A burst of assignments therefore produces one complete file write instead of one write per property. Call
SaveProfile()when the current snapshot must be flushed immediately.
Generated properties, ProfileList<T>, and ProfileDictionary<TKey, TValue> are synchronized for concurrent access. Automatic save requests are handled by one writer per profile and coalesced using AutoSaveDelay. Files are committed through a temporary file and atomic replacement, so readers never observe a partially written configuration.
The coalescing window can be customized in the profile constructor:
publicSystemProfile(){AutoSaveDelay=TimeSpan.FromMilliseconds(500);}Current.LastSaveException exposes the most recent background save failure and is cleared after a successful save. Explicit SaveProfile() calls remain synchronous and surface write failures directly.
Set DefaultProfileOperationMode inside the instance constructor to switch the storage format. The file extension is updated automatically:
[AutoLoadProfile]partialclassSystemProfile:XFEProfile{[ProfileProperty]stringname=string.Empty;[ProfileProperty]int_age;publicSystemProfile(){DefaultProfileOperationMode=ProfileOperationMode.Xml;// Switch to XML; extension becomes .xml// Available modes: XFEDictionary (default), Json, Xml, Custom}}Use the generated static properties ProfilePath and ProfileExtension to control where the file is stored:
[AutoLoadProfile]partialclassSystemProfile:XFEProfile{[ProfileProperty]stringname=string.Empty;[ProfileProperty]int_age;publicSystemProfile(){ProfilePath=$"MyPath/MySubPath/{nameof(SystemProfile)}";// Path without extensionProfileExtension=".ini";// Custom file extension}}
ProfilePathandProfileExtensionare generated static properties and can also be set from outside the class:SystemProfile.ProfilePath="custom/path/SystemProfile";SystemProfile.ProfileExtension=".cfg";
[AutoLoadProfile][ProfilePath("MyPath/MySubPath/SystemProfile")]partialclassSystemProfile:XFEProfile{[ProfileProperty]stringname=string.Empty;[ProfileProperty]int_age;}Set DefaultProfileOperationMode to Custom and provide your own load/save delegates:
[AutoLoadProfile]partialclassSystemProfile:XFEProfile{[ProfileProperty]stringname=string.Empty;[ProfileProperty]int_age;publicSystemProfile(){DefaultProfileOperationMode=ProfileOperationMode.Custom;ProfilePath=$"MyPath/MySubPath/{nameof(SystemProfile)}";ProfileExtension=".ini";LoadOperation=MyCustomLoadProfileOperation;SaveOperation=MyCustomSaveProfileOperation;}// Custom load methodpublicstaticXFEProfile?MyCustomLoadProfileOperation(XFEProfileprofileInstance,stringprofileString,Dictionary<string,Type>propertyInfoDictionary,Dictionary<string,SetValueDelegate>propertySetFuncDictionary){// Implement custom load logic herereturnnull;}// Custom save methodpublicstaticstringMyCustomSaveProfileOperation(XFEProfileprofileInstance,Dictionary<string,Type>propertyInfoDictionary,Dictionary<string,GetValueDelegate>propertyGetFuncDictionary){// Implement custom save logic herereturnstring.Empty;}}ProfileList<T> and ProfileDictionary<TKey, TValue> request a coalesced automatic save whenever the collection is modified (add, remove, clear, index assignment, etc.). Their public operations and enumeration snapshots are safe to use concurrently:
[AutoLoadProfile]partialclassSystemProfile:XFEProfile{[ProfileProperty][ProfilePropertyAddGet("Current.nameList.CurrentProfile = Current")][ProfilePropertyAddGet("return Current.nameList")]ProfileList<string>nameList=[];[ProfileProperty][ProfilePropertyAddGet("Current.nameIdDictionary.CurrentProfile = Current")][ProfilePropertyAddGet("return Current.nameIdDictionary")]ProfileDictionary<string,long>nameIdDictionary=[];}classProgram{staticvoidMain(string[]args){SystemProfile.NameList.Add("Alice");// Auto-saved on addSystemProfile.NameList.AddRange(["Bob","Carol"]);// Batch addSystemProfile.NameList.Remove("Bob");// Auto-saved on removeSystemProfile.NameIdDictionary.Add("Alice",100L);// Dictionary works the same way}}Use [ProfilePropertyAddGet] and [ProfilePropertyAddSet] to insert code snippets directly into the generated property accessors:
[AutoLoadProfile]partialclassSystemProfile:XFEProfile{[ProfileProperty][ProfilePropertyAddGet(@"Console.WriteLine(""Getting Name"")")][ProfilePropertyAddGet("return Current.name")][ProfilePropertyAddSet(@"Console.WriteLine(""Setting Name"")")][ProfilePropertyAddSet("Current.name = value")]stringname=string.Empty;[ProfileProperty][ProfilePropertyAddGet(@"Console.WriteLine(""Getting Age"")")][ProfilePropertyAddGet("return Current._age")][ProfilePropertyAddSet(@"Console.WriteLine(""Setting Age"")")][ProfilePropertyAddSet("Current._age = value")]int_age;}Note: When using
[ProfilePropertyAddGet], you must handle the fullreturnstatement yourself in the lastgetsnippet.
The generator creates static partial void GetXxxProperty() and static partial void SetXxxProperty(ref T value) for each property. Implement them in your own partial class to intercept reads and writes:
[AutoLoadProfile]partialclassSystemProfile:XFEProfile{[ProfileProperty]stringname=string.Empty;[ProfileProperty]int_age;staticpartialvoidGetNameProperty(){Console.WriteLine("Name was read");}staticpartialvoidSetNameProperty(refstringvalue){Console.WriteLine($"Name changing: {Name} -> {value}");}staticpartialvoidGetAgeProperty(){Console.WriteLine("Age was read");}staticpartialvoidSetAgeProperty(refintvalue){value=1999;// Modify the value before it is storedConsole.WriteLine($"Age forced to 1999");}}Assign values directly at the field declaration site:
[AutoLoadProfile]partialclassSystemProfile:XFEProfile{[ProfileProperty]stringname="John Wick";[ProfileProperty]int_age=59;}XML doc comments placed on a field are automatically propagated to the generated static property:
[AutoLoadProfile]partialclassSystemProfile:XFEProfile{/// <summary>/// The user's name. This comment is copied to the generated Name property./// </summary>[ProfileProperty]stringname=string.Empty;[ProfileProperty]int_age;}The following static methods are generated for every profile class:
SystemProfile.LoadProfile();// Load from fileSystemProfile.SaveProfile();// Save to fileSystemProfile.DeleteProfile();// Delete the config filestringexported=SystemProfile.ExportProfile();// Export config as a stringSystemProfile.ImportProfile(exported);// Import config from a string| Attribute | Target | Description |
|---|---|---|
[ProfileProperty] | Field | Marks the field for code generation. Optionally specify a property name: [ProfileProperty("CustomName")] |
[ProfilePropertyAddGet(code)] | Field | Appends a code line to the generated get accessor. Supports multiple attributes. |
[ProfilePropertyAddSet(code)] | Field | Appends a code line to the generated set accessor. Supports multiple attributes. |
[AutoLoadProfile] | Class | Calls LoadProfile() automatically in the static constructor. |
[ProfilePath(path)] | Class | Sets the storage path for the config file. |
| Value | Extension | Description |
|---|---|---|
XFEDictionary (default) | .xpf | XFE dictionary format |
Json | .json | JSON serialization |
Xml | .xml | XML serialization |
Custom | custom | User-provided load/save delegates |
For every partial class that inherits XFEProfile and uses [ProfileProperty], the source generator produces:
| Member | Kind | Description |
|---|---|---|
Current | static T | The singleton profile instance |
ProfilePath | static string | Storage path (without extension) |
ProfileExtension | static string | File extension (auto-detected when empty) |
LoadProfile() | static void | Loads config from file |
SaveProfile() | static void | Immediately saves config to file and waits for completion |
DeleteProfile() | static void | Deletes the config file |
ExportProfile() | static string | Exports config as a string |
ImportProfile(string) | static void | Imports config from a string |
XxxProperty (per field) | static T | Auto-generated static property; saves on set |
InstanceXxx (per field) | T (instance) | Corresponding instance property |
GetXxxProperty() | static partial void | Invoked when the property is read |
SetXxxProperty(ref T) | static partial void | Invoked when the property is written |
| Member | Kind | Description |
|---|---|---|
DefaultProfileOperationMode | ProfileOperationMode | Load/save mode |
LoadOperation | ProfileLoadOperation | Custom load delegate |
SaveOperation | ProfileSaveOperation | Custom save delegate |
ProfilesDefaultPath | static string | Default root directory for all profile files |
AutoSaveDelay | TimeSpan | Coalescing window for automatic saves (100 ms by default) |
LastSaveException | Exception? | Most recent background save failure |
This project is licensed under the MIT License.