├── .editorconfig ├── .github ├── dependabot.yaml └── workflows │ ├── build-debug.yaml │ ├── build-release.yaml │ ├── stale.yaml │ └── toc.yaml ├── .gitignore ├── LICENSE ├── README.md ├── UnitGenerator.sln ├── sandbox ├── ConsoleApp │ ├── AllPrimitives.cs │ ├── ConsoleApp.csproj │ ├── Operators.cs │ ├── Others.cs │ └── Program.cs ├── FileGenerate │ ├── FileGenerate.csproj │ └── SimplePrimitive.cs └── Generated │ └── UnitGenerator │ └── UnitGenerator.SourceGenerator │ ├── FileGenerate.A.g.cs │ ├── FileGenerate.Aa.g.cs │ ├── FileGenerate.B.g.cs │ ├── FileGenerate.Bb.g.cs │ ├── FileGenerate.C.g.cs │ ├── FileGenerate.Cc.g.cs │ ├── FileGenerate.D.g.cs │ └── UnitOfAttribute.cs ├── src ├── EntityFrameworkApp │ ├── DbContext.cs │ ├── EntityFrameworkApp.csproj │ └── Program.cs └── UnitGenerator │ ├── CodeDomShims.cs │ ├── Icon.png │ ├── IgnoreEquality.cs │ ├── Properties │ └── launchSettings.json │ ├── ReferenceSymbols.cs │ ├── SourceGenerator2.cs │ ├── UnitGenerateOptions.cs │ └── UnitGenerator.csproj └── tests ├── UnitGenerator.NET9.Tests ├── UnitGenerator.NET9.Tests.csproj └── UnitOfGuidTests.cs └── UnitGenerator.Tests ├── GenerateTest.cs ├── TestHelper.cs └── UnitGenerator.Tests.csproj /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_style = space 8 | indent_size = 2 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | # Visual Studio Spell checker configs (https://learn.microsoft.com/en-us/visualstudio/ide/text-spell-checker?view=vs-2022#how-to-customize-the-spell-checker) 13 | spelling_exclusion_path = ./exclusion.dic 14 | 15 | [*.cs] 16 | indent_size = 4 17 | charset = utf-8-bom 18 | end_of_line = unset 19 | 20 | # Solution files 21 | [*.{sln,slnx}] 22 | end_of_line = unset 23 | 24 | # MSBuild project files 25 | [*.{csproj,props,targets}] 26 | end_of_line = unset 27 | 28 | # Xml config files 29 | [*.{ruleset,config,nuspec,resx,runsettings,DotSettings}] 30 | end_of_line = unset 31 | 32 | [*{_AssemblyInfo.cs,.notsupported.cs}] 33 | generated_code = true 34 | 35 | # C# code style settings 36 | [*.{cs}] 37 | dotnet_diagnostic.IDE0044.severity = none # IDE0044: Make field readonly 38 | 39 | # https://stackoverflow.com/questions/79195382/how-to-disable-fading-unused-methods-in-visual-studio-2022-17-12-0 40 | dotnet_diagnostic.IDE0051.severity = none # IDE0051: Remove unused private member 41 | dotnet_diagnostic.IDE0130.severity = none # IDE0130: Namespace does not match folder structure 42 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | # ref: https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot 2 | version: 2 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" # Check for updates to GitHub Actions every week 8 | ignore: 9 | # I just want update action when major/minor version is updated. patch updates are too noisy. 10 | - dependency-name: '*' 11 | update-types: 12 | - version-update:semver-patch 13 | -------------------------------------------------------------------------------- /.github/workflows/build-debug.yaml: -------------------------------------------------------------------------------- 1 | name: Build-Debug 2 | 3 | on: 4 | push: 5 | branches: 6 | - "master" 7 | pull_request: 8 | branches: 9 | - "master" 10 | 11 | jobs: 12 | build-dotnet: 13 | permissions: 14 | contents: read 15 | runs-on: ubuntu-24.04 16 | timeout-minutes: 10 17 | steps: 18 | - uses: Cysharp/Actions/.github/actions/checkout@main 19 | - uses: Cysharp/Actions/.github/actions/setup-dotnet@main 20 | - run: dotnet build -c Debug 21 | -------------------------------------------------------------------------------- /.github/workflows/build-release.yaml: -------------------------------------------------------------------------------- 1 | name: Build-Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | tag: 7 | description: "tag: git tag you want create. (sample 1.0.0)" 8 | required: true 9 | dry-run: 10 | description: "dry-run: true will never create release/nuget." 11 | required: true 12 | default: false 13 | type: boolean 14 | 15 | jobs: 16 | build-dotnet: 17 | permissions: 18 | contents: read 19 | runs-on: ubuntu-24.04 20 | timeout-minutes: 10 21 | steps: 22 | - uses: Cysharp/Actions/.github/actions/checkout@main 23 | - uses: Cysharp/Actions/.github/actions/setup-dotnet@main 24 | # pack nuget 25 | - run: dotnet build -c Release -p:Version=${{ inputs.tag }} 26 | - run: dotnet pack -c Release --no-build -p:Version=${{ inputs.tag }} -o ./publish 27 | - uses: Cysharp/Actions/.github/actions/upload-artifact@main 28 | with: 29 | name: nuget 30 | path: ./publish 31 | - uses: Cysharp/Actions/.github/actions/upload-artifact@main 32 | with: 33 | name: UnitGenerator 34 | path: ./src/UnitGenerator/bin/Release/netstandard2.0/UnitGenerator.dll 35 | retention-days: 1 36 | 37 | # release 38 | create-release: 39 | needs: [build-dotnet] 40 | permissions: 41 | contents: write 42 | uses: Cysharp/Actions/.github/workflows/create-release.yaml@main 43 | with: 44 | commit-id: '' 45 | dry-run: ${{ inputs.dry-run }} 46 | tag: ${{ inputs.tag }} 47 | nuget-push: true 48 | release-upload: true 49 | release-asset-path: ./UnitGenerator/UnitGenerator.dll 50 | secrets: inherit 51 | -------------------------------------------------------------------------------- /.github/workflows/stale.yaml: -------------------------------------------------------------------------------- 1 | name: "Close stale issues" 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "0 0 * * *" 7 | 8 | jobs: 9 | stale: 10 | permissions: 11 | contents: read 12 | pull-requests: write 13 | issues: write 14 | uses: Cysharp/Actions/.github/workflows/stale-issue.yaml@main 15 | -------------------------------------------------------------------------------- /.github/workflows/toc.yaml: -------------------------------------------------------------------------------- 1 | name: TOC Generator 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'README.md' 7 | 8 | jobs: 9 | toc: 10 | permissions: 11 | contents: write 12 | uses: Cysharp/Actions/.github/workflows/toc-generator.yaml@main 13 | with: 14 | TOC_TITLE: "## Table of Contents" 15 | secrets: inherit 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Rr]elease/ 19 | x64/ 20 | *_i.c 21 | *_p.c 22 | *.ilk 23 | *.obj 24 | *.pch 25 | *.pdb 26 | *.pgc 27 | *.pgd 28 | *.rsp 29 | *.sbr 30 | *.tlb 31 | *.tli 32 | *.tlh 33 | *.tmp 34 | *.log 35 | *.vspscc 36 | *.vssscc 37 | .builds 38 | 39 | # Visual C++ cache files 40 | ipch/ 41 | *.aps 42 | *.ncb 43 | *.opensdf 44 | *.sdf 45 | 46 | # Visual Studio profiler 47 | *.psess 48 | *.vsp 49 | *.vspx 50 | 51 | # Guidance Automation Toolkit 52 | *.gpState 53 | 54 | # ReSharper is a .NET coding add-in 55 | _ReSharper* 56 | 57 | # NCrunch 58 | *.ncrunch* 59 | .*crunch*.local.xml 60 | 61 | # Installshield output folder 62 | [Ee]xpress 63 | 64 | # DocProject is a documentation generator add-in 65 | DocProject/buildhelp/ 66 | DocProject/Help/*.HxT 67 | DocProject/Help/*.HxC 68 | DocProject/Help/*.hhc 69 | DocProject/Help/*.hhk 70 | DocProject/Help/*.hhp 71 | DocProject/Help/Html2 72 | DocProject/Help/html 73 | 74 | # Click-Once directory 75 | publish 76 | 77 | # Publish Web Output 78 | *.Publish.xml 79 | 80 | # NuGet Packages Directory 81 | packages 82 | 83 | # Windows Azure Build Output 84 | csx 85 | *.build.csdef 86 | 87 | # Windows Store app package directory 88 | AppPackages/ 89 | 90 | # Others 91 | [Bb]in 92 | [Oo]bj 93 | sql 94 | TestResults 95 | [Tt]est[Rr]esult* 96 | *.Cache 97 | ClientBin 98 | [Ss]tyle[Cc]op.* 99 | ~$* 100 | *.dbmdl 101 | Generated_Code #added for RIA/Silverlight projects 102 | 103 | # Backup & report files from converting an old project file to a newer 104 | # Visual Studio version. Backup files are not needed, because we have git ;-) 105 | _UpgradeReport_Files/ 106 | Backup*/ 107 | UpgradeLog*.XML 108 | .vs/config/applicationhost.config 109 | .vs/restore.dg 110 | 111 | # Unity 112 | src/MasterMemory.UnityClient/bin/* 113 | src/MasterMemory.UnityClient/Library/* 114 | src/MasterMemory.UnityClient/obj/* 115 | src/MasterMemory.UnityClient/Temp/* 116 | 117 | # OTHER 118 | nuget/tools/* 119 | *.nupkg 120 | 121 | .vs 122 | 123 | # Unity 124 | Library/ 125 | Temp/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Cysharp, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | UnitGenerator 2 | === 3 | [![GitHub Actions](https://github.com/Cysharp/UnitGenerator/workflows/Build-Debug/badge.svg)](https://github.com/Cysharp/UnitGenerator/actions) [![Releases](https://img.shields.io/github/release/Cysharp/UnitGenerator.svg)](https://github.com/Cysharp/UnitGenerator/releases) 4 | 5 | C# Source Generator to create [Value object](https://en.wikipedia.org/wiki/Value_object) pattern, also inspired by [units of measure](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/units-of-measure) to support all arithmetic operators and serialization. 6 | 7 | NuGet: [UnitGenerator](https://www.nuget.org/packages/UnitGenerator) 8 | 9 | ``` 10 | Install-Package UnitGenerator 11 | ``` 12 | 13 | Execute in Unity Game Engine is also supported, please see the [Unity](#use-for-unity) section for details. 14 | 15 | ## Introduction 16 | 17 | For example, Identifier, UserId is comparable only to UserId, and cannot be assigned to any other type. Also, arithmetic operations are not allowed. 18 | 19 | ```csharp 20 | using UnitGenerator; 21 | 22 | [UnitOf(typeof(int))] 23 | public readonly partial struct UserId; { } 24 | ``` 25 | 26 | or when using C#11 and NET7 you can use 27 | 28 | ```csharp 29 | using UnitGenerator; 30 | 31 | [UnitOf] public readonly partial struct UserId; 32 | ``` 33 | 34 | will generates 35 | 36 | ```csharp 37 | [System.ComponentModel.TypeConverter(typeof(UserIdTypeConverter))] 38 | public readonly partial struct UserId : IEquatable 39 | { 40 | readonly int value; 41 | 42 | public UserId(int value) 43 | { 44 | this.value = value; 45 | } 46 | 47 | public readonly int AsPrimitive() => value; 48 | public static explicit operator int(UserId value) => value.value; 49 | public static explicit operator UserId(int value) => new UserId(value); 50 | public bool Equals(UserId other) => value.Equals(other.value); 51 | public override bool Equals(object? obj) => // snip... 52 | public override int GetHashCode() => value.GetHashCode(); 53 | public override string ToString() => value.ToString(); 54 | public static bool operator ==(in UserId x, in UserId y) => x.value.Equals(y.value); 55 | public static bool operator !=(in UserId x, in UserId y) => !x.value.Equals(y.value); 56 | 57 | private class UserIdTypeConverter : System.ComponentModel.TypeConverter 58 | { 59 | // snip... 60 | } 61 | } 62 | ``` 63 | 64 | However, Hp in games, should not be allowed to be assigned to other types, but should support arithmetic operations with int. For example double heal = `target.Hp = Hp.Min(target.Hp * 2, target.MaxHp)`. 65 | 66 | ```csharp 67 | [UnitOf(UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator | UnitGenerateOptions.Comparable | UnitGenerateOptions.MinMaxMethod)] 68 | public readonly partial struct Hp; 69 | 70 | // -- generates 71 | 72 | [System.ComponentModel.TypeConverter(typeof(HpTypeConverter))] 73 | public readonly partial struct Hp 74 | : IEquatable 75 | #if NET7_0_OR_GREATER 76 | , IEqualityOperators 77 | #endif 78 | , IComparable 79 | #if NET7_0_OR_GREATER 80 | , IComparisonOperators 81 | #endif 82 | #if NET7_0_OR_GREATER 83 | , IAdditionOperators 84 | , ISubtractionOperators 85 | , IMultiplyOperators 86 | , IDivisionOperators 87 | , IUnaryPlusOperators 88 | , IUnaryNegationOperators 89 | , IIncrementOperators 90 | , IDecrementOperators 91 | #endif 92 | { 93 | readonly int value; 94 | 95 | public Hp(int value) 96 | { 97 | this.value = value; 98 | } 99 | 100 | public int AsPrimitive() => value; 101 | public static explicit operator int(Hp value) => value.value; 102 | public static explicit operator Hp(int value) => new Hp(value); 103 | public bool Equals(Hp other) => value.Equals(other.value); 104 | public override bool Equals(object? obj) => // snip... 105 | public override int GetHashCode() => value.GetHashCode(); 106 | public override string ToString() => value.ToString(); 107 | public static bool operator ==(in Hp x, in Hp y) => x.value.Equals(y.value); 108 | public static bool operator !=(in Hp x, in Hp y) => !x.value.Equals(y.value); 109 | private class HpTypeConverter : System.ComponentModel.TypeConverter { /* snip... */ } 110 | 111 | // UnitGenerateOptions.ArithmeticOperator 112 | public static Hp operator +(Hp x, Hp y) => new Hp(checked((int)(x.value + y.value))); 113 | public static Hp operator -(Hp x, Hp y) => new Hp(checked((int)(x.value - y.value))); 114 | public static Hp operator *(Hp x, Hp y) => new Hp(checked((int)(x.value * y.value))); 115 | public static Hp operator /(Hp x, Hp y) => new Hp(checked((int)(x.value / y.value))); 116 | public static Hp operator ++(Hp x) => new Hp(checked((int)(x.value + 1))); 117 | public static Hp operator --(Hp x) => new Hp(checked((int)(x.value - 1))); 118 | public static Hp operator +(A value) => new((int)(+value.value)); 119 | public static Hp operator -(A value) => new((int)(-value.value)); 120 | 121 | // UnitGenerateOptions.ValueArithmeticOperator 122 | public static Hp operator +(Hp x, in int y) => new Hp(checked((int)(x.value + y))); 123 | public static Hp operator -(Hp x, in int y) => new Hp(checked((int)(x.value - y))); 124 | public static Hp operator *(Hp x, in int y) => new Hp(checked((int)(x.value * y))); 125 | public static Hp operator /(Hp x, in int y) => new Hp(checked((int)(x.value / y))); 126 | 127 | // UnitGenerateOptions.Comparable 128 | public int CompareTo(Hp other) => value.CompareTo(other.value); 129 | public static bool operator >(Hp x, Hp y) => x.value > y.value; 130 | public static bool operator <(Hp x, Hp y) => x.value < y.value; 131 | public static bool operator >=(Hp x, Hp y) => x.value >= y.value; 132 | public static bool operator <=(Hp x, Hp y) => x.value <= y.value; 133 | 134 | // UnitGenerateOptions.MinMaxMethod 135 | public static Hp Min(Hp x, Hp y) => new Hp(Math.Min(x.value, y.value)); 136 | public static Hp Max(Hp x, Hp y) => new Hp(Math.Max(x.value, y.value)); 137 | } 138 | ``` 139 | 140 | You can configure with `UnitGenerateOptions`, which method to implement. 141 | 142 | ```csharp 143 | [Flags] 144 | enum UnitGenerateOptions 145 | { 146 | None = 0, 147 | ImplicitOperator = 1, 148 | ParseMethod = 1 << 1, 149 | MinMaxMethod = 1 << 2, 150 | ArithmeticOperator = 1 << 3, 151 | ValueArithmeticOperator = 1 << 4, 152 | Comparable = 1 << 5, 153 | Validate = 1 << 6, 154 | JsonConverter = 1 << 7, 155 | MessagePackFormatter = 1 << 8, 156 | DapperTypeHandler = 1 << 9, 157 | EntityFrameworkValueConverter = 1 << 10, 158 | WithoutComparisonOperator = 1 << 11, 159 | JsonConverterDictionaryKeySupport = 1 << 12, 160 | Normalize = 1 << 13, 161 | } 162 | ``` 163 | 164 | UnitGenerateOptions has some serializer support. For example, a result like `Serialize(userId) => { Value = 1111 }` is awful. The value-object should be serialized natively, i.e. `Serialize(useId) => 1111`, and should be able to be added directly to a database, etc. 165 | 166 | Currently UnitGenerator supports [MessagePack for C#](https://github.com/MessagePack-CSharp/MessagePack-CSharp), System.Text.Json(JsonSerializer), [Dapper](https://github.com/StackExchange/Dapper) and EntityFrameworkCore. 167 | 168 | ```csharp 169 | [UnitOf(UnitGenerateOptions.MessagePackFormatter)] 170 | public readonly partial struct UserId; 171 | 172 | // -- generates 173 | 174 | [MessagePackFormatter(typeof(UserIdMessagePackFormatter))] 175 | public readonly partial struct UserId 176 | { 177 | class UserIdMessagePackFormatter : IMessagePackFormatter 178 | { 179 | public void Serialize(ref MessagePackWriter writer, UserId value, MessagePackSerializerOptions options) 180 | { 181 | options.Resolver.GetFormatterWithVerify().Serialize(ref writer, value.value, options); 182 | } 183 | 184 | public UserId Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) 185 | { 186 | return new UserId(options.Resolver.GetFormatterWithVerify().Deserialize(ref reader, options)); 187 | } 188 | } 189 | } 190 | ``` 191 | 192 | 193 | 194 | ## Table of Contents 195 | 196 | - [UnitOfAttribute](#unitofattribute) 197 | - [UnitGenerateOptions](#unitgenerateoptions) 198 | - [ImplicitOperator](#implicitoperator) 199 | - [ParseMethod](#parsemethod) 200 | - [MinMaxMethod](#minmaxmethod) 201 | - [ArithmeticOperator](#arithmeticoperator) 202 | - [ValueArithmeticOperator](#valuearithmeticoperator) 203 | - [Comparable](#comparable) 204 | - [WithoutComparisonOperator](#withoutcomparisonoperator) 205 | - [Validate](#validate) 206 | - [Normalize](#normalize) 207 | - [JsonConverter](#jsonconverter) 208 | - [JsonConverterDictionaryKeySupport](#jsonconverterdictionarykeysupport) 209 | - [MessagePackFormatter](#messagepackformatter) 210 | - [DapperTypeHandler](#dappertypehandler) 211 | - [EntityFrameworkValueConverter](#entityframeworkvalueconverter) 212 | - [Use for Unity](#use-for-unity) 213 | - [License](#license) 214 | 215 | 216 | 217 | ## UnitOfAttribute 218 | When referring to the UnitGenerator, it generates a internal `UnitOfAttribute`. 219 | 220 | ```csharp 221 | namespace UnitGenerator 222 | { 223 | [AttributeUsage(AttributeTargets.Struct, AllowMultiple = false)] 224 | internal class UnitOfAttribute : Attribute 225 | { 226 | public Type Type { get; } 227 | public UnitGenerateOptions Options { get; } 228 | public UnitArithmeticOperators ArithmeticOperators { get; set; } 229 | public string? ToStringFormat { get; set; } 230 | 231 | public UnitOfAttribute(Type type, UnitGenerateOptions options = UnitGenerateOptions.None) { ... } 232 | } 233 | 234 | #if NET7_0_OR_GREATER 235 | [AttributeUsage(AttributeTargets.Struct, AllowMultiple = false)] 236 | internal class UnitOfAttribute : Attribute 237 | { 238 | public Type Type { get; } 239 | public UnitGenerateOptions Options { get; } 240 | public UnitArithmeticOperators ArithmeticOperators { get; set; } = UnitArithmeticOperators.All; 241 | public string? ToStringFormat { get; set; } 242 | 243 | public UnitOfAttribute(UnitGenerateOptions options = UnitGenerateOptions.None) 244 | { 245 | this.Type = typeof(T); 246 | this.Options = options; 247 | } 248 | } 249 | #endif 250 | } 251 | ``` 252 | 253 | You can attach this attribute with any specified underlying type to `readonly partial struct`. 254 | 255 | ```csharp 256 | [UnitOf(typeof(Guid))] 257 | public readonly partial struct GroupId { } 258 | 259 | [UnitOf(typeof(string))] 260 | public readonly partial struct Message { } 261 | 262 | [UnitOf(typeof(long))] 263 | public readonly partial struct Power { } 264 | 265 | [UnitOf(typeof(byte[]))] 266 | public readonly partial struct Image { } 267 | 268 | [UnitOf(typeof(DateTime))] 269 | public readonly partial struct StartDate { } 270 | 271 | [UnitOf(typeof((string street, string city)))] 272 | public readonly partial struct StreetAddress { } 273 | ``` 274 | 275 | Standard UnitOf(`UnitGenerateOptions.None`) generates value constructor, `explicit operator`, `implement IEquatable`, `override GetHashCode`, `override ToString`, `==` and `!=` operator, `TypeConverter` for ASP.NET Core binding, `AsPrimitive` method. 276 | 277 | If you want to retrieve primitive value, use `AsPrimitive()` instead of `.Value`. This is intended to avoid casual getting of primitive values (using the arithmetic operator option if available). 278 | 279 | > When type is bool, also implements `true`, `false`, `!` operators. 280 | 281 | ```csharp 282 | public static bool operator true(Foo x) => x.value; 283 | public static bool operator false(Foo x) => !x.value; 284 | public static bool operator !(Foo x) => !x.value; 285 | ``` 286 | 287 | > When type is Guid or [Ulid](https://github.com/Cysharp/Ulid), also implements `New()` and `New***()` static operator.
288 | > For Guid type in .NET 9.0 or later, these methods accept an optional `uuidV7` parameter. When `uuidV7` is set to `true`, the methods use `Guid.CreateVersion7()` internally. 289 | 290 | ```csharp 291 | public static GroupId New(); 292 | public static GroupId NewGroupId(); 293 | // overload .NET 9.0+ 294 | public static GroupId New(bool uuidV7); 295 | public static GroupId NewGroupId(bool uuidV7); 296 | ``` 297 | 298 | Second parameter `UnitGenerateOptions options` can configure which method to implement, default is `None`. 299 | 300 | Optional named parameter: `ArithmeticOperators` can configure which generates operators specifically. Default is `Number`. (This can be used if UnitGenerateOptions.ArithmeticOperator is specified.) 301 | 302 | Optional named parameter: `ToStringFormat` can configure `ToString` format. Default is null and output as $`{0}`. 303 | 304 | ## UnitGenerateOptions 305 | 306 | When referring to the UnitGenerator, it generates a internal `UnitGenerateOptions` that is bit flag of which method to implement. 307 | 308 | ```csharp 309 | [Flags] 310 | internal enum UnitGenerateOptions 311 | { 312 | None = 0, 313 | ImplicitOperator = 1, 314 | ParseMethod = 2, 315 | MinMaxMethod = 4, 316 | ArithmeticOperator = 8, 317 | ValueArithmeticOperator = 16, 318 | Comparable = 32, 319 | Validate = 64, 320 | JsonConverter = 128, 321 | MessagePackFormatter = 256, 322 | DapperTypeHandler = 512, 323 | EntityFrameworkValueConverter = 1024, 324 | } 325 | ``` 326 | 327 | You can use this with `[UnitOf]`. 328 | 329 | ```csharp 330 | [UnitOf(typeof(int), UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator | UnitGenerateOptions.Comparable | UnitGenerateOptions.MinMaxMethod)] 331 | public readonly partial struct Strength { } 332 | 333 | [UnitOf(typeof(DateTime), UnitGenerateOptions.Validate | UnitGenerateOptions.ParseMethod | UnitGenerateOptions.Comparable)] 334 | public readonly partial struct EndDate { } 335 | 336 | [UnitOf(typeof(double), UnitGenerateOptions.ParseMethod | UnitGenerateOptions.MinMaxMethod | UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator | UnitGenerateOptions.Comparable | UnitGenerateOptions.Validate | UnitGenerateOptions.JsonConverter | UnitGenerateOptions.MessagePackFormatter | UnitGenerateOptions.DapperTypeHandler | UnitGenerateOptions.EntityFrameworkValueConverter)] 337 | public readonly partial struct AllOptionsStruct { } 338 | ``` 339 | 340 | You can setup project default options like this. 341 | 342 | ```csharp 343 | internal static class UnitOfOptions 344 | { 345 | public const UnitGenerateOptions Default = UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator | UnitGenerateOptions.Comparable | UnitGenerateOptions.MinMaxMethod; 346 | } 347 | 348 | [UnitOf(typeof(int), UnitOfOptions.Default)] 349 | public readonly partial struct Hp { } 350 | ``` 351 | 352 | ### ImplicitOperator 353 | 354 | ```csharp 355 | // Default 356 | public static explicit operator U(T value) => value.value; 357 | public static explicit operator T(U value) => new T(value); 358 | 359 | // UnitGenerateOptions.ImplicitOperator 360 | public static implicit operator U(T value) => value.value; 361 | public static implicit operator T(U value) => new T(value); 362 | ``` 363 | 364 | ### ParseMethod 365 | 366 | ```csharp 367 | public static T Parse(string s) 368 | public static bool TryParse(string s, out T result) 369 | ``` 370 | 371 | ### MinMaxMethod 372 | 373 | ```csharp 374 | public static T Min(T x, T y) 375 | public static T Max(T x, T y) 376 | ``` 377 | 378 | ### ArithmeticOperator 379 | 380 | ```csharp 381 | public static T operator +(in T x, in T y) => new T(checked((U)(x.value + y.value))); 382 | public static T operator -(in T x, in T y) => new T(checked((U)(x.value - y.value))); 383 | public static T operator *(in T x, in T y) => new T(checked((U)(x.value * y.value))); 384 | public static T operator /(in T x, in T y) => new T(checked((U)(x.value / y.value))); 385 | public static T operator +(T value) => new((U)(+value.value)); 386 | public static T operator -(T value) => new((U)(-value.value)); 387 | public static T operator ++(T x) => new T(checked((U)(x.value + 1))); 388 | public static T operator --(T x) => new T(checked((U)(x.value - 1))); 389 | ``` 390 | 391 | In addition, all members conforming to [System.Numerics.INumber](https://learn.microsoft.com/ja-jp/dotnet/api/system.numerics.inumber-1) are generated. 392 | 393 | If you want to suppress this and generate only certain operators, you can use the the `ArithmeticOperatros` option of `[UnitOf]` attribute as follows: 394 | 395 | ```csharp 396 | [UnitOf( 397 | typeof(int), 398 | UnitGenerateOptions.ArithmeticOperator, 399 | ArithmeticOperators = UnitArithmeticOperators.Addition | UnitArithmeticOperators.Subtraction)] 400 | public readonly partial struct Hp { } 401 | ``` 402 | 403 | | Value | Generates | 404 | |-------------------------------------|----------------------------------------------------------------------------------------| 405 | | UnitArithmeticOperators.Addition | `T operator +(T, T)` | 406 | | UnitArithmeticOperators.Subtraction | `T operator -(T, T)` | 407 | | UnitArithmeticOperators.Multiply | `T operator *(T, T)`, `T operator +(T)`, `T operator-(T)` | 408 | | UnitArithmeticOperators.Division | `T operator /(T, T)`, `T operator +(T)`, `T operator-(T)` | 409 | | UnitArithmeticOperators.Increment | `T operator ++(T)` | 410 | | UnitArithmeticOperators.Decrement | `T operator --(T)` | 411 | 412 | ### ValueArithmeticOperator 413 | 414 | ```csharp 415 | public static T operator +(in T x, in U y) => new T(checked((U)(x.value + y))); 416 | public static T operator -(in T x, in U y) => new T(checked((U)(x.value - y))); 417 | public static T operator *(in T x, in U y) => new T(checked((U)(x.value * y))); 418 | public static T operator /(in T x, in U y) => new T(checked((U)(x.value / y))); 419 | ``` 420 | 421 | ### Comparable 422 | 423 | Implements `IComparable` and `>`, `<`, `>=`, `<=` operators. 424 | 425 | ```csharp 426 | public U CompareTo(T other) => value.CompareTo(other.value); 427 | public static bool operator >(in T x, in T y) => x.value > y.value; 428 | public static bool operator <(in T x, in T y) => x.value < y.value; 429 | public static bool operator >=(in T x, in T y) => x.value >= y.value; 430 | public static bool operator <=(in T x, in T y) => x.value <= y.value; 431 | ``` 432 | 433 | ### WithoutComparisonOperator 434 | 435 | Without implements `>`, `<`, `>=`, `<=` operators. For example, useful for Guid. 436 | 437 | ```csharp 438 | [UnitOf(typeof(Guid), UnitGenerateOptions.Comparable | UnitGenerateOptions.WithoutComparisonOperator)] 439 | public readonly partial struct FooId { } 440 | ``` 441 | 442 | ### Validate 443 | 444 | Implements `partial void Validate()` method that is called on constructor. 445 | 446 | ```csharp 447 | // You can implement this custom validate method. 448 | [UnitOf(typeof(int), UnitGenerateOptions.Validate)] 449 | public readonly partial struct SampleValidate 450 | { 451 | // impl here. 452 | private partial void Validate() 453 | { 454 | if (value > 9999) throw new Exception("Invalid value range: " + value); 455 | } 456 | } 457 | 458 | // Source generator generate this codes. 459 | public T(int value) 460 | { 461 | this.value = value; 462 | this.Validate(); 463 | } 464 | 465 | private partial void Validate(); 466 | ``` 467 | 468 | ### Normalize 469 | 470 | Implements `partial void Normalize(ref T value)` method that is called on constructor. 471 | 472 | ```csharp 473 | // You can implement this custom normalize method to change value during initialization 474 | [UnitOf(typeof(int), UnitGenerateOptions.Normalize)] 475 | public readonly partial struct SampleValidate 476 | { 477 | // impl here. 478 | private partial void Normalize(ref int value) 479 | { 480 | value = Math.Max(value, 9999); 481 | } 482 | } 483 | 484 | // Source generator generate this codes. 485 | public T(int value) 486 | { 487 | this.value = value; 488 | this.Normalize(ref this.value); 489 | } 490 | 491 | private partial void Normalize(ref int value); 492 | ``` 493 | 494 | ### JsonConverter 495 | 496 | Implements `System.Text.Json`'s `JsonConverter`. It will be used `JsonSerializer` automatically. 497 | 498 | ```csharp 499 | [JsonConverter(typeof(UserIdJsonConverter))] 500 | public readonly partial struct UserId 501 | { 502 | class UserIdJsonConverter : JsonConverter 503 | } 504 | ``` 505 | 506 | ### JsonConverterDictionaryKeySupport 507 | 508 | Implements `JsonConverter`'s `WriteAsPropertyName/ReadAsPropertyName`. It supports from .NET 6, supports Dictionary's Key. 509 | 510 | ```csharp 511 | var dict = Dictionary 512 | JsonSerializer.Serialize(dict); 513 | ```` 514 | 515 | ### MessagePackFormatter 516 | 517 | Implements MessagePack for C#'s `MessagePackFormatter`. It will be used `MessagePackSerializer` automatically. 518 | 519 | ```csharp 520 | [MessagePackFormatter(typeof(UserIdMessagePackFormatter))] 521 | public readonly partial struct UserId 522 | { 523 | class UserIdMessagePackFormatter : IMessagePackFormatter 524 | } 525 | ``` 526 | 527 | ### DapperTypeHandler 528 | 529 | Implements Dapper's TypeHandler by public accessibility. TypeHandler is automatically registered at the time of Module initialization. 530 | 531 | ```csharp 532 | public readonly partial struct UserId 533 | { 534 | public class UserIdTypeHandler : Dapper.SqlMapper.TypeHandler 535 | } 536 | 537 | [ModuleInitializer] 538 | public static void AddTypeHandler() 539 | { 540 | Dapper.SqlMapper.AddTypeHandler(new A.ATypeHandler()); 541 | } 542 | ``` 543 | 544 | ### EntityFrameworkValueConverter 545 | 546 | Implements EntityFrameworkCore's ValueConverter by public accessibility. It is not registered automatically so you need to register manually. 547 | 548 | ```csharp 549 | public readonly partial struct UserId 550 | { 551 | public class UserIdValueConverter : ValueConverter 552 | } 553 | 554 | // setup handler manually 555 | builder.HasConversion(new UserId.UserIdValueConverter()); 556 | ``` 557 | 558 | ## Use for Unity 559 | 560 | Minimum supported Unity version is `2022.3.12f1`. 561 | 562 | The easiest way is to install `UnitGenerator` from NuGet using [NuGetForUnity](https://github.com/GlitchEnzo/NuGetForUnity). 563 | 564 | Alternatively, you can download `UnitGenerator.dll` from the [releases page](https://github.com/Cysharp/UnitGenerator/releases) page and set it up as a `RoslynAnalyzer` to make it work. 565 | 566 | License 567 | --- 568 | This library is under the MIT License. 569 | -------------------------------------------------------------------------------- /UnitGenerator.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32929.385 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitGenerator", "src\UnitGenerator\UnitGenerator.csproj", "{208D16B4-5904-4E06-8A41-D06C3FE7293E}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleApp", "sandbox\ConsoleApp\ConsoleApp.csproj", "{8AF2ABCF-38FB-4D64-A051-8735AF83E223}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sandbox", "sandbox", "{34EB4113-923D-4855-979C-A0467461B75C}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{1ABB9F1B-30F7-47D6-B29A-624E4484F67F}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{187FBF64-D2AA-444D-AFB1-CE999BC6AD34}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitGenerator.Tests", "tests\UnitGenerator.Tests\UnitGenerator.Tests.csproj", "{5DA06D43-A023-4464-B856-8BB42E8E4A05}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileGenerate", "sandbox\FileGenerate\FileGenerate.csproj", "{F8353A7A-290E-41D7-A6F8-8D8DBDD44433}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityFrameworkApp", "src\EntityFrameworkApp\EntityFrameworkApp.csproj", "{51AE7857-4223-40FE-AEA9-F0E64C5F8238}" 21 | EndProject 22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitGenerator.NET9.Tests", "tests\UnitGenerator.NET9.Tests\UnitGenerator.NET9.Tests.csproj", "{EAC3A025-E49B-4719-8738-9C3B67484A4A}" 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {208D16B4-5904-4E06-8A41-D06C3FE7293E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {208D16B4-5904-4E06-8A41-D06C3FE7293E}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {208D16B4-5904-4E06-8A41-D06C3FE7293E}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {208D16B4-5904-4E06-8A41-D06C3FE7293E}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {8AF2ABCF-38FB-4D64-A051-8735AF83E223}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {8AF2ABCF-38FB-4D64-A051-8735AF83E223}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {8AF2ABCF-38FB-4D64-A051-8735AF83E223}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {8AF2ABCF-38FB-4D64-A051-8735AF83E223}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {5DA06D43-A023-4464-B856-8BB42E8E4A05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {5DA06D43-A023-4464-B856-8BB42E8E4A05}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {5DA06D43-A023-4464-B856-8BB42E8E4A05}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {5DA06D43-A023-4464-B856-8BB42E8E4A05}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {F8353A7A-290E-41D7-A6F8-8D8DBDD44433}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {F8353A7A-290E-41D7-A6F8-8D8DBDD44433}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {F8353A7A-290E-41D7-A6F8-8D8DBDD44433}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {F8353A7A-290E-41D7-A6F8-8D8DBDD44433}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {51AE7857-4223-40FE-AEA9-F0E64C5F8238}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {51AE7857-4223-40FE-AEA9-F0E64C5F8238}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {51AE7857-4223-40FE-AEA9-F0E64C5F8238}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {51AE7857-4223-40FE-AEA9-F0E64C5F8238}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {EAC3A025-E49B-4719-8738-9C3B67484A4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {EAC3A025-E49B-4719-8738-9C3B67484A4A}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {EAC3A025-E49B-4719-8738-9C3B67484A4A}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {EAC3A025-E49B-4719-8738-9C3B67484A4A}.Release|Any CPU.Build.0 = Release|Any CPU 54 | EndGlobalSection 55 | GlobalSection(SolutionProperties) = preSolution 56 | HideSolutionNode = FALSE 57 | EndGlobalSection 58 | GlobalSection(NestedProjects) = preSolution 59 | {208D16B4-5904-4E06-8A41-D06C3FE7293E} = {1ABB9F1B-30F7-47D6-B29A-624E4484F67F} 60 | {8AF2ABCF-38FB-4D64-A051-8735AF83E223} = {34EB4113-923D-4855-979C-A0467461B75C} 61 | {5DA06D43-A023-4464-B856-8BB42E8E4A05} = {187FBF64-D2AA-444D-AFB1-CE999BC6AD34} 62 | {F8353A7A-290E-41D7-A6F8-8D8DBDD44433} = {34EB4113-923D-4855-979C-A0467461B75C} 63 | {51AE7857-4223-40FE-AEA9-F0E64C5F8238} = {34EB4113-923D-4855-979C-A0467461B75C} 64 | {EAC3A025-E49B-4719-8738-9C3B67484A4A} = {187FBF64-D2AA-444D-AFB1-CE999BC6AD34} 65 | EndGlobalSection 66 | GlobalSection(ExtensibilityGlobals) = postSolution 67 | SolutionGuid = {A64DF779-7829-414F-9E6E-3AF349486508} 68 | EndGlobalSection 69 | EndGlobal 70 | -------------------------------------------------------------------------------- /sandbox/ConsoleApp/AllPrimitives.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Globalization; 5 | using System.Linq; 6 | using System.Numerics; 7 | using System.Text; 8 | using System.Text.Json; 9 | using System.Text.Json.Serialization; 10 | using System.Threading.Tasks; 11 | using UnitGenerator; 12 | 13 | namespace ConsoleApp 14 | { 15 | 16 | 17 | 18 | [UnitOf(typeof(int), UnitGenerateOptions.Normalize | UnitGenerateOptions.ParseMethod | UnitGenerateOptions.MinMaxMethod | UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator | UnitGenerateOptions.Comparable | UnitGenerateOptions.Validate | UnitGenerateOptions.JsonConverter | UnitGenerateOptions.MessagePackFormatter | UnitGenerateOptions.DapperTypeHandler | UnitGenerateOptions.EntityFrameworkValueConverter | UnitGenerateOptions.JsonConverterDictionaryKeySupport)] 19 | public readonly partial struct A 20 | { 21 | private partial void Normalize(ref int value) 22 | { 23 | value = Math.Clamp(value, 0, 100); 24 | } 25 | 26 | private partial void Validate() 27 | { 28 | _ = AsPrimitive(); 29 | } 30 | } 31 | 32 | [UnitOf(typeof(int), UnitGenerateOptions.ParseMethod | UnitGenerateOptions.MinMaxMethod | UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator | UnitGenerateOptions.Comparable | UnitGenerateOptions.Validate | UnitGenerateOptions.JsonConverter | UnitGenerateOptions.MessagePackFormatter | UnitGenerateOptions.DapperTypeHandler | UnitGenerateOptions.EntityFrameworkValueConverter | UnitGenerateOptions.JsonConverterDictionaryKeySupport)] 33 | public readonly partial struct T 34 | { 35 | private partial void Validate() 36 | { 37 | _ = AsPrimitive(); 38 | } 39 | } 40 | 41 | [UnitOf(typeof(uint), UnitGenerateOptions.ParseMethod | UnitGenerateOptions.MinMaxMethod | UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator | UnitGenerateOptions.Comparable | UnitGenerateOptions.Validate | UnitGenerateOptions.JsonConverter | UnitGenerateOptions.MessagePackFormatter | UnitGenerateOptions.DapperTypeHandler | UnitGenerateOptions.EntityFrameworkValueConverter | UnitGenerateOptions.JsonConverterDictionaryKeySupport)] 42 | public readonly partial struct B 43 | { 44 | private partial void Validate() 45 | { 46 | _ = AsPrimitive(); 47 | } 48 | } 49 | 50 | [UnitOf(typeof(short), UnitGenerateOptions.ParseMethod | UnitGenerateOptions.MinMaxMethod | UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator | UnitGenerateOptions.Comparable | UnitGenerateOptions.Validate | UnitGenerateOptions.JsonConverter | UnitGenerateOptions.MessagePackFormatter | UnitGenerateOptions.DapperTypeHandler | UnitGenerateOptions.EntityFrameworkValueConverter | UnitGenerateOptions.JsonConverterDictionaryKeySupport)] 51 | public readonly partial struct C 52 | { 53 | private partial void Validate() 54 | { 55 | _ = AsPrimitive(); 56 | } 57 | } 58 | 59 | [UnitOf(typeof(ushort), UnitGenerateOptions.ParseMethod | UnitGenerateOptions.MinMaxMethod | UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator | UnitGenerateOptions.Comparable | UnitGenerateOptions.Validate | UnitGenerateOptions.JsonConverter | UnitGenerateOptions.MessagePackFormatter | UnitGenerateOptions.DapperTypeHandler | UnitGenerateOptions.EntityFrameworkValueConverter | UnitGenerateOptions.JsonConverterDictionaryKeySupport)] 60 | public readonly partial struct D 61 | { 62 | private partial void Validate() 63 | { 64 | _ = AsPrimitive(); 65 | } 66 | } 67 | 68 | [UnitOf(typeof(byte), UnitGenerateOptions.ParseMethod | UnitGenerateOptions.MinMaxMethod | UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator | UnitGenerateOptions.Comparable | UnitGenerateOptions.Validate | UnitGenerateOptions.JsonConverter | UnitGenerateOptions.MessagePackFormatter | UnitGenerateOptions.DapperTypeHandler | UnitGenerateOptions.EntityFrameworkValueConverter | UnitGenerateOptions.JsonConverterDictionaryKeySupport)] 69 | public readonly partial struct E 70 | { 71 | private partial void Validate() 72 | { 73 | _ = AsPrimitive(); 74 | } 75 | } 76 | 77 | [UnitOf(typeof(sbyte), UnitGenerateOptions.ParseMethod | UnitGenerateOptions.MinMaxMethod | UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator | UnitGenerateOptions.Comparable | UnitGenerateOptions.Validate | UnitGenerateOptions.JsonConverter | UnitGenerateOptions.MessagePackFormatter | UnitGenerateOptions.DapperTypeHandler | UnitGenerateOptions.EntityFrameworkValueConverter | UnitGenerateOptions.JsonConverterDictionaryKeySupport)] 78 | public readonly partial struct F 79 | { 80 | private partial void Validate() 81 | { 82 | _ = AsPrimitive(); 83 | } 84 | } 85 | 86 | [UnitOf(typeof(float), UnitGenerateOptions.ParseMethod | UnitGenerateOptions.MinMaxMethod | UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator | UnitGenerateOptions.Comparable | UnitGenerateOptions.Validate | UnitGenerateOptions.JsonConverter | UnitGenerateOptions.MessagePackFormatter | UnitGenerateOptions.DapperTypeHandler | UnitGenerateOptions.EntityFrameworkValueConverter | UnitGenerateOptions.JsonConverterDictionaryKeySupport)] 87 | public readonly partial struct G 88 | { 89 | private partial void Validate() 90 | { 91 | _ = AsPrimitive(); 92 | } 93 | } 94 | 95 | [UnitOf(typeof(double), UnitGenerateOptions.ParseMethod | UnitGenerateOptions.MinMaxMethod | UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator | UnitGenerateOptions.Comparable | UnitGenerateOptions.Validate | UnitGenerateOptions.JsonConverter | UnitGenerateOptions.MessagePackFormatter | UnitGenerateOptions.DapperTypeHandler | UnitGenerateOptions.EntityFrameworkValueConverter | UnitGenerateOptions.JsonConverterDictionaryKeySupport)] 96 | public readonly partial struct H 97 | { 98 | private partial void Validate() 99 | { 100 | _ = AsPrimitive(); 101 | } 102 | } 103 | 104 | [UnitOf(typeof(decimal), UnitGenerateOptions.ParseMethod | UnitGenerateOptions.MinMaxMethod | UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator | UnitGenerateOptions.Comparable | UnitGenerateOptions.Validate | UnitGenerateOptions.JsonConverter | UnitGenerateOptions.MessagePackFormatter | UnitGenerateOptions.DapperTypeHandler | UnitGenerateOptions.EntityFrameworkValueConverter | UnitGenerateOptions.JsonConverterDictionaryKeySupport)] 105 | public readonly partial struct I 106 | { 107 | private partial void Validate() 108 | { 109 | _ = AsPrimitive(); 110 | } 111 | } 112 | 113 | [UnitOf(typeof(float), UnitGenerateOptions.ParseMethod | UnitGenerateOptions.MinMaxMethod | UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator | UnitGenerateOptions.Comparable | UnitGenerateOptions.Validate | UnitGenerateOptions.JsonConverter | UnitGenerateOptions.MessagePackFormatter | UnitGenerateOptions.DapperTypeHandler | UnitGenerateOptions.EntityFrameworkValueConverter | UnitGenerateOptions.JsonConverterDictionaryKeySupport)] 114 | public readonly partial struct J 115 | { 116 | private partial void Validate() 117 | { 118 | _ = AsPrimitive(); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /sandbox/ConsoleApp/ConsoleApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /sandbox/ConsoleApp/Operators.cs: -------------------------------------------------------------------------------- 1 | using UnitGenerator; 2 | 3 | namespace ConsoleApp 4 | { 5 | namespace ConsoleApp 6 | { 7 | [UnitOf(typeof(int), UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator, ArithmeticOperators = UnitArithmeticOperators.Addition)] 8 | public readonly partial struct Add 9 | { 10 | } 11 | 12 | [UnitOf(typeof(int), UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator, ArithmeticOperators = UnitArithmeticOperators.Subtraction)] 13 | public readonly partial struct Sub 14 | { 15 | } 16 | 17 | [UnitOf(typeof(int), UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator, ArithmeticOperators = UnitArithmeticOperators.Multiply)] 18 | public readonly partial struct Mul 19 | { 20 | } 21 | 22 | [UnitOf(typeof(int), UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator, ArithmeticOperators = UnitArithmeticOperators.Division)] 23 | public readonly partial struct Div 24 | { 25 | } 26 | 27 | [UnitOf(typeof(int), UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator, ArithmeticOperators = UnitArithmeticOperators.Increment)] 28 | public readonly partial struct Inc 29 | { 30 | } 31 | 32 | [UnitOf(typeof(int), UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator, ArithmeticOperators = UnitArithmeticOperators.Decrement)] 33 | public readonly partial struct Dec 34 | { 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sandbox/ConsoleApp/Others.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnitGenerator; 3 | 4 | namespace ConsoleApp 5 | { 6 | [UnitOf(typeof(Guid), UnitGenerateOptions.ParseMethod | UnitGenerateOptions.Validate | UnitGenerateOptions.DapperTypeHandler | UnitGenerateOptions.EntityFrameworkValueConverter)] 7 | public readonly partial struct GD 8 | { 9 | private partial void Validate() 10 | { 11 | _ = AsPrimitive(); 12 | } 13 | } 14 | [UnitOf(typeof(DateTime), UnitGenerateOptions.ParseMethod | UnitGenerateOptions.Validate | UnitGenerateOptions.DapperTypeHandler | UnitGenerateOptions.EntityFrameworkValueConverter)] 15 | public readonly partial struct DT 16 | { 17 | private partial void Validate() 18 | { 19 | _ = AsPrimitive(); 20 | } 21 | } 22 | [UnitOf(typeof(string), UnitGenerateOptions.Validate | UnitGenerateOptions.JsonConverter | UnitGenerateOptions.DapperTypeHandler | UnitGenerateOptions.EntityFrameworkValueConverter)] 23 | public readonly partial struct ST 24 | { 25 | private partial void Validate() 26 | { 27 | _ = AsPrimitive(); 28 | } 29 | } 30 | [UnitOf(typeof(byte[]), UnitGenerateOptions.Validate | UnitGenerateOptions.DapperTypeHandler | UnitGenerateOptions.EntityFrameworkValueConverter)] 31 | public readonly partial struct BA 32 | { 33 | private partial void Validate() 34 | { 35 | _ = AsPrimitive(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /sandbox/ConsoleApp/Program.cs: -------------------------------------------------------------------------------- 1 | //using Sample; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.Globalization; 7 | using System.Text; 8 | using System.Text.Json; 9 | using System.Text.Json.Serialization; 10 | using UnitGenerator; 11 | 12 | //var a = UnitGenerateOptions.JsonConverterDictionaryKeySupport; 13 | 14 | //var has = UnitGenerateOptions.JsonConverterDictionaryKeySupport.HasFlag(UnitGenerateOptions.Validate); 15 | //Console.WriteLine(has); 16 | 17 | var json = JsonSerializer.Serialize(new Dictionary { { Guid.NewGuid(), "hogemoge" } }); 18 | 19 | 20 | 21 | Console.WriteLine(json); 22 | 23 | 24 | 25 | 26 | 27 | [UnitOf] public readonly partial struct MyId; 28 | 29 | 30 | public readonly struct MyParsable : IParsable 31 | { 32 | public static MyParsable Parse(string s) 33 | => throw new NotImplementedException(); 34 | 35 | public static bool TryParse([NotNullWhen(true)] string? s, [MaybeNullWhen(false)] out MyParsable result) 36 | => throw new NotImplementedException(); 37 | 38 | public static MyParsable Parse(string s, IFormatProvider? provider) 39 | => throw new NotImplementedException(); 40 | 41 | public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out MyParsable result) 42 | => throw new NotImplementedException(); 43 | } 44 | 45 | [UnitOf(typeof(MyParsable), UnitGenerateOptions.ParseMethod)] 46 | public readonly partial struct StructInOtherLib 47 | { 48 | public static void Test() 49 | => StructInOtherLib.Parse(""); 50 | } 51 | 52 | [UnitOf(typeof(ulong), UnitGenerateOptions.ArithmeticOperator)] 53 | public readonly partial struct Money 54 | { 55 | } 56 | 57 | [UnitOf(typeof(int))] 58 | public readonly partial struct NoNamespace 59 | { 60 | } 61 | 62 | [UnitOf(typeof(Guid), UnitGenerateOptions.Comparable | UnitGenerateOptions.WithoutComparisonOperator)] 63 | public readonly partial struct FooId { } 64 | 65 | [UnitOf(typeof(Ulid), UnitGenerateOptions.Comparable | UnitGenerateOptions.WithoutComparisonOperator | UnitGenerateOptions.MessagePackFormatter | UnitGenerateOptions.JsonConverter | UnitGenerateOptions.JsonConverterDictionaryKeySupport)] 66 | public readonly partial struct BarId { } 67 | 68 | namespace Sample 69 | { 70 | [UnitOf(typeof(int), UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.Comparable | UnitGenerateOptions.MinMaxMethod | UnitGenerateOptions.JsonConverter | UnitGenerateOptions.JsonConverterDictionaryKeySupport)] 71 | public readonly partial struct Hp 72 | { 73 | // public static Hp operator +(in Hp x, in Hp y) => new Hp(checked((int)(x.value + y.value))); 74 | 75 | void Foo() 76 | { 77 | _ = this.AsPrimitive(); 78 | _ = this.ToString(); 79 | 80 | _ = FooId.NewFooId(); 81 | Guid.NewGuid(); 82 | //public static readonly Guid Empty; 83 | //Guid.Empty 84 | 85 | // public static readonly Ulid Empty = default(Ulid); 86 | // Ulid.Empty 87 | 88 | 89 | } 90 | 91 | } 92 | 93 | [UnitOf(typeof(int), UnitGenerateOptions.MessagePackFormatter)] 94 | public readonly partial struct UserId { } 95 | 96 | 97 | [UnitOf(typeof(int), UnitGenerateOptions.Validate)] 98 | public readonly partial struct SampleValidate 99 | { 100 | // impl here. 101 | private partial void Validate() 102 | { 103 | if (value > 9999) throw new Exception("Invalid value range: " + value); 104 | } 105 | } 106 | 107 | [UnitOf(typeof(int), UnitGenerateOptions.MessagePackFormatter)] 108 | public readonly partial struct UserId2 109 | { 110 | public void Foo() 111 | { 112 | 113 | 114 | _ = AsPrimitive(); 115 | } 116 | } 117 | 118 | [UnitOf(typeof(string), UnitGenerateOptions.ParseMethod)] 119 | public readonly partial struct StringId { } 120 | } 121 | 122 | -------------------------------------------------------------------------------- /sandbox/FileGenerate/FileGenerate.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | 6 | true 7 | $(ProjectDir)..\Generated 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /sandbox/FileGenerate/SimplePrimitive.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnitGenerator; 3 | 4 | namespace FileGenerate 5 | { 6 | [UnitOf(typeof(int))] 7 | public readonly partial struct A 8 | { 9 | } 10 | 11 | [UnitOf(typeof(string))] 12 | public readonly partial struct B 13 | { 14 | } 15 | 16 | [UnitOf(typeof(int), UnitGenerateOptions.Comparable | UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator | UnitGenerateOptions.ParseMethod)] 17 | public readonly partial struct C 18 | { 19 | } 20 | 21 | [UnitOf(typeof(Guid), UnitGenerateOptions.Comparable | UnitGenerateOptions.ParseMethod)] 22 | public readonly partial struct D 23 | { 24 | } 25 | 26 | [UnitOf] 27 | public readonly partial struct Aa 28 | { 29 | } 30 | 31 | [UnitOf()] 32 | public readonly partial struct Bb 33 | { 34 | } 35 | 36 | [UnitOf(UnitGenerateOptions.Comparable | UnitGenerateOptions.ArithmeticOperator | UnitGenerateOptions.ValueArithmeticOperator)] 37 | public readonly partial struct Cc 38 | { 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.A.g.cs: -------------------------------------------------------------------------------- 1 | // 2 | // THIS (.cs) FILE IS GENERATED BY UnitGenerator. DO NOT CHANGE IT. 3 | // 4 | #pragma warning disable CS8669 5 | using System; 6 | using System.Globalization; 7 | #if NET7_0_OR_GREATER 8 | using System.Numerics; 9 | #endif 10 | namespace FileGenerate 11 | { 12 | [System.ComponentModel.TypeConverter(typeof(ATypeConverter))] 13 | readonly partial struct A 14 | : IEquatable 15 | , IFormattable 16 | #if NET6_0_OR_GREATER 17 | , ISpanFormattable 18 | #endif 19 | #if NET8_0_OR_GREATER 20 | , IEqualityOperators 21 | , IUtf8SpanFormattable 22 | #endif 23 | { 24 | readonly int value; 25 | 26 | public int AsPrimitive() => value; 27 | 28 | public A(int value) 29 | { 30 | this.value = value; 31 | } 32 | 33 | public static explicit operator int(A value) 34 | { 35 | return value.value; 36 | } 37 | 38 | public static explicit operator A(int value) 39 | { 40 | return new A(value); 41 | } 42 | 43 | public bool Equals(A other) 44 | { 45 | return value.Equals(other.value); 46 | } 47 | 48 | public override bool Equals(object obj) 49 | { 50 | if (obj == null) return false; 51 | var t = obj.GetType(); 52 | if (t == typeof(A)) 53 | { 54 | return Equals((A)obj); 55 | } 56 | if (t == typeof(int)) 57 | { 58 | return value.Equals((int)obj); 59 | } 60 | 61 | return value.Equals(obj); 62 | } 63 | 64 | public static bool operator ==(A x, A y) 65 | { 66 | return x.value.Equals(y.value); 67 | } 68 | 69 | public static bool operator !=(A x, A y) 70 | { 71 | return !x.value.Equals(y.value); 72 | } 73 | 74 | public override int GetHashCode() 75 | { 76 | return value.GetHashCode(); 77 | } 78 | 79 | public override string ToString() => value.ToString(); 80 | 81 | public string ToString(string? format, IFormatProvider? formatProvider) => value.ToString(format, formatProvider); 82 | 83 | #if NET6_0_OR_GREATER 84 | public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => 85 | ((ISpanFormattable)value).TryFormat(destination, out charsWritten, format, provider); 86 | #endif 87 | #if NET8_0_OR_GREATER 88 | public bool TryFormat (Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) => 89 | ((IUtf8SpanFormattable)value).TryFormat(utf8Destination, out bytesWritten, format, provider); 90 | #endif 91 | 92 | // Default 93 | 94 | private class ATypeConverter : System.ComponentModel.TypeConverter 95 | { 96 | private static readonly Type WrapperType = typeof(A); 97 | private static readonly Type ValueType = typeof(int); 98 | 99 | public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, Type sourceType) 100 | { 101 | if (sourceType == WrapperType || sourceType == ValueType) 102 | { 103 | return true; 104 | } 105 | 106 | return base.CanConvertFrom(context, sourceType); 107 | } 108 | 109 | public override bool CanConvertTo(System.ComponentModel.ITypeDescriptorContext context, Type destinationType) 110 | { 111 | if (destinationType == WrapperType || destinationType == ValueType) 112 | { 113 | return true; 114 | } 115 | 116 | return base.CanConvertTo(context, destinationType); 117 | } 118 | 119 | public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) 120 | { 121 | if (value != null) 122 | { 123 | var t = value.GetType(); 124 | if (t == typeof(A)) 125 | { 126 | return (A)value; 127 | } 128 | if (t == typeof(int)) 129 | { 130 | return new A((int)value); 131 | } 132 | } 133 | 134 | return base.ConvertFrom(context, culture, value); 135 | } 136 | 137 | public override object ConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) 138 | { 139 | if (value is A wrappedValue) 140 | { 141 | if (destinationType == WrapperType) 142 | { 143 | return wrappedValue; 144 | } 145 | 146 | if (destinationType == ValueType) 147 | { 148 | return wrappedValue.AsPrimitive(); 149 | } 150 | } 151 | 152 | return base.ConvertTo(context, culture, value, destinationType); 153 | } 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.Aa.g.cs: -------------------------------------------------------------------------------- 1 | // 2 | // THIS (.cs) FILE IS GENERATED BY UnitGenerator. DO NOT CHANGE IT. 3 | // 4 | #pragma warning disable CS8669 5 | using System; 6 | using System.Globalization; 7 | #if NET7_0_OR_GREATER 8 | using System.Numerics; 9 | #endif 10 | namespace FileGenerate 11 | { 12 | [System.ComponentModel.TypeConverter(typeof(AaTypeConverter))] 13 | readonly partial struct Aa 14 | : IEquatable 15 | , IFormattable 16 | #if NET6_0_OR_GREATER 17 | , ISpanFormattable 18 | #endif 19 | #if NET8_0_OR_GREATER 20 | , IEqualityOperators 21 | , IUtf8SpanFormattable 22 | #endif 23 | { 24 | readonly int value; 25 | 26 | public int AsPrimitive() => value; 27 | 28 | public Aa(int value) 29 | { 30 | this.value = value; 31 | } 32 | 33 | public static explicit operator int(Aa value) 34 | { 35 | return value.value; 36 | } 37 | 38 | public static explicit operator Aa(int value) 39 | { 40 | return new Aa(value); 41 | } 42 | 43 | public bool Equals(Aa other) 44 | { 45 | return value.Equals(other.value); 46 | } 47 | 48 | public override bool Equals(object obj) 49 | { 50 | if (obj == null) return false; 51 | var t = obj.GetType(); 52 | if (t == typeof(Aa)) 53 | { 54 | return Equals((Aa)obj); 55 | } 56 | if (t == typeof(int)) 57 | { 58 | return value.Equals((int)obj); 59 | } 60 | 61 | return value.Equals(obj); 62 | } 63 | 64 | public static bool operator ==(Aa x, Aa y) 65 | { 66 | return x.value.Equals(y.value); 67 | } 68 | 69 | public static bool operator !=(Aa x, Aa y) 70 | { 71 | return !x.value.Equals(y.value); 72 | } 73 | 74 | public override int GetHashCode() 75 | { 76 | return value.GetHashCode(); 77 | } 78 | 79 | public override string ToString() => value.ToString(); 80 | 81 | public string ToString(string? format, IFormatProvider? formatProvider) => value.ToString(format, formatProvider); 82 | 83 | #if NET6_0_OR_GREATER 84 | public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => 85 | ((ISpanFormattable)value).TryFormat(destination, out charsWritten, format, provider); 86 | #endif 87 | #if NET8_0_OR_GREATER 88 | public bool TryFormat (Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) => 89 | ((IUtf8SpanFormattable)value).TryFormat(utf8Destination, out bytesWritten, format, provider); 90 | #endif 91 | 92 | // Default 93 | 94 | private class AaTypeConverter : System.ComponentModel.TypeConverter 95 | { 96 | private static readonly Type WrapperType = typeof(Aa); 97 | private static readonly Type ValueType = typeof(int); 98 | 99 | public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, Type sourceType) 100 | { 101 | if (sourceType == WrapperType || sourceType == ValueType) 102 | { 103 | return true; 104 | } 105 | 106 | return base.CanConvertFrom(context, sourceType); 107 | } 108 | 109 | public override bool CanConvertTo(System.ComponentModel.ITypeDescriptorContext context, Type destinationType) 110 | { 111 | if (destinationType == WrapperType || destinationType == ValueType) 112 | { 113 | return true; 114 | } 115 | 116 | return base.CanConvertTo(context, destinationType); 117 | } 118 | 119 | public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) 120 | { 121 | if (value != null) 122 | { 123 | var t = value.GetType(); 124 | if (t == typeof(Aa)) 125 | { 126 | return (Aa)value; 127 | } 128 | if (t == typeof(int)) 129 | { 130 | return new Aa((int)value); 131 | } 132 | } 133 | 134 | return base.ConvertFrom(context, culture, value); 135 | } 136 | 137 | public override object ConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) 138 | { 139 | if (value is Aa wrappedValue) 140 | { 141 | if (destinationType == WrapperType) 142 | { 143 | return wrappedValue; 144 | } 145 | 146 | if (destinationType == ValueType) 147 | { 148 | return wrappedValue.AsPrimitive(); 149 | } 150 | } 151 | 152 | return base.ConvertTo(context, culture, value, destinationType); 153 | } 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.B.g.cs: -------------------------------------------------------------------------------- 1 | // 2 | // THIS (.cs) FILE IS GENERATED BY UnitGenerator. DO NOT CHANGE IT. 3 | // 4 | #pragma warning disable CS8669 5 | using System; 6 | using System.Globalization; 7 | #if NET7_0_OR_GREATER 8 | using System.Numerics; 9 | #endif 10 | namespace FileGenerate 11 | { 12 | [System.ComponentModel.TypeConverter(typeof(BTypeConverter))] 13 | readonly partial struct B 14 | : IEquatable 15 | #if NET8_0_OR_GREATER 16 | , IEqualityOperators 17 | #endif 18 | { 19 | readonly string value; 20 | 21 | public string AsPrimitive() => value; 22 | 23 | public B(string value) 24 | { 25 | this.value = value; 26 | } 27 | 28 | public static explicit operator string(B value) 29 | { 30 | return value.value; 31 | } 32 | 33 | public static explicit operator B(string value) 34 | { 35 | return new B(value); 36 | } 37 | 38 | public bool Equals(B other) 39 | { 40 | return value.Equals(other.value); 41 | } 42 | 43 | public override bool Equals(object obj) 44 | { 45 | if (obj == null) return false; 46 | var t = obj.GetType(); 47 | if (t == typeof(B)) 48 | { 49 | return Equals((B)obj); 50 | } 51 | if (t == typeof(string)) 52 | { 53 | return value.Equals((string)obj); 54 | } 55 | 56 | return value.Equals(obj); 57 | } 58 | 59 | public static bool operator ==(B x, B y) 60 | { 61 | return x.value.Equals(y.value); 62 | } 63 | 64 | public static bool operator !=(B x, B y) 65 | { 66 | return !x.value.Equals(y.value); 67 | } 68 | 69 | public override int GetHashCode() 70 | { 71 | return value.GetHashCode(); 72 | } 73 | 74 | public override string ToString() => value == null ? "null" : value.ToString(); 75 | 76 | // Default 77 | 78 | private class BTypeConverter : System.ComponentModel.TypeConverter 79 | { 80 | private static readonly Type WrapperType = typeof(B); 81 | private static readonly Type ValueType = typeof(string); 82 | 83 | public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, Type sourceType) 84 | { 85 | if (sourceType == WrapperType || sourceType == ValueType) 86 | { 87 | return true; 88 | } 89 | 90 | return base.CanConvertFrom(context, sourceType); 91 | } 92 | 93 | public override bool CanConvertTo(System.ComponentModel.ITypeDescriptorContext context, Type destinationType) 94 | { 95 | if (destinationType == WrapperType || destinationType == ValueType) 96 | { 97 | return true; 98 | } 99 | 100 | return base.CanConvertTo(context, destinationType); 101 | } 102 | 103 | public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) 104 | { 105 | if (value != null) 106 | { 107 | var t = value.GetType(); 108 | if (t == typeof(B)) 109 | { 110 | return (B)value; 111 | } 112 | if (t == typeof(string)) 113 | { 114 | return new B((string)value); 115 | } 116 | } 117 | 118 | return base.ConvertFrom(context, culture, value); 119 | } 120 | 121 | public override object ConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) 122 | { 123 | if (value is B wrappedValue) 124 | { 125 | if (destinationType == WrapperType) 126 | { 127 | return wrappedValue; 128 | } 129 | 130 | if (destinationType == ValueType) 131 | { 132 | return wrappedValue.AsPrimitive(); 133 | } 134 | } 135 | 136 | return base.ConvertTo(context, culture, value, destinationType); 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.Bb.g.cs: -------------------------------------------------------------------------------- 1 | // 2 | // THIS (.cs) FILE IS GENERATED BY UnitGenerator. DO NOT CHANGE IT. 3 | // 4 | #pragma warning disable CS8669 5 | using System; 6 | using System.Globalization; 7 | #if NET7_0_OR_GREATER 8 | using System.Numerics; 9 | #endif 10 | namespace FileGenerate 11 | { 12 | [System.ComponentModel.TypeConverter(typeof(BbTypeConverter))] 13 | readonly partial struct Bb 14 | : IEquatable 15 | #if NET8_0_OR_GREATER 16 | , IEqualityOperators 17 | #endif 18 | { 19 | readonly string value; 20 | 21 | public string AsPrimitive() => value; 22 | 23 | public Bb(string value) 24 | { 25 | this.value = value; 26 | } 27 | 28 | public static explicit operator string(Bb value) 29 | { 30 | return value.value; 31 | } 32 | 33 | public static explicit operator Bb(string value) 34 | { 35 | return new Bb(value); 36 | } 37 | 38 | public bool Equals(Bb other) 39 | { 40 | return value.Equals(other.value); 41 | } 42 | 43 | public override bool Equals(object obj) 44 | { 45 | if (obj == null) return false; 46 | var t = obj.GetType(); 47 | if (t == typeof(Bb)) 48 | { 49 | return Equals((Bb)obj); 50 | } 51 | if (t == typeof(string)) 52 | { 53 | return value.Equals((string)obj); 54 | } 55 | 56 | return value.Equals(obj); 57 | } 58 | 59 | public static bool operator ==(Bb x, Bb y) 60 | { 61 | return x.value.Equals(y.value); 62 | } 63 | 64 | public static bool operator !=(Bb x, Bb y) 65 | { 66 | return !x.value.Equals(y.value); 67 | } 68 | 69 | public override int GetHashCode() 70 | { 71 | return value.GetHashCode(); 72 | } 73 | 74 | public override string ToString() => value == null ? "null" : value.ToString(); 75 | 76 | // Default 77 | 78 | private class BbTypeConverter : System.ComponentModel.TypeConverter 79 | { 80 | private static readonly Type WrapperType = typeof(Bb); 81 | private static readonly Type ValueType = typeof(string); 82 | 83 | public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, Type sourceType) 84 | { 85 | if (sourceType == WrapperType || sourceType == ValueType) 86 | { 87 | return true; 88 | } 89 | 90 | return base.CanConvertFrom(context, sourceType); 91 | } 92 | 93 | public override bool CanConvertTo(System.ComponentModel.ITypeDescriptorContext context, Type destinationType) 94 | { 95 | if (destinationType == WrapperType || destinationType == ValueType) 96 | { 97 | return true; 98 | } 99 | 100 | return base.CanConvertTo(context, destinationType); 101 | } 102 | 103 | public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) 104 | { 105 | if (value != null) 106 | { 107 | var t = value.GetType(); 108 | if (t == typeof(Bb)) 109 | { 110 | return (Bb)value; 111 | } 112 | if (t == typeof(string)) 113 | { 114 | return new Bb((string)value); 115 | } 116 | } 117 | 118 | return base.ConvertFrom(context, culture, value); 119 | } 120 | 121 | public override object ConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) 122 | { 123 | if (value is Bb wrappedValue) 124 | { 125 | if (destinationType == WrapperType) 126 | { 127 | return wrappedValue; 128 | } 129 | 130 | if (destinationType == ValueType) 131 | { 132 | return wrappedValue.AsPrimitive(); 133 | } 134 | } 135 | 136 | return base.ConvertTo(context, culture, value, destinationType); 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.C.g.cs: -------------------------------------------------------------------------------- 1 | // 2 | // THIS (.cs) FILE IS GENERATED BY UnitGenerator. DO NOT CHANGE IT. 3 | // 4 | #pragma warning disable CS8669 5 | using System; 6 | using System.Globalization; 7 | #if NET7_0_OR_GREATER 8 | using System.Numerics; 9 | #endif 10 | namespace FileGenerate 11 | { 12 | [System.ComponentModel.TypeConverter(typeof(CTypeConverter))] 13 | readonly partial struct C 14 | : IEquatable 15 | , IComparable 16 | , IFormattable 17 | #if NET6_0_OR_GREATER 18 | , ISpanFormattable 19 | #endif 20 | #if NET7_0_OR_GREATER 21 | , IComparisonOperators 22 | , IAdditionOperators 23 | , ISubtractionOperators 24 | , IMultiplyOperators 25 | , IDivisionOperators 26 | , IUnaryPlusOperators 27 | , IUnaryNegationOperators 28 | , IIncrementOperators 29 | , IDecrementOperators 30 | #endif 31 | #if NET8_0_OR_GREATER 32 | , IEqualityOperators 33 | , IUtf8SpanFormattable 34 | #endif 35 | { 36 | readonly int value; 37 | 38 | public int AsPrimitive() => value; 39 | 40 | public C(int value) 41 | { 42 | this.value = value; 43 | } 44 | 45 | public static explicit operator int(C value) 46 | { 47 | return value.value; 48 | } 49 | 50 | public static explicit operator C(int value) 51 | { 52 | return new C(value); 53 | } 54 | 55 | public bool Equals(C other) 56 | { 57 | return value.Equals(other.value); 58 | } 59 | 60 | public override bool Equals(object obj) 61 | { 62 | if (obj == null) return false; 63 | var t = obj.GetType(); 64 | if (t == typeof(C)) 65 | { 66 | return Equals((C)obj); 67 | } 68 | if (t == typeof(int)) 69 | { 70 | return value.Equals((int)obj); 71 | } 72 | 73 | return value.Equals(obj); 74 | } 75 | 76 | public static bool operator ==(C x, C y) 77 | { 78 | return x.value.Equals(y.value); 79 | } 80 | 81 | public static bool operator !=(C x, C y) 82 | { 83 | return !x.value.Equals(y.value); 84 | } 85 | 86 | public override int GetHashCode() 87 | { 88 | return value.GetHashCode(); 89 | } 90 | 91 | public override string ToString() => value.ToString(); 92 | 93 | public string ToString(string? format, IFormatProvider? formatProvider) => value.ToString(format, formatProvider); 94 | 95 | #if NET6_0_OR_GREATER 96 | public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => 97 | ((ISpanFormattable)value).TryFormat(destination, out charsWritten, format, provider); 98 | #endif 99 | #if NET8_0_OR_GREATER 100 | public bool TryFormat (Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) => 101 | ((IUtf8SpanFormattable)value).TryFormat(utf8Destination, out bytesWritten, format, provider); 102 | #endif 103 | 104 | // UnitGenerateOptions.ParseMethod 105 | 106 | public static C Parse(string s) 107 | { 108 | return new C(int.Parse(s)); 109 | } 110 | 111 | public static bool TryParse(string s, out C result) 112 | { 113 | if (int.TryParse(s, out var r)) 114 | { 115 | result = new C(r); 116 | return true; 117 | } 118 | else 119 | { 120 | result = default(C); 121 | return false; 122 | } 123 | } 124 | 125 | // UnitGenerateOptions.ArithmeticOperator 126 | 127 | public static C operator +(C x, C y) 128 | { 129 | checked 130 | { 131 | return new C((int)(x.value + y.value)); 132 | } 133 | } 134 | 135 | public static C operator -(C x, C y) 136 | { 137 | checked 138 | { 139 | return new C((int)(x.value - y.value)); 140 | } 141 | } 142 | 143 | public static C operator +(C value) => new((int)(+value.value)); 144 | public static C operator -(C value) => new((int)(-value.value)); 145 | 146 | public static C operator *(C x, C y) 147 | { 148 | checked 149 | { 150 | return new C((int)(x.value * y.value)); 151 | } 152 | } 153 | 154 | 155 | public static C operator /(C x, C y) 156 | { 157 | checked 158 | { 159 | return new C((int)(x.value / y.value)); 160 | } 161 | } 162 | 163 | public static C operator ++(C x) 164 | { 165 | checked 166 | { 167 | return new C((int)((int)(x.value + 1))); 168 | } 169 | } 170 | 171 | public static C operator --(C x) 172 | { 173 | checked 174 | { 175 | return new C((int)((int)(x.value - 1))); 176 | } 177 | } 178 | 179 | // UnitGenerateOptions.ValueArithmeticOperator 180 | 181 | public static C operator +(C x, int y) 182 | { 183 | checked 184 | { 185 | return new C((int)(x.value + y)); 186 | } 187 | } 188 | 189 | public static C operator -(C x, int y) 190 | { 191 | checked 192 | { 193 | return new C((int)(x.value - y)); 194 | } 195 | } 196 | 197 | public static C operator *(C x, int y) 198 | { 199 | checked 200 | { 201 | return new C((int)(x.value * y)); 202 | } 203 | } 204 | 205 | 206 | public static C operator /(C x, int y) 207 | { 208 | checked 209 | { 210 | return new C((int)(x.value / y)); 211 | } 212 | } 213 | 214 | // UnitGenerateOptions.Comparable 215 | 216 | public int CompareTo(C other) 217 | { 218 | return value.CompareTo(other.value); 219 | } 220 | public static bool operator >(C x, C y) 221 | { 222 | return x.value > y.value; 223 | } 224 | 225 | public static bool operator <(C x, C y) 226 | { 227 | return x.value < y.value; 228 | } 229 | 230 | public static bool operator >=(C x, C y) 231 | { 232 | return x.value >= y.value; 233 | } 234 | 235 | public static bool operator <=(C x, C y) 236 | { 237 | return x.value <= y.value; 238 | } 239 | 240 | // Default 241 | 242 | private class CTypeConverter : System.ComponentModel.TypeConverter 243 | { 244 | private static readonly Type WrapperType = typeof(C); 245 | private static readonly Type ValueType = typeof(int); 246 | 247 | public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, Type sourceType) 248 | { 249 | if (sourceType == WrapperType || sourceType == ValueType) 250 | { 251 | return true; 252 | } 253 | 254 | return base.CanConvertFrom(context, sourceType); 255 | } 256 | 257 | public override bool CanConvertTo(System.ComponentModel.ITypeDescriptorContext context, Type destinationType) 258 | { 259 | if (destinationType == WrapperType || destinationType == ValueType) 260 | { 261 | return true; 262 | } 263 | 264 | return base.CanConvertTo(context, destinationType); 265 | } 266 | 267 | public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) 268 | { 269 | if (value != null) 270 | { 271 | var t = value.GetType(); 272 | if (t == typeof(C)) 273 | { 274 | return (C)value; 275 | } 276 | if (t == typeof(int)) 277 | { 278 | return new C((int)value); 279 | } 280 | } 281 | 282 | return base.ConvertFrom(context, culture, value); 283 | } 284 | 285 | public override object ConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) 286 | { 287 | if (value is C wrappedValue) 288 | { 289 | if (destinationType == WrapperType) 290 | { 291 | return wrappedValue; 292 | } 293 | 294 | if (destinationType == ValueType) 295 | { 296 | return wrappedValue.AsPrimitive(); 297 | } 298 | } 299 | 300 | return base.ConvertTo(context, culture, value, destinationType); 301 | } 302 | } 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.Cc.g.cs: -------------------------------------------------------------------------------- 1 | // 2 | // THIS (.cs) FILE IS GENERATED BY UnitGenerator. DO NOT CHANGE IT. 3 | // 4 | #pragma warning disable CS8669 5 | using System; 6 | using System.Globalization; 7 | #if NET7_0_OR_GREATER 8 | using System.Numerics; 9 | #endif 10 | namespace FileGenerate 11 | { 12 | [System.ComponentModel.TypeConverter(typeof(CcTypeConverter))] 13 | readonly partial struct Cc 14 | : IEquatable 15 | , IComparable 16 | , IFormattable 17 | #if NET6_0_OR_GREATER 18 | , ISpanFormattable 19 | #endif 20 | #if NET7_0_OR_GREATER 21 | , IComparisonOperators 22 | , IAdditionOperators 23 | , ISubtractionOperators 24 | , IMultiplyOperators 25 | , IDivisionOperators 26 | , IUnaryPlusOperators 27 | , IUnaryNegationOperators 28 | , IIncrementOperators 29 | , IDecrementOperators 30 | #endif 31 | #if NET8_0_OR_GREATER 32 | , IEqualityOperators 33 | , IUtf8SpanFormattable 34 | #endif 35 | { 36 | readonly int value; 37 | 38 | public int AsPrimitive() => value; 39 | 40 | public Cc(int value) 41 | { 42 | this.value = value; 43 | } 44 | 45 | public static explicit operator int(Cc value) 46 | { 47 | return value.value; 48 | } 49 | 50 | public static explicit operator Cc(int value) 51 | { 52 | return new Cc(value); 53 | } 54 | 55 | public bool Equals(Cc other) 56 | { 57 | return value.Equals(other.value); 58 | } 59 | 60 | public override bool Equals(object obj) 61 | { 62 | if (obj == null) return false; 63 | var t = obj.GetType(); 64 | if (t == typeof(Cc)) 65 | { 66 | return Equals((Cc)obj); 67 | } 68 | if (t == typeof(int)) 69 | { 70 | return value.Equals((int)obj); 71 | } 72 | 73 | return value.Equals(obj); 74 | } 75 | 76 | public static bool operator ==(Cc x, Cc y) 77 | { 78 | return x.value.Equals(y.value); 79 | } 80 | 81 | public static bool operator !=(Cc x, Cc y) 82 | { 83 | return !x.value.Equals(y.value); 84 | } 85 | 86 | public override int GetHashCode() 87 | { 88 | return value.GetHashCode(); 89 | } 90 | 91 | public override string ToString() => value.ToString(); 92 | 93 | public string ToString(string? format, IFormatProvider? formatProvider) => value.ToString(format, formatProvider); 94 | 95 | #if NET6_0_OR_GREATER 96 | public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => 97 | ((ISpanFormattable)value).TryFormat(destination, out charsWritten, format, provider); 98 | #endif 99 | #if NET8_0_OR_GREATER 100 | public bool TryFormat (Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) => 101 | ((IUtf8SpanFormattable)value).TryFormat(utf8Destination, out bytesWritten, format, provider); 102 | #endif 103 | 104 | // UnitGenerateOptions.ArithmeticOperator 105 | 106 | public static Cc operator +(Cc x, Cc y) 107 | { 108 | checked 109 | { 110 | return new Cc((int)(x.value + y.value)); 111 | } 112 | } 113 | 114 | public static Cc operator -(Cc x, Cc y) 115 | { 116 | checked 117 | { 118 | return new Cc((int)(x.value - y.value)); 119 | } 120 | } 121 | 122 | public static Cc operator +(Cc value) => new((int)(+value.value)); 123 | public static Cc operator -(Cc value) => new((int)(-value.value)); 124 | 125 | public static Cc operator *(Cc x, Cc y) 126 | { 127 | checked 128 | { 129 | return new Cc((int)(x.value * y.value)); 130 | } 131 | } 132 | 133 | 134 | public static Cc operator /(Cc x, Cc y) 135 | { 136 | checked 137 | { 138 | return new Cc((int)(x.value / y.value)); 139 | } 140 | } 141 | 142 | public static Cc operator ++(Cc x) 143 | { 144 | checked 145 | { 146 | return new Cc((int)((int)(x.value + 1))); 147 | } 148 | } 149 | 150 | public static Cc operator --(Cc x) 151 | { 152 | checked 153 | { 154 | return new Cc((int)((int)(x.value - 1))); 155 | } 156 | } 157 | 158 | // UnitGenerateOptions.ValueArithmeticOperator 159 | 160 | public static Cc operator +(Cc x, int y) 161 | { 162 | checked 163 | { 164 | return new Cc((int)(x.value + y)); 165 | } 166 | } 167 | 168 | public static Cc operator -(Cc x, int y) 169 | { 170 | checked 171 | { 172 | return new Cc((int)(x.value - y)); 173 | } 174 | } 175 | 176 | public static Cc operator *(Cc x, int y) 177 | { 178 | checked 179 | { 180 | return new Cc((int)(x.value * y)); 181 | } 182 | } 183 | 184 | 185 | public static Cc operator /(Cc x, int y) 186 | { 187 | checked 188 | { 189 | return new Cc((int)(x.value / y)); 190 | } 191 | } 192 | 193 | // UnitGenerateOptions.Comparable 194 | 195 | public int CompareTo(Cc other) 196 | { 197 | return value.CompareTo(other.value); 198 | } 199 | public static bool operator >(Cc x, Cc y) 200 | { 201 | return x.value > y.value; 202 | } 203 | 204 | public static bool operator <(Cc x, Cc y) 205 | { 206 | return x.value < y.value; 207 | } 208 | 209 | public static bool operator >=(Cc x, Cc y) 210 | { 211 | return x.value >= y.value; 212 | } 213 | 214 | public static bool operator <=(Cc x, Cc y) 215 | { 216 | return x.value <= y.value; 217 | } 218 | 219 | // Default 220 | 221 | private class CcTypeConverter : System.ComponentModel.TypeConverter 222 | { 223 | private static readonly Type WrapperType = typeof(Cc); 224 | private static readonly Type ValueType = typeof(int); 225 | 226 | public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, Type sourceType) 227 | { 228 | if (sourceType == WrapperType || sourceType == ValueType) 229 | { 230 | return true; 231 | } 232 | 233 | return base.CanConvertFrom(context, sourceType); 234 | } 235 | 236 | public override bool CanConvertTo(System.ComponentModel.ITypeDescriptorContext context, Type destinationType) 237 | { 238 | if (destinationType == WrapperType || destinationType == ValueType) 239 | { 240 | return true; 241 | } 242 | 243 | return base.CanConvertTo(context, destinationType); 244 | } 245 | 246 | public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) 247 | { 248 | if (value != null) 249 | { 250 | var t = value.GetType(); 251 | if (t == typeof(Cc)) 252 | { 253 | return (Cc)value; 254 | } 255 | if (t == typeof(int)) 256 | { 257 | return new Cc((int)value); 258 | } 259 | } 260 | 261 | return base.ConvertFrom(context, culture, value); 262 | } 263 | 264 | public override object ConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) 265 | { 266 | if (value is Cc wrappedValue) 267 | { 268 | if (destinationType == WrapperType) 269 | { 270 | return wrappedValue; 271 | } 272 | 273 | if (destinationType == ValueType) 274 | { 275 | return wrappedValue.AsPrimitive(); 276 | } 277 | } 278 | 279 | return base.ConvertTo(context, culture, value, destinationType); 280 | } 281 | } 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/FileGenerate.D.g.cs: -------------------------------------------------------------------------------- 1 | // 2 | // THIS (.cs) FILE IS GENERATED BY UnitGenerator. DO NOT CHANGE IT. 3 | // 4 | #pragma warning disable CS8669 5 | using System; 6 | using System.Globalization; 7 | #if NET7_0_OR_GREATER 8 | using System.Numerics; 9 | #endif 10 | namespace FileGenerate 11 | { 12 | [System.ComponentModel.TypeConverter(typeof(DTypeConverter))] 13 | readonly partial struct D 14 | : IEquatable 15 | , IComparable 16 | , IFormattable 17 | #if NET6_0_OR_GREATER 18 | , ISpanFormattable 19 | #endif 20 | #if NET7_0_OR_GREATER 21 | , IComparisonOperators 22 | #endif 23 | #if NET8_0_OR_GREATER 24 | , IEqualityOperators 25 | , IUtf8SpanFormattable 26 | #endif 27 | { 28 | readonly System.Guid value; 29 | 30 | public System.Guid AsPrimitive() => value; 31 | 32 | public D(System.Guid value) 33 | { 34 | this.value = value; 35 | } 36 | 37 | public static explicit operator System.Guid(D value) 38 | { 39 | return value.value; 40 | } 41 | 42 | public static explicit operator D(System.Guid value) 43 | { 44 | return new D(value); 45 | } 46 | 47 | public bool Equals(D other) 48 | { 49 | return value.Equals(other.value); 50 | } 51 | 52 | public override bool Equals(object obj) 53 | { 54 | if (obj == null) return false; 55 | var t = obj.GetType(); 56 | if (t == typeof(D)) 57 | { 58 | return Equals((D)obj); 59 | } 60 | if (t == typeof(System.Guid)) 61 | { 62 | return value.Equals((System.Guid)obj); 63 | } 64 | 65 | return value.Equals(obj); 66 | } 67 | 68 | public static bool operator ==(D x, D y) 69 | { 70 | return x.value.Equals(y.value); 71 | } 72 | 73 | public static bool operator !=(D x, D y) 74 | { 75 | return !x.value.Equals(y.value); 76 | } 77 | 78 | public override int GetHashCode() 79 | { 80 | return value.GetHashCode(); 81 | } 82 | 83 | public override string ToString() => value.ToString(); 84 | 85 | public string ToString(string? format, IFormatProvider? formatProvider) => value.ToString(format, formatProvider); 86 | 87 | #if NET6_0_OR_GREATER 88 | public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => 89 | ((ISpanFormattable)value).TryFormat(destination, out charsWritten, format, provider); 90 | #endif 91 | #if NET8_0_OR_GREATER 92 | public bool TryFormat (Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) => 93 | ((IUtf8SpanFormattable)value).TryFormat(utf8Destination, out bytesWritten, format, provider); 94 | #endif 95 | 96 | public static readonly D Empty = default(D); 97 | 98 | public static D New() 99 | { 100 | return new D(Guid.NewGuid()); 101 | } 102 | 103 | public static D NewD() 104 | { 105 | return new D(Guid.NewGuid()); 106 | } 107 | 108 | // UnitGenerateOptions.ParseMethod 109 | 110 | public static D Parse(string s) 111 | { 112 | return new D(System.Guid.Parse(s)); 113 | } 114 | 115 | public static bool TryParse(string s, out D result) 116 | { 117 | if (System.Guid.TryParse(s, out var r)) 118 | { 119 | result = new D(r); 120 | return true; 121 | } 122 | else 123 | { 124 | result = default(D); 125 | return false; 126 | } 127 | } 128 | 129 | // UnitGenerateOptions.Comparable 130 | 131 | public int CompareTo(D other) 132 | { 133 | return value.CompareTo(other.value); 134 | } 135 | public static bool operator >(D x, D y) 136 | { 137 | return x.value > y.value; 138 | } 139 | 140 | public static bool operator <(D x, D y) 141 | { 142 | return x.value < y.value; 143 | } 144 | 145 | public static bool operator >=(D x, D y) 146 | { 147 | return x.value >= y.value; 148 | } 149 | 150 | public static bool operator <=(D x, D y) 151 | { 152 | return x.value <= y.value; 153 | } 154 | 155 | // Default 156 | 157 | private class DTypeConverter : System.ComponentModel.TypeConverter 158 | { 159 | private static readonly Type WrapperType = typeof(D); 160 | private static readonly Type ValueType = typeof(System.Guid); 161 | 162 | public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, Type sourceType) 163 | { 164 | if (sourceType == WrapperType || sourceType == ValueType) 165 | { 166 | return true; 167 | } 168 | 169 | return base.CanConvertFrom(context, sourceType); 170 | } 171 | 172 | public override bool CanConvertTo(System.ComponentModel.ITypeDescriptorContext context, Type destinationType) 173 | { 174 | if (destinationType == WrapperType || destinationType == ValueType) 175 | { 176 | return true; 177 | } 178 | 179 | return base.CanConvertTo(context, destinationType); 180 | } 181 | 182 | public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) 183 | { 184 | if (value != null) 185 | { 186 | var t = value.GetType(); 187 | if (t == typeof(D)) 188 | { 189 | return (D)value; 190 | } 191 | if (t == typeof(System.Guid)) 192 | { 193 | return new D((System.Guid)value); 194 | } 195 | } 196 | 197 | return base.ConvertFrom(context, culture, value); 198 | } 199 | 200 | public override object ConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) 201 | { 202 | if (value is D wrappedValue) 203 | { 204 | if (destinationType == WrapperType) 205 | { 206 | return wrappedValue; 207 | } 208 | 209 | if (destinationType == ValueType) 210 | { 211 | return wrappedValue.AsPrimitive(); 212 | } 213 | } 214 | 215 | return base.ConvertTo(context, culture, value, destinationType); 216 | } 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /sandbox/Generated/UnitGenerator/UnitGenerator.SourceGenerator/UnitOfAttribute.cs: -------------------------------------------------------------------------------- 1 | // 2 | // THIS (.cs) FILE IS GENERATED BY UnitGenerator. DO NOT CHANGE IT. 3 | // 4 | #nullable enable 5 | using System; 6 | #if NET7_0_OR_GREATER 7 | using System.Numerics; 8 | #endif 9 | 10 | namespace UnitGenerator 11 | { 12 | [AttributeUsage(AttributeTargets.Struct, AllowMultiple = false)] 13 | internal class UnitOfAttribute : Attribute 14 | { 15 | public Type Type { get; } 16 | public UnitGenerateOptions Options { get; } 17 | public UnitArithmeticOperators ArithmeticOperators { get; set; } = UnitArithmeticOperators.All; 18 | public string? ToStringFormat { get; set; } 19 | 20 | public UnitOfAttribute(Type type, UnitGenerateOptions options = UnitGenerateOptions.None) 21 | { 22 | this.Type = type; 23 | this.Options = options; 24 | } 25 | } 26 | 27 | #if NET7_0_OR_GREATER 28 | [AttributeUsage(AttributeTargets.Struct, AllowMultiple = false)] 29 | internal class UnitOfAttribute : Attribute 30 | { 31 | public Type Type { get; } 32 | public UnitGenerateOptions Options { get; } 33 | public UnitArithmeticOperators ArithmeticOperators { get; set; } = UnitArithmeticOperators.All; 34 | public string? ToStringFormat { get; set; } 35 | 36 | public UnitOfAttribute(UnitGenerateOptions options = UnitGenerateOptions.None) 37 | { 38 | this.Type = typeof(T); 39 | this.Options = options; 40 | } 41 | } 42 | #endif 43 | 44 | [Flags] 45 | internal enum UnitGenerateOptions 46 | { 47 | None = 0, 48 | ImplicitOperator = 1, 49 | ParseMethod = 1 << 1, 50 | MinMaxMethod = 1 << 2, 51 | ArithmeticOperator = 1 << 3, 52 | ValueArithmeticOperator = 1 << 4, 53 | Comparable = 1 << 5, 54 | Validate = 1 << 6, 55 | JsonConverter = 1 << 7, 56 | MessagePackFormatter = 1 << 8, 57 | DapperTypeHandler = 1 << 9, 58 | EntityFrameworkValueConverter = 1 << 10, 59 | WithoutComparisonOperator = 1 << 11, 60 | JsonConverterDictionaryKeySupport = 1 << 12, 61 | Normalize = 1 << 13, 62 | } 63 | 64 | [Flags] 65 | internal enum UnitArithmeticOperators 66 | { 67 | All = Addition | Subtraction | Multiply | Division | Increment | Decrement, 68 | Addition = 1, 69 | Subtraction = 1 << 1, 70 | Multiply = 1 << 2, 71 | Division = 1 << 3, 72 | Increment = 1 << 4, 73 | Decrement = 1 << 5, 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/EntityFrameworkApp/DbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using UnitGenerator; 3 | 4 | namespace EntityFrameworkApp; 5 | 6 | [UnitOf(typeof(int), 7 | UnitGenerateOptions.ParseMethod | 8 | UnitGenerateOptions.EntityFrameworkValueConverter)] 9 | public readonly partial struct UserId { } 10 | 11 | public class User 12 | { 13 | public UserId UserId { get; set; } 14 | } 15 | 16 | public class SampleDbContext : DbContext 17 | { 18 | public SampleDbContext() { } 19 | public SampleDbContext(DbContextOptions options) : base(options) { } 20 | 21 | public DbSet Users { get; set; } = default!; 22 | 23 | protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) 24 | { 25 | configurationBuilder.Properties().HaveConversion(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/EntityFrameworkApp/EntityFrameworkApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/EntityFrameworkApp/Program.cs: -------------------------------------------------------------------------------- 1 | using EntityFrameworkApp; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | var options = new DbContextOptionsBuilder() 5 | .UseInMemoryDatabase("SampleDb") 6 | .Options; 7 | var context = new SampleDbContext(options); 8 | var user = context.Users.SingleOrDefault(x => x.UserId == new UserId(1)); 9 | 10 | Console.WriteLine("Hello, World!"); -------------------------------------------------------------------------------- /src/UnitGenerator/CodeDomShims.cs: -------------------------------------------------------------------------------- 1 | namespace System.CodeDom.Compiler 2 | { 3 | public class CompilerError 4 | { 5 | public string? ErrorText { get; set; } 6 | public bool IsWarning { get; set; } 7 | } 8 | 9 | public class CompilerErrorCollection 10 | { 11 | public void Add(CompilerError error) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/UnitGenerator/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cysharp/UnitGenerator/73e58634afb45b2613ac1e15153245aff5f67b82/src/UnitGenerator/Icon.png -------------------------------------------------------------------------------- /src/UnitGenerator/IgnoreEquality.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace UnitGenerator; 4 | 5 | public readonly struct IgnoreEquality(T value) : IEquatable> 6 | { 7 | public readonly T Value => value; 8 | 9 | public static implicit operator IgnoreEquality(T value) 10 | { 11 | return new IgnoreEquality(value); 12 | } 13 | 14 | public static implicit operator T(IgnoreEquality value) 15 | { 16 | return value.Value; 17 | } 18 | 19 | public bool Equals(IgnoreEquality other) 20 | { 21 | // always true to ignore equality check. 22 | return true; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/UnitGenerator/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "UnitGenerator": { 4 | "commandName": "DebugRoslynComponent", 5 | "targetProject": "..\\..\\sandbox\\ConsoleApp\\ConsoleApp.csproj" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/UnitGenerator/ReferenceSymbols.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.CodeAnalysis; 3 | 4 | namespace UnitGenerator; 5 | 6 | class ReferenceSymbols 7 | { 8 | public static ReferenceSymbols Create(Compilation compilation) 9 | { 10 | return new ReferenceSymbols 11 | { 12 | GuidType = compilation.GetTypeByMetadataName("System.Guid")!, 13 | UlidType = compilation.GetTypeByMetadataName("System.Ulid"), 14 | FormattableInterface = compilation.GetTypeByMetadataName("System.IFormattable")!, 15 | ParsableInterface = compilation.GetTypeByMetadataName("System.IParsable`1"), 16 | SpanFormattableInterface = compilation.GetTypeByMetadataName("System.ISpanFormattable"), 17 | SpanParsableInterface = compilation.GetTypeByMetadataName("System.ISpanParsable`1"), 18 | Utf8SpanFormattableInterface = compilation.GetTypeByMetadataName("System.IUtf8SpanFormattable"), 19 | Utf8SpanParsableInterface = compilation.GetTypeByMetadataName("System.IUtf8SpanParsable`1"), 20 | }; 21 | } 22 | 23 | public INamedTypeSymbol GuidType { get; private set; } = default!; 24 | public INamedTypeSymbol? UlidType { get; private set; } 25 | public INamedTypeSymbol FormattableInterface { get; private set; } = default!; 26 | public INamedTypeSymbol? ParsableInterface { get; private set; } 27 | public INamedTypeSymbol? SpanFormattableInterface { get; private set; } 28 | public INamedTypeSymbol? SpanParsableInterface { get; private set; } 29 | public INamedTypeSymbol? Utf8SpanFormattableInterface { get; private set; } 30 | public INamedTypeSymbol? Utf8SpanParsableInterface { get; private set; } 31 | } -------------------------------------------------------------------------------- /src/UnitGenerator/SourceGenerator2.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp.Syntax; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | using System.Text; 6 | using System.Threading; 7 | 8 | namespace UnitGenerator; 9 | 10 | [Generator(LanguageNames.CSharp)] 11 | public class SourceGenerator2 : IIncrementalGenerator 12 | { 13 | public void Initialize(IncrementalGeneratorInitializationContext context) 14 | { 15 | context.RegisterPostInitializationOutput(EmitDefaultAttribute); 16 | 17 | var source1 = context.SyntaxProvider.ForAttributeWithMetadataName( 18 | "UnitGenerator.UnitOfAttribute", 19 | static (node, token) => node is StructDeclarationSyntax, // target only struct 20 | ParseUnitOfAttribute); 21 | 22 | var source2 = context.SyntaxProvider.ForAttributeWithMetadataName( 23 | "UnitGenerator.UnitOfAttribute`1", 24 | static (node, token) => node is StructDeclarationSyntax, // target only struct 25 | ParseUnitOfAttribute); 26 | 27 | context.RegisterSourceOutput(source1, EmitSourceOutput); 28 | context.RegisterSourceOutput(source2, EmitSourceOutput); 29 | } 30 | 31 | static void EmitDefaultAttribute(IncrementalGeneratorPostInitializationContext context) 32 | { 33 | // lang=C#-Test 34 | var attrCode = """ 35 | // 36 | // THIS (.cs) FILE IS GENERATED BY UnitGenerator. DO NOT CHANGE IT. 37 | // 38 | #nullable enable 39 | #pragma warning disable 40 | using System; 41 | #if NET7_0_OR_GREATER 42 | using System.Numerics; 43 | #endif 44 | 45 | namespace UnitGenerator 46 | { 47 | [AttributeUsage(AttributeTargets.Struct, AllowMultiple = false)] 48 | internal class UnitOfAttribute : Attribute 49 | { 50 | public Type Type { get; } 51 | public UnitGenerateOptions Options { get; } 52 | public UnitArithmeticOperators ArithmeticOperators { get; set; } = UnitArithmeticOperators.All; 53 | public string? ToStringFormat { get; set; } 54 | 55 | public UnitOfAttribute(Type type, UnitGenerateOptions options = UnitGenerateOptions.None) 56 | { 57 | this.Type = type; 58 | this.Options = options; 59 | } 60 | } 61 | 62 | #if NET7_0_OR_GREATER 63 | [AttributeUsage(AttributeTargets.Struct, AllowMultiple = false)] 64 | internal class UnitOfAttribute : Attribute 65 | { 66 | public Type Type { get; } 67 | public UnitGenerateOptions Options { get; } 68 | public UnitArithmeticOperators ArithmeticOperators { get; set; } = UnitArithmeticOperators.All; 69 | public string? ToStringFormat { get; set; } 70 | 71 | public UnitOfAttribute(UnitGenerateOptions options = UnitGenerateOptions.None) 72 | { 73 | this.Type = typeof(T); 74 | this.Options = options; 75 | } 76 | } 77 | #endif 78 | 79 | [Flags] 80 | internal enum UnitGenerateOptions 81 | { 82 | None = 0, 83 | ImplicitOperator = 1, 84 | ParseMethod = 1 << 1, 85 | MinMaxMethod = 1 << 2, 86 | ArithmeticOperator = 1 << 3, 87 | ValueArithmeticOperator = 1 << 4, 88 | Comparable = 1 << 5, 89 | Validate = 1 << 6, 90 | JsonConverter = 1 << 7, 91 | MessagePackFormatter = 1 << 8, 92 | DapperTypeHandler = 1 << 9, 93 | EntityFrameworkValueConverter = 1 << 10, 94 | WithoutComparisonOperator = 1 << 11, 95 | JsonConverterDictionaryKeySupport = 1 << 12, 96 | Normalize = 1 << 13, 97 | } 98 | 99 | [Flags] 100 | internal enum UnitArithmeticOperators 101 | { 102 | All = Addition | Subtraction | Multiply | Division | Increment | Decrement, 103 | Addition = 1, 104 | Subtraction = 1 << 1, 105 | Multiply = 1 << 2, 106 | Division = 1 << 3, 107 | Increment = 1 << 4, 108 | Decrement = 1 << 5, 109 | } 110 | } 111 | 112 | """; 113 | context.AddSource("UnitOfAttribute.g.cs", attrCode); 114 | } 115 | 116 | static UnitOfAttributeProperty ParseUnitOfAttribute(GeneratorAttributeSyntaxContext target, CancellationToken _) 117 | { 118 | var model = target.SemanticModel; 119 | var compilation = model.Compilation; 120 | var targetType = (INamedTypeSymbol)target.TargetSymbol; 121 | var attr = target.Attributes[0]; // AllowMultiple = false 122 | 123 | var symbols = ReferenceSymbols.Create(compilation); 124 | 125 | UnitOfAttributeProperty prop = null!; 126 | 127 | if (attr.AttributeClass!.IsGenericType) 128 | { 129 | // UnitOfAttribute(UnitGenerateOptions options = UnitGenerateOptions.None) 130 | prop = new UnitOfAttributeProperty(targetType, attr.AttributeClass.TypeArguments[0], symbols); 131 | if (attr.ConstructorArguments.Length != 0) 132 | { 133 | prop.Options = (UnitGenerateOptions)attr.ConstructorArguments[0].Value!; 134 | } 135 | } 136 | else 137 | { 138 | // UnitOfAttribute(Type type, UnitGenerateOptions options = UnitGenerateOptions.None) 139 | for (int i = 0; i < attr.ConstructorArguments.Length; i++) 140 | { 141 | var arg = attr.ConstructorArguments[i]; 142 | if (i == 0) // Type 143 | { 144 | prop = new(targetType, (ITypeSymbol)attr.ConstructorArguments[0].Value!, symbols); 145 | } 146 | else // UnitGenerateOptions 147 | { 148 | prop.Options = (UnitGenerateOptions)arg.Value!; 149 | } 150 | } 151 | } 152 | 153 | // ToStringFormat, ArithmeticOperators 154 | foreach (var arg in attr.NamedArguments) 155 | { 156 | switch (arg.Key) 157 | { 158 | case "ArithmeticOperators": 159 | prop.ArithmeticOperators = (UnitArithmeticOperators)arg.Value.Value!; 160 | break; 161 | case "ToStringFormat": 162 | prop.ToStringFormat = (string)arg.Value.Value!; 163 | break; 164 | default: 165 | break; 166 | } 167 | } 168 | 169 | return prop; 170 | } 171 | 172 | static void EmitSourceOutput(SourceProductionContext context, UnitOfAttributeProperty prop) 173 | { 174 | var unitTypeName = prop.UnitType.Value.Name; 175 | var innerTypeName = prop.TypeName; 176 | var ns = prop.UnitType.Value.ContainingNamespace.IsGlobalNamespace ? null : prop.UnitType.Value.ContainingNamespace.ToDisplayString(); 177 | 178 | var sb = new StringBuilder(""" 179 | // 180 | // THIS (.cs) FILE IS GENERATED BY UnitGenerator. DO NOT CHANGE IT. 181 | // 182 | #pragma warning disable 183 | using System; 184 | using System.Globalization; 185 | #if NET7_0_OR_GREATER 186 | using System.Numerics; 187 | #endif 188 | 189 | """); 190 | if (prop.HasFlag(UnitGenerateOptions.MessagePackFormatter)) 191 | { 192 | sb.AppendLine(""" 193 | using MessagePack; 194 | using MessagePack.Formatters; 195 | """); 196 | } 197 | if (prop.HasFlag(UnitGenerateOptions.JsonConverter)) 198 | { 199 | sb.AppendLine(""" 200 | using System.Text.Json; 201 | using System.Text.Json.Serialization; 202 | """); 203 | } 204 | if (prop.HasFlag(UnitGenerateOptions.DapperTypeHandler)) 205 | { 206 | sb.AppendLine(""" 207 | using System.Runtime.CompilerServices; 208 | """); 209 | } 210 | if (!string.IsNullOrEmpty(ns)) 211 | { 212 | sb.AppendLine($$""" 213 | namespace {{ns}} 214 | { 215 | """); 216 | } 217 | if (prop.HasFlag(UnitGenerateOptions.JsonConverter)) 218 | { 219 | sb.AppendLine($$""" 220 | [JsonConverter(typeof({{unitTypeName}}JsonConverter))] 221 | """); 222 | } 223 | if (prop.HasFlag(UnitGenerateOptions.MessagePackFormatter)) 224 | { 225 | sb.AppendLine($$""" 226 | [MessagePackFormatter(typeof({{unitTypeName}}MessagePackFormatter))] 227 | """); 228 | } 229 | 230 | var anyPlatformInterfaces = new List(); 231 | var net6Interfaces = new List(); 232 | var net7Interfaces = new List(); 233 | var net8Interfaces = new List 234 | { 235 | $"IEqualityOperators<{unitTypeName}, {unitTypeName}, bool>" 236 | }; 237 | 238 | sb.AppendLine($$""" 239 | [System.ComponentModel.TypeConverter(typeof({{unitTypeName}}TypeConverter))] 240 | readonly partial struct {{unitTypeName}} 241 | : IEquatable<{{unitTypeName}}> 242 | """); 243 | if (prop.HasFlag(UnitGenerateOptions.Comparable)) 244 | { 245 | anyPlatformInterfaces.Add($"IComparable<{unitTypeName}>"); 246 | if (!prop.HasFlag(UnitGenerateOptions.WithoutComparisonOperator)) 247 | { 248 | net7Interfaces.Add($"IComparisonOperators<{unitTypeName}, {unitTypeName}, bool>"); 249 | } 250 | } 251 | if (prop.HasFormattableInterface()) 252 | { 253 | anyPlatformInterfaces.Add("IFormattable"); 254 | } 255 | if (prop.HasSpanFormattableInterface()) 256 | { 257 | net6Interfaces.Add($"ISpanFormattable"); 258 | } 259 | if (prop.HasUtf8SpanFormattableInterface()) 260 | { 261 | net8Interfaces.Add($"IUtf8SpanFormattable"); 262 | } 263 | if (prop.HasFlag(UnitGenerateOptions.ParseMethod)) 264 | { 265 | if (prop.HasParsableInterface()) 266 | { 267 | net7Interfaces.Add($"IParsable<{unitTypeName}>"); 268 | } 269 | if (prop.HasSpanParsableInterface()) 270 | { 271 | net7Interfaces.Add($"ISpanParsable<{unitTypeName}>"); 272 | } 273 | if (prop.HasUtf8SpanParsableInterface()) 274 | { 275 | net8Interfaces.Add($"IUtf8SpanParsable<{unitTypeName}>"); 276 | } 277 | } 278 | 279 | if (prop.HasFlag(UnitGenerateOptions.ArithmeticOperator)) 280 | { 281 | if (prop.HasArithmeticOperator(UnitArithmeticOperators.Addition)) 282 | { 283 | net7Interfaces.Add($"IAdditionOperators<{unitTypeName}, {unitTypeName}, {unitTypeName}>"); 284 | net7Interfaces.Add($"IUnaryPlusOperators<{unitTypeName}, {unitTypeName}>"); 285 | } 286 | if (prop.HasArithmeticOperator(UnitArithmeticOperators.Subtraction)) 287 | { 288 | net7Interfaces.Add($"ISubtractionOperators<{unitTypeName}, {unitTypeName}, {unitTypeName}>"); 289 | net7Interfaces.Add($"IUnaryNegationOperators<{unitTypeName}, {unitTypeName}>"); 290 | } 291 | if (prop.HasArithmeticOperator(UnitArithmeticOperators.Multiply)) 292 | { 293 | net7Interfaces.Add($"IMultiplyOperators<{unitTypeName}, {unitTypeName}, {unitTypeName}>"); 294 | } 295 | if (prop.HasArithmeticOperator(UnitArithmeticOperators.Division)) 296 | { 297 | net7Interfaces.Add($"IDivisionOperators<{unitTypeName}, {unitTypeName}, {unitTypeName}>"); 298 | } 299 | if (prop.HasArithmeticOperator(UnitArithmeticOperators.Increment)) 300 | { 301 | net7Interfaces.Add($"IIncrementOperators<{unitTypeName}>"); 302 | } 303 | if (prop.HasArithmeticOperator(UnitArithmeticOperators.Decrement)) 304 | { 305 | net7Interfaces.Add($"IDecrementOperators<{unitTypeName}>"); 306 | } 307 | } 308 | 309 | foreach (var interfaceName in anyPlatformInterfaces) 310 | { 311 | sb.AppendLine($" , {interfaceName}"); 312 | } 313 | if (net6Interfaces.Count > 0) 314 | { 315 | sb.AppendLine("#if NET6_0_OR_GREATER"); 316 | } 317 | foreach (var interfaceName in net6Interfaces) 318 | { 319 | sb.AppendLine($" , {interfaceName}"); 320 | } 321 | if (net6Interfaces.Count > 0) 322 | { 323 | sb.AppendLine("#endif"); 324 | } 325 | 326 | if (net7Interfaces.Count > 0) 327 | { 328 | sb.AppendLine("#if NET7_0_OR_GREATER"); 329 | } 330 | foreach (var interfaceName in net7Interfaces) 331 | { 332 | sb.AppendLine($" , {interfaceName}"); 333 | } 334 | if (net7Interfaces.Count > 0) 335 | { 336 | sb.AppendLine("#endif"); 337 | } 338 | 339 | if (net8Interfaces.Count > 0) 340 | { 341 | sb.AppendLine("#if NET8_0_OR_GREATER"); 342 | } 343 | foreach (var interfaceName in net8Interfaces) 344 | { 345 | sb.AppendLine($" , {interfaceName}"); 346 | } 347 | if (net8Interfaces.Count > 0) 348 | { 349 | sb.AppendLine("#endif"); 350 | } 351 | 352 | sb.AppendLine($$""" 353 | { 354 | readonly {{innerTypeName}} value; 355 | 356 | public {{innerTypeName}} AsPrimitive() => value; 357 | 358 | public {{unitTypeName}}({{innerTypeName}} value) 359 | { 360 | this.value = value; 361 | """); 362 | if (prop.HasFlag(UnitGenerateOptions.Normalize)) 363 | { 364 | sb.AppendLine(""" 365 | this.Normalize(ref this.value); 366 | """); 367 | } 368 | if (prop.HasFlag(UnitGenerateOptions.Validate)) 369 | { 370 | sb.AppendLine(""" 371 | this.Validate(); 372 | """); 373 | } 374 | sb.AppendLine(""" 375 | } 376 | 377 | """); 378 | if (prop.HasFlag(UnitGenerateOptions.Normalize)) 379 | { 380 | sb.AppendLine($$""" 381 | private partial void Normalize(ref {{innerTypeName}} value); 382 | 383 | """); 384 | 385 | } 386 | 387 | if (prop.HasFlag(UnitGenerateOptions.Validate)) 388 | { 389 | sb.AppendLine(""" 390 | private partial void Validate(); 391 | 392 | """); 393 | } 394 | 395 | var convertModifier = prop.HasFlag(UnitGenerateOptions.ImplicitOperator) ? "implicit" : "explicit"; 396 | sb.AppendLine($$""" 397 | public static {{convertModifier}} operator {{innerTypeName}}({{unitTypeName}} value) 398 | { 399 | return value.value; 400 | } 401 | 402 | public static {{convertModifier}} operator {{unitTypeName}}({{innerTypeName}} value) 403 | { 404 | return new {{unitTypeName}}(value); 405 | } 406 | 407 | public bool Equals({{unitTypeName}} other) 408 | { 409 | return {{(prop.IsReferenceType() ? $"System.Collections.Generic.EqualityComparer<{innerTypeName}>.Default.Equals(value, other.value)" : "value.Equals(other.value)")}}; 410 | } 411 | 412 | public override bool Equals(object obj) 413 | { 414 | if (obj == null) return false; 415 | var t = obj.GetType(); 416 | if (t == typeof({{unitTypeName}})) 417 | { 418 | return Equals(({{unitTypeName}})obj); 419 | } 420 | if (t == typeof({{innerTypeName}})) 421 | { 422 | return value.Equals(({{innerTypeName}})obj); 423 | } 424 | 425 | return value.Equals(obj); 426 | } 427 | 428 | public static bool operator ==({{unitTypeName}} x, {{unitTypeName}} y) 429 | { 430 | return System.Collections.Generic.EqualityComparer<{{innerTypeName}}>.Default.Equals(x.value, y.value); 431 | } 432 | 433 | public static bool operator !=({{unitTypeName}} x, {{unitTypeName}} y) 434 | { 435 | return !System.Collections.Generic.EqualityComparer<{{innerTypeName}}>.Default.Equals(x.value, y.value); 436 | } 437 | 438 | public override int GetHashCode() 439 | { 440 | return {{(prop.IsReferenceType() ? "value?.GetHashCode() ?? 0" : "value.GetHashCode()")}}; 441 | } 442 | 443 | """); 444 | 445 | if (prop.IsString()) 446 | { 447 | if (prop.ToStringFormat is { } format) 448 | { 449 | sb.AppendLine($$""" 450 | public override string ToString() => value == null ? "null" : string.Format("{{format}}", value); 451 | 452 | """); 453 | } 454 | else 455 | { 456 | sb.AppendLine(""" 457 | public override string ToString() => value == null ? "null" : value.ToString(); 458 | 459 | """); 460 | } 461 | } 462 | else 463 | { 464 | if (prop.ToStringFormat is { } format) 465 | { 466 | sb.AppendLine($$""" 467 | public override string ToString() => string.Format("{{format}}", value); 468 | 469 | """); 470 | } 471 | else 472 | { 473 | sb.AppendLine(""" 474 | public override string ToString() => value.ToString(); 475 | 476 | """); 477 | } 478 | } 479 | 480 | if (prop.HasFormattableInterface()) 481 | { 482 | sb.AppendLine(""" 483 | public string ToString(string? format, IFormatProvider? formatProvider) => value.ToString(format, formatProvider); 484 | 485 | """); 486 | } 487 | if (prop.HasSpanFormattableInterface()) 488 | { 489 | sb.AppendLine(""" 490 | #if NET6_0_OR_GREATER 491 | public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => 492 | ((ISpanFormattable)value).TryFormat(destination, out charsWritten, format, provider); 493 | #endif 494 | """); 495 | } 496 | if (prop.HasUtf8SpanFormattableInterface()) 497 | { 498 | sb.AppendLine(""" 499 | #if NET8_0_OR_GREATER 500 | public bool TryFormat (Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) => 501 | ((IUtf8SpanFormattable)value).TryFormat(utf8Destination, out bytesWritten, format, provider); 502 | #endif 503 | 504 | """); 505 | } 506 | 507 | if (prop.IsGuid()) 508 | { 509 | sb.AppendLine($$""" 510 | public static readonly {{unitTypeName}} Empty = default({{unitTypeName}}); 511 | 512 | public static {{unitTypeName}} New() 513 | { 514 | return new {{unitTypeName}}(Guid.NewGuid()); 515 | } 516 | 517 | public static {{unitTypeName}} New{{unitTypeName}}() 518 | { 519 | return new {{unitTypeName}}(Guid.NewGuid()); 520 | } 521 | 522 | #if NET9_0_OR_GREATER 523 | public static {{unitTypeName}} New(bool uuidV7) 524 | { 525 | return new {{unitTypeName}}(uuidV7 ? Guid.CreateVersion7() : Guid.NewGuid()); 526 | } 527 | 528 | public static {{unitTypeName}} New{{unitTypeName}}(bool uuidV7) 529 | { 530 | return new {{unitTypeName}}(uuidV7 ? Guid.CreateVersion7() : Guid.NewGuid()); 531 | } 532 | #endif 533 | 534 | """); 535 | } 536 | 537 | if (prop.IsUlid()) 538 | { 539 | sb.AppendLine($$""" 540 | public static readonly {{unitTypeName}} Empty = default({{unitTypeName}}); 541 | 542 | public static {{unitTypeName}} New() 543 | { 544 | return new {{unitTypeName}}(Ulid.NewUlid()); 545 | } 546 | 547 | public static {{unitTypeName}} New{{unitTypeName}}() 548 | { 549 | return new {{unitTypeName}}(Ulid.NewUlid()); 550 | } 551 | 552 | """); 553 | } 554 | 555 | if (prop.HasFlag(UnitGenerateOptions.ParseMethod)) 556 | { 557 | sb.AppendLine(""" 558 | // UnitGenerateOptions.ParseMethod 559 | 560 | """); 561 | if (prop.IsString()) 562 | { 563 | sb.AppendLine($$""" 564 | public static {{unitTypeName}} Parse(string s) 565 | { 566 | return new {{unitTypeName}}(s); 567 | } 568 | 569 | public static bool TryParse(string s, out {{unitTypeName}} result) 570 | { 571 | try 572 | { 573 | result = Parse(s); 574 | return true; 575 | } 576 | catch 577 | { 578 | result = default; 579 | return false; 580 | } 581 | } 582 | 583 | """); 584 | } 585 | else 586 | { 587 | sb.AppendLine($$""" 588 | public static {{unitTypeName}} Parse(string s) 589 | { 590 | return new {{unitTypeName}}({{innerTypeName}}.Parse(s)); 591 | } 592 | 593 | public static bool TryParse(string s, out {{unitTypeName}} result) 594 | { 595 | if ({{innerTypeName}}.TryParse(s, out var r)) 596 | { 597 | result = new {{unitTypeName}}(r); 598 | return true; 599 | } 600 | else 601 | { 602 | result = default({{unitTypeName}}); 603 | return false; 604 | } 605 | } 606 | 607 | """); 608 | } 609 | 610 | if (prop.HasParsableInterface()) 611 | { 612 | sb.AppendLine($$""" 613 | #if NET7_0_OR_GREATER 614 | public static {{unitTypeName}} Parse(string s, IFormatProvider? provider) 615 | { 616 | return new {{unitTypeName}}({{innerTypeName}}.Parse(s, provider)); 617 | } 618 | 619 | public static bool TryParse(string s, IFormatProvider? provider, out {{unitTypeName}} result) 620 | { 621 | if ({{innerTypeName}}.TryParse(s, provider, out var r)) 622 | { 623 | result = new {{unitTypeName}}(r); 624 | return true; 625 | } 626 | else 627 | { 628 | result = default({{unitTypeName}}); 629 | return false; 630 | } 631 | } 632 | #endif 633 | 634 | """); 635 | } 636 | if (prop.HasSpanParsableInterface()) 637 | { 638 | sb.AppendLine($$""" 639 | #if NET7_0_OR_GREATER 640 | public static {{unitTypeName}} Parse(ReadOnlySpan s, IFormatProvider? provider) 641 | { 642 | return new {{unitTypeName}}({{innerTypeName}}.Parse(s, provider)); 643 | } 644 | 645 | public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, out {{unitTypeName}} result) 646 | { 647 | if ({{innerTypeName}}.TryParse(s, provider, out var r)) 648 | { 649 | result = new {{unitTypeName}}(r); 650 | return true; 651 | } 652 | else 653 | { 654 | result = default({{unitTypeName}}); 655 | return false; 656 | } 657 | } 658 | #endif 659 | 660 | """); 661 | } 662 | 663 | if (prop.HasUtf8SpanParsableInterface()) 664 | { 665 | sb.AppendLine($$""" 666 | #if NET8_0_OR_GREATER 667 | public static {{unitTypeName}} Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) 668 | { 669 | return new {{unitTypeName}}({{innerTypeName}}.Parse(utf8Text, provider)); 670 | } 671 | 672 | public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out {{unitTypeName}} result) 673 | { 674 | if ({{innerTypeName}}.TryParse(utf8Text, provider, out var r)) 675 | { 676 | result = new {{unitTypeName}}(r); 677 | return true; 678 | } 679 | else 680 | { 681 | result = default({{unitTypeName}}); 682 | return false; 683 | } 684 | } 685 | #endif 686 | 687 | """); 688 | } 689 | } 690 | 691 | if (prop.HasFlag(UnitGenerateOptions.MinMaxMethod)) 692 | { 693 | sb.AppendLine($$""" 694 | // UnitGenerateOptions.MinMaxMethod 695 | 696 | public static {{unitTypeName}} Min({{unitTypeName}} x, {{unitTypeName}} y) 697 | { 698 | return new {{unitTypeName}}(Math.Min(x.value, y.value)); 699 | } 700 | 701 | public static {{unitTypeName}} Max({{unitTypeName}} x, {{unitTypeName}} y) 702 | { 703 | return new {{unitTypeName}}(Math.Max(x.value, y.value)); 704 | } 705 | 706 | """); 707 | } 708 | 709 | if (prop.IsBool()) 710 | { 711 | sb.AppendLine($$""" 712 | // Default 713 | 714 | public static {{innerTypeName}} operator true({{unitTypeName}} x) 715 | { 716 | return x.value; 717 | } 718 | 719 | public static {{innerTypeName}} operator false({{unitTypeName}} x) 720 | { 721 | return !x.value; 722 | } 723 | 724 | public static {{innerTypeName}} operator !({{unitTypeName}} x) 725 | { 726 | return !x.value; 727 | } 728 | 729 | """); 730 | } 731 | 732 | if (prop.HasFlag(UnitGenerateOptions.ArithmeticOperator)) 733 | { 734 | sb.AppendLine(""" 735 | // UnitGenerateOptions.ArithmeticOperator 736 | 737 | """); 738 | if (prop.HasArithmeticOperator(UnitArithmeticOperators.Addition)) 739 | { 740 | sb.AppendLine($$""" 741 | public static {{unitTypeName}} operator +({{unitTypeName}} x, {{unitTypeName}} y) 742 | { 743 | checked 744 | { 745 | return new {{unitTypeName}}(({{innerTypeName}})(x.value + y.value)); 746 | } 747 | } 748 | 749 | """); 750 | sb.AppendLine($$""" 751 | public static {{unitTypeName}} operator +({{unitTypeName}} value) => new(({{innerTypeName}})(+value.value)); 752 | """); 753 | } 754 | if (prop.HasArithmeticOperator(UnitArithmeticOperators.Subtraction)) 755 | { 756 | sb.AppendLine($$""" 757 | public static {{unitTypeName}} operator -({{unitTypeName}} x, {{unitTypeName}} y) 758 | { 759 | checked 760 | { 761 | return new {{unitTypeName}}(({{innerTypeName}})(x.value - y.value)); 762 | } 763 | } 764 | 765 | """); 766 | string zero = ""; 767 | if (innerTypeName == "ulong") 768 | { 769 | zero = "0UL "; 770 | } 771 | 772 | sb.AppendLine($$""" 773 | public static {{unitTypeName}} operator -({{unitTypeName}} value) => new(({{innerTypeName}})({{zero}}-value.value)); 774 | 775 | """); 776 | } 777 | 778 | if (prop.HasArithmeticOperator(UnitArithmeticOperators.Multiply) || 779 | prop.HasArithmeticOperator(UnitArithmeticOperators.Division)) 780 | { 781 | if (prop.HasArithmeticOperator(UnitArithmeticOperators.Multiply)) 782 | { 783 | sb.AppendLine($$""" 784 | public static {{unitTypeName}} operator *({{unitTypeName}} x, {{unitTypeName}} y) 785 | { 786 | checked 787 | { 788 | return new {{unitTypeName}}(({{innerTypeName}})(x.value * y.value)); 789 | } 790 | } 791 | 792 | """); 793 | } 794 | if (prop.HasArithmeticOperator(UnitArithmeticOperators.Division)) 795 | { 796 | sb.AppendLine($$""" 797 | 798 | public static {{unitTypeName}} operator /({{unitTypeName}} x, {{unitTypeName}} y) 799 | { 800 | checked 801 | { 802 | return new {{unitTypeName}}(({{innerTypeName}})(x.value / y.value)); 803 | } 804 | } 805 | 806 | """); 807 | } 808 | } // End Multiply, Division 809 | 810 | if (prop.HasArithmeticOperator(UnitArithmeticOperators.Increment)) 811 | { 812 | sb.AppendLine($$""" 813 | public static {{unitTypeName}} operator ++({{unitTypeName}} x) 814 | { 815 | checked 816 | { 817 | return new {{unitTypeName}}(({{innerTypeName}})(({{innerTypeName}})(x.value + 1))); 818 | } 819 | } 820 | 821 | """); 822 | } 823 | if (prop.HasArithmeticOperator(UnitArithmeticOperators.Decrement)) 824 | { 825 | sb.AppendLine($$""" 826 | public static {{unitTypeName}} operator --({{unitTypeName}} x) 827 | { 828 | checked 829 | { 830 | return new {{unitTypeName}}(({{innerTypeName}})(({{innerTypeName}})(x.value - 1))); 831 | } 832 | } 833 | 834 | """); 835 | } 836 | } // End ArithmeticOperator 837 | 838 | if (prop.HasFlag(UnitGenerateOptions.ValueArithmeticOperator)) 839 | { 840 | sb.AppendLine(""" 841 | // UnitGenerateOptions.ValueArithmeticOperator 842 | 843 | """); 844 | if (prop.HasValueArithmeticOperator(UnitArithmeticOperators.Addition)) 845 | { 846 | sb.AppendLine($$""" 847 | public static {{unitTypeName}} operator +({{unitTypeName}} x, {{innerTypeName}} y) 848 | { 849 | checked 850 | { 851 | return new {{unitTypeName}}(({{innerTypeName}})(x.value + y)); 852 | } 853 | } 854 | 855 | """); 856 | } 857 | if (prop.HasValueArithmeticOperator(UnitArithmeticOperators.Subtraction)) 858 | { 859 | sb.AppendLine($$""" 860 | public static {{unitTypeName}} operator -({{unitTypeName}} x, {{innerTypeName}} y) 861 | { 862 | checked 863 | { 864 | return new {{unitTypeName}}(({{innerTypeName}})(x.value - y)); 865 | } 866 | } 867 | 868 | """); 869 | } 870 | if (prop.HasValueArithmeticOperator(UnitArithmeticOperators.Multiply)) 871 | { 872 | sb.AppendLine($$""" 873 | public static {{unitTypeName}} operator *({{unitTypeName}} x, {{innerTypeName}} y) 874 | { 875 | checked 876 | { 877 | return new {{unitTypeName}}(({{innerTypeName}})(x.value * y)); 878 | } 879 | } 880 | 881 | """); 882 | } 883 | if (prop.HasValueArithmeticOperator(UnitArithmeticOperators.Division)) 884 | { 885 | sb.AppendLine($$""" 886 | 887 | public static {{unitTypeName}} operator /({{unitTypeName}} x, {{innerTypeName}} y) 888 | { 889 | checked 890 | { 891 | return new {{unitTypeName}}(({{innerTypeName}})(x.value / y)); 892 | } 893 | } 894 | 895 | """); 896 | } 897 | } // End ValueArithmeticOperator 898 | 899 | if (prop.HasFlag(UnitGenerateOptions.Comparable)) 900 | { 901 | sb.AppendLine($$""" 902 | // UnitGenerateOptions.Comparable 903 | 904 | public int CompareTo({{unitTypeName}} other) 905 | { 906 | return value.CompareTo(other.value); 907 | } 908 | """); 909 | } 910 | if (prop.HasFlag(UnitGenerateOptions.Comparable) && !prop.HasFlag(UnitGenerateOptions.WithoutComparisonOperator)) 911 | { 912 | sb.AppendLine($$""" 913 | public static bool operator >({{unitTypeName}} x, {{unitTypeName}} y) 914 | { 915 | return x.value > y.value; 916 | } 917 | 918 | public static bool operator <({{unitTypeName}} x, {{unitTypeName}} y) 919 | { 920 | return x.value < y.value; 921 | } 922 | 923 | public static bool operator >=({{unitTypeName}} x, {{unitTypeName}} y) 924 | { 925 | return x.value >= y.value; 926 | } 927 | 928 | public static bool operator <=({{unitTypeName}} x, {{unitTypeName}} y) 929 | { 930 | return x.value <= y.value; 931 | } 932 | 933 | """); 934 | } 935 | 936 | if (prop.HasFlag(UnitGenerateOptions.JsonConverter)) 937 | { 938 | sb.AppendLine($$""" 939 | // UnitGenerateOptions.JsonConverter 940 | 941 | private class {{unitTypeName}}JsonConverter : JsonConverter<{{unitTypeName}}> 942 | { 943 | public override void Write(Utf8JsonWriter writer, {{unitTypeName}} value, JsonSerializerOptions options) 944 | { 945 | var converter = options.GetConverter(typeof({{innerTypeName}})) as JsonConverter<{{innerTypeName}}>; 946 | if (converter != null) 947 | { 948 | converter.Write(writer, value.value, options); 949 | } 950 | else 951 | { 952 | throw new JsonException($"{typeof({{innerTypeName}})} converter does not found."); 953 | } 954 | } 955 | 956 | public override {{unitTypeName}} Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 957 | { 958 | var converter = options.GetConverter(typeof({{innerTypeName}})) as JsonConverter<{{innerTypeName}}>; 959 | if (converter != null) 960 | { 961 | return new {{unitTypeName}}(converter.Read(ref reader, typeToConvert, options)); 962 | } 963 | else 964 | { 965 | throw new JsonException($"{typeof({{innerTypeName}})} converter does not found."); 966 | } 967 | } 968 | 969 | """); 970 | if (prop.HasFlag(UnitGenerateOptions.JsonConverterDictionaryKeySupport)) 971 | { 972 | if (prop.IsSupportUtf8Formatter()) 973 | { 974 | sb.AppendLine($$""" 975 | public override void WriteAsPropertyName(Utf8JsonWriter writer, {{unitTypeName}} value, JsonSerializerOptions options) 976 | { 977 | Span buffer = stackalloc byte[36]; 978 | if (System.Buffers.Text.Utf8Formatter.TryFormat(value.value, buffer, out var written)) 979 | { 980 | writer.WritePropertyName(buffer.Slice(0, written)); 981 | } 982 | else 983 | { 984 | writer.WritePropertyName(value.value.ToString()); 985 | } 986 | } 987 | 988 | public override {{unitTypeName}} ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 989 | { 990 | if (System.Buffers.Text.Utf8Parser.TryParse(reader.ValueSpan, out {{innerTypeName}} value, out var consumed)) 991 | { 992 | return new {{unitTypeName}}(value); 993 | } 994 | else 995 | { 996 | return new {{unitTypeName}}({{innerTypeName}}.Parse(reader.GetString())); 997 | } 998 | } 999 | 1000 | """); 1001 | } 1002 | else if (prop.IsUlid()) 1003 | { 1004 | sb.AppendLine($$""" 1005 | public override void WriteAsPropertyName(Utf8JsonWriter writer, {{unitTypeName}} value, JsonSerializerOptions options) 1006 | { 1007 | writer.WritePropertyName(value.value.ToString()); 1008 | } 1009 | 1010 | public override {{unitTypeName}} ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 1011 | { 1012 | return new {{unitTypeName}}({{innerTypeName}}.Parse(reader.GetString())); 1013 | } 1014 | 1015 | """); 1016 | } 1017 | else if (prop.IsString()) 1018 | { 1019 | sb.AppendLine($$""" 1020 | public override void WriteAsPropertyName(Utf8JsonWriter writer, {{unitTypeName}} value, JsonSerializerOptions options) 1021 | { 1022 | writer.WritePropertyName(value.value.ToString()); 1023 | } 1024 | 1025 | public override {{unitTypeName}} ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 1026 | { 1027 | return new {{unitTypeName}}(reader.GetString()); 1028 | } 1029 | 1030 | """); 1031 | } 1032 | else 1033 | { 1034 | sb.AppendLine($$""" 1035 | public override void WriteAsPropertyName(Utf8JsonWriter writer, {{unitTypeName}} value, JsonSerializerOptions options) 1036 | { 1037 | writer.WritePropertyName(value.value.ToString()); 1038 | } 1039 | 1040 | public override {{unitTypeName}} ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 1041 | { 1042 | return new {{unitTypeName}}({{innerTypeName}}.Parse(reader.GetString())); 1043 | } 1044 | 1045 | """); 1046 | } 1047 | } // End JsonConverterDictionaryKeySupport 1048 | 1049 | sb.AppendLine($$""" 1050 | } 1051 | 1052 | """); 1053 | } // End JsonConverter 1054 | 1055 | if (prop.HasFlag(UnitGenerateOptions.MessagePackFormatter)) 1056 | { 1057 | sb.AppendLine($$""" 1058 | // UnitGenerateOptions.MessagePackFormatter 1059 | private class {{unitTypeName}}MessagePackFormatter : IMessagePackFormatter<{{unitTypeName}}> 1060 | { 1061 | public void Serialize(ref MessagePackWriter writer, {{unitTypeName}} value, MessagePackSerializerOptions options) 1062 | { 1063 | options.Resolver.GetFormatterWithVerify<{{innerTypeName}}>().Serialize(ref writer, value.value, options); 1064 | } 1065 | 1066 | public {{unitTypeName}} Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) 1067 | { 1068 | return new {{unitTypeName}}(options.Resolver.GetFormatterWithVerify<{{innerTypeName}}>().Deserialize(ref reader, options)); 1069 | } 1070 | } 1071 | 1072 | """); 1073 | } // End MessagePackFormatter 1074 | 1075 | if (prop.HasFlag(UnitGenerateOptions.DapperTypeHandler)) 1076 | { 1077 | sb.AppendLine($$""" 1078 | // UnitGenerateOptions.DapperTypeHandler 1079 | public class {{unitTypeName}}TypeHandler : Dapper.SqlMapper.TypeHandler<{{unitTypeName}}> 1080 | { 1081 | public override {{unitTypeName}} Parse(object value) 1082 | { 1083 | return new {{unitTypeName}}(({{innerTypeName}})value); 1084 | } 1085 | 1086 | public override void SetValue(System.Data.IDbDataParameter parameter, {{unitTypeName}} value) 1087 | { 1088 | parameter.DbType = System.Data.DbType.{{prop.GetDbType()}}; 1089 | parameter.Value = value.value; 1090 | } 1091 | } 1092 | 1093 | [ModuleInitializer] 1094 | public static void AddTypeHandler() 1095 | { 1096 | Dapper.SqlMapper.AddTypeHandler(new {{unitTypeName}}.{{unitTypeName}}TypeHandler()); 1097 | } 1098 | 1099 | """); 1100 | } // End DapperTypeHandler 1101 | 1102 | if (prop.HasFlag(UnitGenerateOptions.EntityFrameworkValueConverter)) 1103 | { 1104 | sb.AppendLine($$""" 1105 | // UnitGenerateOptions.EntityFrameworkValueConverter 1106 | public class {{unitTypeName}}ValueConverter : Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter<{{unitTypeName}}, {{innerTypeName}}> 1107 | { 1108 | public {{unitTypeName}}ValueConverter() 1109 | : base( 1110 | convertToProviderExpression: x => x.value, 1111 | convertFromProviderExpression: x => new {{unitTypeName}}(x)) 1112 | { 1113 | } 1114 | 1115 | public {{unitTypeName}}ValueConverter(Microsoft.EntityFrameworkCore.Storage.ValueConversion.ConverterMappingHints mappingHints = null) 1116 | : base( 1117 | convertToProviderExpression: x => x.value, 1118 | convertFromProviderExpression: x => new {{unitTypeName}}(x), 1119 | mappingHints: mappingHints) 1120 | { 1121 | } 1122 | } 1123 | 1124 | """); 1125 | } // End EntityFrameworkValueConverter 1126 | 1127 | 1128 | sb.AppendLine($$""" 1129 | // Default 1130 | 1131 | private class {{unitTypeName}}TypeConverter : System.ComponentModel.TypeConverter 1132 | { 1133 | private static readonly Type WrapperType = typeof({{unitTypeName}}); 1134 | private static readonly Type ValueType = typeof({{innerTypeName}}); 1135 | 1136 | public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, Type sourceType) 1137 | { 1138 | if (sourceType == WrapperType || sourceType == ValueType) 1139 | { 1140 | return true; 1141 | } 1142 | 1143 | return base.CanConvertFrom(context, sourceType); 1144 | } 1145 | 1146 | public override bool CanConvertTo(System.ComponentModel.ITypeDescriptorContext context, Type destinationType) 1147 | { 1148 | if (destinationType == WrapperType || destinationType == ValueType) 1149 | { 1150 | return true; 1151 | } 1152 | 1153 | return base.CanConvertTo(context, destinationType); 1154 | } 1155 | 1156 | public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) 1157 | { 1158 | if (value != null) 1159 | { 1160 | var t = value.GetType(); 1161 | if (t == typeof({{unitTypeName}})) 1162 | { 1163 | return ({{unitTypeName}})value; 1164 | } 1165 | if (t == typeof({{innerTypeName}})) 1166 | { 1167 | return new {{unitTypeName}}(({{innerTypeName}})value); 1168 | } 1169 | } 1170 | 1171 | return base.ConvertFrom(context, culture, value); 1172 | } 1173 | 1174 | public override object ConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) 1175 | { 1176 | if (value is {{unitTypeName}} wrappedValue) 1177 | { 1178 | if (destinationType == WrapperType) 1179 | { 1180 | return wrappedValue; 1181 | } 1182 | 1183 | if (destinationType == ValueType) 1184 | { 1185 | return wrappedValue.AsPrimitive(); 1186 | } 1187 | } 1188 | 1189 | return base.ConvertTo(context, culture, value, destinationType); 1190 | } 1191 | } 1192 | } 1193 | """); 1194 | 1195 | if (!string.IsNullOrEmpty(ns)) 1196 | { 1197 | sb.AppendLine("}"); 1198 | } 1199 | 1200 | var source = sb.ToString(); 1201 | 1202 | // Add Source 1203 | var filename = ns == null 1204 | ? $"{prop.UnitType.Value.Name}.g.cs" 1205 | : $"{ns}.{prop.UnitType.Value.Name}.g.cs"; 1206 | 1207 | context.AddSource(filename, source); 1208 | } 1209 | 1210 | record UnitOfAttributeProperty 1211 | { 1212 | public IgnoreEquality UnitType { get; } 1213 | public IgnoreEquality Type { get; } 1214 | IgnoreEquality ReferenceSymbols { get; } 1215 | 1216 | public string TypeName { get; } 1217 | public UnitGenerateOptions Options { get; set; } = UnitGenerateOptions.None; 1218 | public UnitArithmeticOperators ArithmeticOperators { get; set; } = UnitArithmeticOperators.All; 1219 | public string? ToStringFormat { get; set; } 1220 | 1221 | public UnitOfAttributeProperty(ITypeSymbol unitType, ITypeSymbol type, ReferenceSymbols referenceSymbols) 1222 | { 1223 | this.UnitType = new(unitType); 1224 | this.Type = new(type); 1225 | this.TypeName = type.ToString(); 1226 | this.ReferenceSymbols = new(referenceSymbols); 1227 | } 1228 | 1229 | public bool IsString() => TypeName is "string"; 1230 | public bool IsBool() => TypeName is "bool"; 1231 | public bool IsReferenceType() => Type.Value.IsReferenceType; 1232 | public bool IsUlid() => SymbolEqualityComparer.Default.Equals(Type.Value, ReferenceSymbols.Value.UlidType); 1233 | public bool IsGuid() => SymbolEqualityComparer.Default.Equals(Type.Value, ReferenceSymbols.Value.GuidType); 1234 | 1235 | public bool HasFlag(UnitGenerateOptions options) => Options.HasFlag(options); 1236 | 1237 | public bool HasArithmeticOperator(UnitArithmeticOperators op) 1238 | { 1239 | return HasFlag(UnitGenerateOptions.ArithmeticOperator) && ArithmeticOperators.HasFlag(op); 1240 | } 1241 | 1242 | public bool HasValueArithmeticOperator(UnitArithmeticOperators op) 1243 | { 1244 | return HasFlag(UnitGenerateOptions.ValueArithmeticOperator) && ArithmeticOperators.HasFlag(op); 1245 | } 1246 | 1247 | public bool HasFormattableInterface() => IsImplemented(ReferenceSymbols.Value.FormattableInterface); 1248 | public bool HasParsableInterface() => ReferenceSymbols.Value.ParsableInterface != null && IsImplementedGenericSelfType(ReferenceSymbols.Value.ParsableInterface); 1249 | public bool HasSpanFormattableInterface() => ReferenceSymbols.Value.SpanFormattableInterface != null && IsImplemented(ReferenceSymbols.Value.SpanFormattableInterface); 1250 | public bool HasSpanParsableInterface() => ReferenceSymbols.Value.SpanParsableInterface != null && IsImplementedGenericSelfType(ReferenceSymbols.Value.SpanParsableInterface); 1251 | public bool HasUtf8SpanFormattableInterface() => ReferenceSymbols.Value.Utf8SpanFormattableInterface != null && IsImplemented(ReferenceSymbols.Value.Utf8SpanFormattableInterface); 1252 | public bool HasUtf8SpanParsableInterface() => ReferenceSymbols.Value.Utf8SpanParsableInterface != null && IsImplementedGenericSelfType(ReferenceSymbols.Value.Utf8SpanParsableInterface); 1253 | 1254 | public DbType GetDbType() 1255 | { 1256 | return TypeName switch 1257 | { 1258 | "short" => DbType.Int16, 1259 | "int" => DbType.Int32, 1260 | "long" => DbType.Int64, 1261 | "ushort" => DbType.UInt16, 1262 | "uint" => DbType.UInt32, 1263 | "ulong" => DbType.UInt64, 1264 | "string" => DbType.AnsiString, 1265 | "byte[]" => DbType.Binary, 1266 | "bool" => DbType.Boolean, 1267 | "byte" => DbType.Byte, 1268 | "sbyte" => DbType.SByte, 1269 | "float" => DbType.Single, 1270 | "double" => DbType.Double, 1271 | "System.DateTime" => DbType.DateTime, 1272 | "System.DateTimeOffset" => DbType.DateTimeOffset, 1273 | "System.TimeSpan" => DbType.Time, 1274 | "System.Guid" => DbType.Guid, 1275 | "decimal" => DbType.Currency, 1276 | _ => DbType.Object 1277 | }; 1278 | } 1279 | 1280 | public bool IsSupportUtf8Formatter() 1281 | { 1282 | return TypeName switch 1283 | { 1284 | "short" => true, 1285 | "int" => true, 1286 | "long" => true, 1287 | "ushort" => true, 1288 | "uint" => true, 1289 | "ulong" => true, 1290 | "bool" => true, 1291 | "byte" => true, 1292 | "sbyte" => true, 1293 | "float" => true, 1294 | "double" => true, 1295 | "System.DateTime" => true, 1296 | "System.DateTimeOffset" => true, 1297 | "System.TimeSpan" => true, 1298 | "System.Guid" => true, 1299 | _ => false 1300 | }; 1301 | } 1302 | 1303 | bool IsImplemented(INamedTypeSymbol interfaceSymbol) 1304 | { 1305 | foreach (var x in Type.Value.AllInterfaces) 1306 | { 1307 | if (SymbolEqualityComparer.Default.Equals(x, interfaceSymbol)) 1308 | { 1309 | foreach (var interfaceMember in x.GetMembers()) 1310 | { 1311 | if (interfaceMember.IsStatic) 1312 | { 1313 | // Do not allow explicit implementation 1314 | var implementation = Type.Value.FindImplementationForInterfaceMember(interfaceMember); 1315 | switch (implementation) 1316 | { 1317 | case IMethodSymbol { ExplicitInterfaceImplementations.Length: > 0 }: 1318 | return false; 1319 | case IPropertySymbol { ExplicitInterfaceImplementations.Length: > 0 }: 1320 | return false; 1321 | } 1322 | } 1323 | } 1324 | return true; 1325 | } 1326 | } 1327 | return false; 1328 | } 1329 | 1330 | bool IsImplementedGenericSelfType(INamedTypeSymbol interfaceSymbol) 1331 | { 1332 | foreach (var x in Type.Value.AllInterfaces) 1333 | { 1334 | if (x.IsGenericType && 1335 | SymbolEqualityComparer.Default.Equals(x.ConstructedFrom, interfaceSymbol) && 1336 | SymbolEqualityComparer.Default.Equals(x.TypeArguments[0], Type.Value)) 1337 | { 1338 | foreach (var interfaceMember in x.GetMembers()) 1339 | { 1340 | if (interfaceMember.IsStatic) 1341 | { 1342 | // Do not allow explicit implementation 1343 | var implementation = Type.Value.FindImplementationForInterfaceMember(interfaceMember); 1344 | switch (implementation) 1345 | { 1346 | case IMethodSymbol { ExplicitInterfaceImplementations.Length: > 0 }: 1347 | return false; 1348 | case IPropertySymbol { ExplicitInterfaceImplementations.Length: > 0 }: 1349 | return false; 1350 | } 1351 | } 1352 | } 1353 | return true; 1354 | } 1355 | } 1356 | return false; 1357 | } 1358 | } 1359 | } 1360 | -------------------------------------------------------------------------------- /src/UnitGenerator/UnitGenerateOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace UnitGenerator 4 | { 5 | // same as Generated Options(check SourceGenerator.cs). 6 | [Flags] 7 | internal enum UnitGenerateOptions 8 | { 9 | None = 0, 10 | ImplicitOperator = 1, 11 | ParseMethod = 1 << 1, 12 | MinMaxMethod = 1 << 2, 13 | ArithmeticOperator = 1 << 3, 14 | ValueArithmeticOperator = 1 << 4, 15 | Comparable = 1 << 5, 16 | Validate = 1 << 6, 17 | JsonConverter = 1 << 7, 18 | MessagePackFormatter = 1 << 8, 19 | DapperTypeHandler = 1 << 9, 20 | EntityFrameworkValueConverter = 1 << 10, 21 | WithoutComparisonOperator = 1 << 11, 22 | JsonConverterDictionaryKeySupport = 1 << 12, 23 | Normalize = 1 << 13, 24 | } 25 | 26 | [Flags] 27 | internal enum UnitArithmeticOperators 28 | { 29 | All = Addition | Subtraction | Multiply | Division | Increment | Decrement, 30 | Addition = 1, 31 | Subtraction = 1 << 1, 32 | Multiply = 1 << 2, 33 | Division = 1 << 3, 34 | Increment = 1 << 4, 35 | Decrement = 1 << 5, 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/UnitGenerator/UnitGenerator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 13 6 | enable 7 | $(NoWarn);CS1591 8 | true 9 | 10 | 11 | false 12 | true 13 | false 14 | true 15 | 16 | 17 | UnitGenerator 18 | C# Source Generator to create value-object, inspired by units of measure. 19 | 20 | $(Version) 21 | Cysharp 22 | Cysharp 23 | © Cysharp, Inc. 24 | sourcegenerator;valueobject 25 | https://github.com/Cysharp/UnitGenerator 26 | $(PackageProjectUrl) 27 | git 28 | MIT 29 | Icon.png 30 | true 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /tests/UnitGenerator.NET9.Tests/UnitGenerator.NET9.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | enable 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/UnitGenerator.NET9.Tests/UnitOfGuidTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using System; 3 | 4 | namespace UnitGenerator.NET9.Tests; 5 | 6 | public class UnitOfGuidTests 7 | { 8 | [Fact] 9 | public void Guidv7_v4_Comparison_AsExpected() 10 | { 11 | // v7 12 | TryGetUuidV7Timestamp(Guidv7Unit.New(uuidV7: true).AsPrimitive(), out var v).Should().BeTrue(); 13 | // ...approximate check 14 | v?.ToString("yyyyMMdd").Should().Be(DateTime.UtcNow.ToString("yyyyMMdd")); 15 | TryGetUuidV7Timestamp(Guidv7Unit.New().AsPrimitive(), out var _).Should().BeFalse(); 16 | } 17 | 18 | static bool TryGetUuidV7Timestamp(Guid uuid, out DateTimeOffset? timestamp) 19 | { 20 | timestamp = null; 21 | var uuidString = uuid.ToString("N"); 22 | // version number is the 13th character 23 | if (uuidString[12] == '7') 24 | { 25 | var timestampHex = uuidString.Substring(0, 12); 26 | var milliseconds = Convert.ToInt64(timestampHex, 16); 27 | timestamp = DateTimeOffset.FromUnixTimeMilliseconds(milliseconds); 28 | return true; 29 | } 30 | else return false; 31 | } 32 | } 33 | 34 | [UnitOf()] 35 | public readonly partial struct Guidv7Unit { } 36 | -------------------------------------------------------------------------------- /tests/UnitGenerator.Tests/GenerateTest.cs: -------------------------------------------------------------------------------- 1 | using MessagePack; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Text.Json; 8 | using System.Text.Json.Serialization; 9 | using System.Threading.Tasks; 10 | using Xunit; 11 | 12 | namespace UnitGenerator.Tests 13 | { 14 | public class GenerateTest 15 | { 16 | const string PrimitiveTemplate = @" 17 | using System; 18 | using UnitGenerator; 19 | 20 | namespace MyApp 21 | { 22 | [UnitOf(typeof(int), UnitGenerateOptions.All)] 23 | public readonly partial struct A 24 | { 25 | private partial void Validate() 26 | { 27 | } 28 | } 29 | } 30 | "; 31 | 32 | [Fact] 33 | public void Foo() 34 | { 35 | //var comp = TestHelper.CreateCompilation(PrimitiveTemplate, typeof(TypeConverter), typeof(MessagePackSerializer), typeof(MessagePackObjectAttribute), typeof(JsonSerializer), typeof(Dapper.SqlMapper), typeof(Microsoft.EntityFrameworkCore.ValueGeneration.ValueGenerator)); 36 | //var newComp = TestHelper.RunGenerators(comp, out var generatorDiags, new SourceGenerator()); 37 | 38 | //Assert.Empty(generatorDiags); 39 | //Assert.Empty(newComp.GetDiagnostics()); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/UnitGenerator.Tests/TestHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using System; 4 | using System.Collections.Immutable; 5 | using System.Linq; 6 | using System.Reflection; 7 | 8 | namespace UnitGenerator.Tests 9 | { 10 | // https://gist.github.com/chsienki/2955ed9336d7eb22bcb246840bfeb05c 11 | 12 | public static class TestHelper 13 | { 14 | public static Compilation CreateCompilation(string source, params Type[] metadataLocations) 15 | { 16 | var references = metadataLocations 17 | .Concat(new[] { typeof(Binder), typeof(object) }) 18 | .Select(x => x.Assembly.Location) 19 | .Distinct() 20 | .Select(x => MetadataReference.CreateFromFile(x)) 21 | .ToArray(); 22 | 23 | return CSharpCompilation.Create( 24 | assemblyName: "compilation", 25 | syntaxTrees: new[] { CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview)) }, 26 | references: references, 27 | options: new CSharpCompilationOptions(OutputKind.ConsoleApplication) 28 | ); 29 | } 30 | 31 | private static GeneratorDriver CreateDriver(Compilation compilation, params ISourceGenerator[] generators) => CSharpGeneratorDriver.Create( 32 | generators: ImmutableArray.Create(generators), 33 | additionalTexts: ImmutableArray.Empty, 34 | parseOptions: (CSharpParseOptions)compilation.SyntaxTrees.First().Options, 35 | optionsProvider: null 36 | ); 37 | 38 | public static Compilation RunGenerators(Compilation compilation, out ImmutableArray diagnostics, params ISourceGenerator[] generators) 39 | { 40 | CreateDriver(compilation, generators).RunGeneratorsAndUpdateCompilation(compilation, out var updatedCompilation, out diagnostics); 41 | return updatedCompilation; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /tests/UnitGenerator.Tests/UnitGenerator.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | latest 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | all 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | --------------------------------------------------------------------------------