Introducing Stidgen, an ID type generator
Stidgen is an old (4 years) project of mine that was created to fill a professional need but that I never really advertised until now. The name stands for “Strongly Typed ID types GENerator”.
What it does is to generate C# types to wrap simple types used as identifiers
(string
-> UserId
, Guid
-> TweetId
, …) to have nicely typed code.
A simple .stidgen
file (The format is documented on the readme) with the following content :
public UserId<string>
public CompanyId<string>
public UploadedFileId<Guid>
Generate a 837 lines file, the types can then be used to get readable type signatures :
interface IPopulationQueries
{
UserInfo GetUserInfo(UserId id);
CompanyId GetUserCompany(UserId id);
IReadOnlyCollection<UserId> GetCompanyUsers(CompanyId id);
}
Usage
The latest version of stidgen is available as a dotnet code (2.1+) tool. It can be installed globally like that :
dotnet tool install --global stidgen
and can the be used from anywhere :
stidgen samples/Simple.stidgen
As a dotnet tool it can also be installed in a project and used from CI/FAKE. (No official FAKE module yet, it’ll come)
What ID types provide
A very simple wrapper type can be generated by hand in a dozen of lines but stidgen provide a lot more features by default :
- An immutable structure with a single member, something that the JIT can recognize and optimize away.
- Casts from/to the underlying types.
- Equality members, interfaces and operators.
- Interning of string when it’s the underlying type, with nice optimizations to Equal and GetHashCode. I’ll try to blog about them it’s quite an interesting optimization.
ToString()
and formattable overloads are lifted.DebuggerDisplay
is present to simplify debugging.- Optional protobuf-net attributes generation.
Comparison with F# solution
While stidgen is written in F# it doesn’t provide a way to generate ID types in F#. One of the reason is that generating formatted F# code is quite hard, but the other is that there is already 80% of the features directly available in the language.
The solution to the same problem in F# is to use a single-case discriminated union:
[<Struct>]
type UserId = | UserId of string
[<Struct>]
type CompanyId = | CompanyId of string
[<Struct>]
type UploadedFileId = | UploadedFileId of Guid
And F# nicely provide most of what stidgen does by default (Equality interfaces and operators, immutability) but there are still a few missing things so maybe one day i’ll work on a F# generator :
- No way to automatically intern strings
- No interning-specific optimizations.
- No check for null (Producing nulls in F# is rare but .NET corelib/libraries can produce them)
ToString
isn’t handled- No
DebuggerDisplay
Note: Stidgen generated types works very nicely from F#
The code
Stidgen is an F# project that uses Roslyn the C# compiler platform to generate nicely formatted code automatically.
The FluentRoslyn.fs
file in the respository contains an F# wrapper for Roslyn code generation that produce code like that :
if'
(equals info.ThisValueMemberAccess Literal.Null)
(block
[|
ret
(invocation
(dottedMemberAccess' ["System"; "Convert"; m.Name])
[| Literal.Null |])
|])
Conclusion
Stidgen is a project that we use extensively at work but I feel that we might not be the only ones that want strongly typed code in a C# codebase. So if you have this need or a similar one, why not try it ?