StrictlyTyped.Generator
1.23.10508.12101-beta
dotnet add package StrictlyTyped.Generator --version 1.23.10508.12101-beta
NuGet\Install-Package StrictlyTyped.Generator -Version 1.23.10508.12101-beta
<PackageReference Include="StrictlyTyped.Generator" Version="1.23.10508.12101-beta" />
paket add StrictlyTyped.Generator --version 1.23.10508.12101-beta
#r "nuget: StrictlyTyped.Generator, 1.23.10508.12101-beta"
// Install StrictlyTyped.Generator as a Cake Addin #addin nuget:?package=StrictlyTyped.Generator&version=1.23.10508.12101-beta&prerelease // Install StrictlyTyped.Generator as a Cake Tool #tool nuget:?package=StrictlyTyped.Generator&version=1.23.10508.12101-beta&prerelease
Strictly Typed
What is it?
Strictly Typed is a library of interfaces and extensions that allow developers to strictly type simple primitive values.
Using strictly typed values eliminates certain classes of errors and potential bugs from code. You can't mix up two Guid
arguments to a method if they are of different types! You can also implement domain-specific functionality on types, or
allow conversions or other operations, such as making it impossible to add NumberOfDays
to NumberOfPlanets
because
those operations are meaningless. Note that if you want that operation, you can add it (by implementing, say, public static Z operator +(X left, Y right)
).
But by default, that sort of thing just doesn't happen.
What do I do with it?
This library is intended to be used with the Strictly Typed Generator (not included in this package), which generates strictly typed immutable value record structs from stubs annotated with marker Attributes.
For example, the Generator will generate an Employee Id type from this stub:
[StrictGuid] public partial record struct EmployeeId;
Note that the part you write must be partial
, the Source Generator will implement the rest.
The namespace will be preserved, as will any enclosing types (static
or non-static
). Note that any enclosing types
must also be partial
, lest you anger Roslyn by causing the Source Generator to generate conflicting non-partial
types.
like so:
public static partial class Employee
{
[StrictGuid] public partial record struct Id;
}
This will generate a strictly typed Guid
of type Employee.Id
.
These types implement a TypeConverter
, so they will convert transparently to and from primitive types for, say, ASP .Net
model binding for action methods. They also serialize and deserialize to and from Json transparently using System.Text.Json
,
so if , say, your web client application uses javascript in AJAX it is not necessary to do any conversions. The serialization
and deserialization will take care of mapping the strict types to the correct primitive types.
But I want more control over my values!
These strict types use partial
methods to provide options for manipulating values and validating them. For example,
[StrictGuid]
public partial record struct HasAnOverriddenToString
{
static partial void _overrideToString(Guid value, ref string result)
{
result = $"Overridden {value}";
}
}
This will cause the HasAnOverriddenToString
's ToString() method to return Overridden 7240837c-757e-4c02-b592-28b7c24132c6
for a value that
contains the Guid
7240837c-757e-4c02-b592-28b7c24132c6
.
There are other partial
methods which can be implemented, depending on the wrapped primitive type. For example, the StrictGuid
types allow
overrides for the following:
/// <summary>
/// If implemented, the wrapped value will be preprocessed by this method before creation
/// Preprocessing runs before validation (if implemented)
/// </summary>
/// <param name="value">The value which is to be preprocessed</param>
static partial void _preprocess(ref global::System.Guid result);
/// <summary>
/// If implemented, the result of calling CompareTo on the wrapped value will be modified by this method
/// </summary>
/// <param name="result">The value which will be returned by CompareTo</param>
partial void _overrideEquals(AStrictGuid? obj, ref global::System.Boolean result);
/// <summary>
/// If implemented, the result of calling ToString on the wrapped value will be modified by this method
/// </summary>
/// <param name="result">The value which will be returned by ToString</param>
static partial void _overrideToString(global::System.Guid value, ref global::System.String result);
/// <summary>
/// If implemented, the result of calling CompareTo on the wrapped value will be modified by this method
/// </summary>
/// <param name="result">The value which will be returned by CompareTo</param>
partial void _overrideCompareTo(global::System.Object? obj, ref global::System.Int32 result);
/// <summary>
/// If implemented, this method will be used to check that the value is valid.
/// Validation runs after preprocessing (if implemented)
/// If errors contains any values validation will be considered to have failed.
/// (note that using the constructor, Create, or cast operators will not use this method)
/// </summary>
/// <param name="errors">A set of reasons why the value fails validation</param>
partial void _validate(ref global::System.Collections.Generic.HashSet<global::System.String> errors);
So once I've defined a type, how to I get one of these values?
For [StrictString] public partial record struct FirstName;
No Validation Or Preprocessing
- Implicit cast (this is the most common one)
FirstName firstName = "Michael";
- Explicit cast
FirstName firstName = (FirstName)"Michael";
Create
static method
FirstName firstName = FirstName.Create("Michael");
- Constructor
FirstName firstName = new ("Michael");
Preprocessing, No Validation
From
static method
FirstName firstName = FirstName.From("Michael");
As
Extension
FirstName firstName = "Michael".As<FirstName>();
AsNullable
Extension (allows nulls, if the value passed in is null)
FirstName? firstName = "Michael".AsNullable<FirstName>();
FirstName? noFirstName = null.AsNullable<FirstName>();
Preprocessing and Validation
TryAs
Extension (without validation resultout
parameter)
if ("Michael".TryAs(out FirstName result)) { /* ... */ }
TryAs
Extension (with validation resultout
parameter)
if ("Michael".TryAs(out FirstName result, out IReadOnlySet<string> failures)) { /* ... */ }
TryAsNullable
Extension (allows nulls if the value passed in is null with validation resultout
parameter)
if ("Michael".TryAsNullable(out FirstName? result, out IReadOnlySet<string> failures)) { /* ... */ }
TryFrom
static method (without validation resultout
parameter)
if (FirstName.TryFrom("Michael", out FirstName result)) { /* ... */ }
TryFrom
static method (with validation resultout
parameter)
if (FirstName.TryFrom("Michael", out FirstName result, out IReadOnlySet<string> failures)) { /* ... */ }
Note that validation can always be performed on any instance by calling .Validate()
Yes, that's quite a few ways of getting values. I almost always use implicit cast or a TryAs
extension, but testing with
other developers has resulted in the varied ways to create values here.
What about JSON support?
These types serialize and deserialize exactly like the types they wrap. There are currently two leading
libraries that handle JSON support for C#.
System.Text.Json
: This is supported by default.
Newtsonsoft.Json
: This is supported, but you must add the following build symbol:
USE_NEWTONSOFT_JSON
You can add a build symbol through the Visual Studio package properties UI, or like this in the.csproj
file.<DefineConstants>$(DefineConstants);USE_NEWTONSOFT_JSON</DefineConstants>
Does this work with Swagger?
Yes, there's a separate package that adds support for Swagger (not included in this package).
Does this work with Entity Framework?
Yes, there's a separate package that adds support for Entity Framework (not included in this package). The
Source Generator also generates an EFConverter
which you can enable in the declaring project by adding the
USE_EF_CORE
build symbol.
No really, how do I use this?
Look in the Github repo (url) and you'll find there's an Example
project that is a slight adaptation of
one of the simple Microsoft WebAPI tutorial projects.
What is included?
StrictBool
StrictByte
StrictDecimal
StrictDouble
StrictFloat
StrictHalf
StrictInt
StrictLong
StrictSByte
StrictShort
StrictString
StrictUInt
StrictULong
StrictUShort
What is not included (yet)?
The following are on my to-do list:
StrictDateTime
StrictDateOnly
StrictTimeOnly
Strict<T>
Acknowledgements
This project has been heavily influenced by the code, articles, books, videos, blog posts, StackOverflow answers, and tutorials by (in no particular order)
In particular, Andrew Lock's excellent Strongly Typed IDs project showed me how to solve the problem of Entity Framework forcing evaluation on the server rather than the database. The blog post series accompanying it helped file a lot of rough edges off my source generator project. I doubt I would have been able to finish without such a clear guide to making source generators work.
Get in touch!
Contact the developer, "DMJ", at david.markham.jones@gmail.com
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
1.23.10508.12101-beta | 93 | 5/9/2023 |
1.23.10430.11649-beta | 85 | 4/30/2023 |
Initial release.