UtilityTypeGenerator 0.0.9
dotnet add package UtilityTypeGenerator --version 0.0.9
NuGet\Install-Package UtilityTypeGenerator -Version 0.0.9
<PackageReference Include="UtilityTypeGenerator" Version="0.0.9" />
paket add UtilityTypeGenerator --version 0.0.9
#r "nuget: UtilityTypeGenerator, 0.0.9"
// Install UtilityTypeGenerator as a Cake Addin #addin nuget:?package=UtilityTypeGenerator&version=0.0.9 // Install UtilityTypeGenerator as a Cake Tool #tool nuget:?package=UtilityTypeGenerator&version=0.0.9
UtilityTypeGenerator
Generates TypeScript-like utility types for C#.
Utility types are generated types based on one or more input types. Slap the [UtilityType(selector)]
attribute on a
partial
type and the generator will generate a partial type with the same name and type (e.g., class, record, struct)
as the type with the attribute (yes, that can be different from the input type(s)!), but with the specified selector(s) applied.
For more information about utility types and how to use them, check out the TypeScript docs.
Important Note: This only generates auto-properties, no matter whether the input type's properties are auto-properties or not. This can be handy in and of itself, but computed properties are out of scope for this project.
Another important note: This is a source generator, so it only works w/ .NET 5.0+. However, I'm opinionated about using the latest stable C# SDK, so YMMV if you are running something ancient (like C# 7.3). You should really be setting
<LangVersion>latest</LangVersion>
in your projects (yes, that works with older TargetFrameworks).
Usage
- Add the UtilityTypeGenerator NuGet package to your project:
<PackageReference Include="UtilityTypeGenerator" Version="0.0.4" PrivateAssets="all" IncludeAssets="build; analyzers" />
- Add the
[UtilityType("selector")]
attribute to apartial
type, replacing"selector"
with the selector(s) of your choice.
Notes on generated types
- The generated type will be a
partial
type with the same name, type, and accessibility as the type with the[UtilityType]
attribute. - Each property will have the same accessibility as the property in the input type.
- Comments (leading trivia) from the first matching property in the input type will be copied to the generated property.
- Initializer statements for properties will be stripped. If the property type is not nullable, the
= default!;
initializer will be added.
If you need to customize the generated type, you can simply provide whatever you need in the partial type. Make sure to exclude any properties that you don't want to be generated if they would conflict!
Supported selectors
A selector is a string that specifies a verb (e.g., Pick
), one or more types or nested selectors, and (for some verbs) property names.
Verb | Syntax | Description |
---|---|---|
Import |
Import<T> |
Imports all of the properties from T (a type or selector). |
Intersection |
Intersection<T1, T2 [, T3] [...]> or Intersect<T1, T2 [, T3] [...]> |
Creates a type with the intersection of properties from T1 and T2 , etc. (types or selectors). Duplicate properties are okay, but the type of the property must be the same in both types. |
NotNull |
NotNull<T> |
Creates a type with all properties from T (a type or selector) transformed to non-nullable. |
Nullable |
Nullable<T> |
Creates a type with all properties from T (a type or selector) transformed to nullable. |
Omit |
Omit<T, Property1 [| Property2] [...]> or Omit<T, Property1 [, Property2] [...]> |
Creates a type with all properties from T (a type or selector) except the specified properties. |
Optional * |
Optional<T> |
Creates a type with all properties from T (a type or selector) stripped of the required keyword. <br>* Optional<T> behaves differently than it does in TypeScript! See below for details. |
Pick |
Pick<T, Property1 [| Property2] [...]> or Pick<T, Property1 [, Property2] [...]> |
Creates a type with only the specified properties from T (a type or selector). |
Required |
Required<T> |
Creates a type with all properties from T (a type or selector) marked as required .<br>Requires C# 11+ (or PolySharp!) |
Union |
Union<T1, T2 [, T3] [...]> |
Creates a type with the union of properties from T1 and T2 , etc. (types or selectors). Duplicate properties are okay, but the type of the property must be the same in both types. At least 2 types must be present in the selector. |
Examples
Import
namespace MyNamespace;
using MyNamespace.InternalData;
public class Person
{
/// <summary>The unique identifier for the person.</summary>
public Guid Id { get; set; }
/// <summary>The name of the person.</summary>
public string? Name { get; set; }
/// <summary>The date of birth of the person.</summary>
public DateTimeOffset? BirthDate { get; set; }
/// <summary>Internal data object for that person.</summary>
internal PersonData Data { get; set; }
}
[UtilityType("Import<Person>")]
internal partial class Foo
{
public required string SomeOtherProperty { get; }
}
// generates:
internal partial class Foo
{
/// <summary>The unique identifier for the person.</summary>
public Guid Id { get; set; }
/// <summary>The name of the person.</summary>
public string? Name { get; set; }
/// <summary>The date of birth of the person.</summary>
public DateTimeOffset? BirthDate { get; set; }
/// <summary>Internal data object for that person.</summary>
internal global::MyNamespace.InternalData.PersonData Data { get; set; }
}
// since this is a partial class, SomeOtherProperty is also defined (but not in the generated source).
Pick
public class Person
{
public Guid Id { get; set; }
public string? Name { get; set; }
public DateTimeOffset? BirthDate { get; set; }
}
[UtilityType("Pick<Person, Name>")]
public partial class OnlyName;
// generates:
public partial class OnlyName
{
public string? Name { get; set; }
}
Omit
public class Person
{
public Guid Id { get; set; }
public string? Name { get; set; }
public DateTimeOffset? BirthDate { get; set; }
}
[UtilityType("Omit<Person, Name>")]
public partial class OmitName;
// generates:
public partial class OmitName
{
public Guid Id { get; set; }
public DateTimeOffset? BirthDate { get; set; }
}
Required
public class Person
{
public Guid Id { get; set; }
public string? Name { get; set; }
public DateTimeOffset? BirthDate { get; set; }
}
[UtilityType("Required<Person>")]
public partial class PersonRequired;
// generates:
public partial class PersonRequired
{
public Guid Id { get; set; } = default!;
public string Name { get; set; } = default!;
public DateTimeOffset BirthDate { get; set; } = default!;
}
Optional
IMPORTANT: This is different from TypeScript, where
Optional<T>
allowsundefined
values for the properties.
TIP: This should be combined with
Nullable<T>
to avoid NullReferenceExceptions for any reference type properties. Composition withPick<T>
andOmit<T>
can also be helpful.
public class Person
{
public required Guid Id { get; }
public required string? Name { get; }
public DateTimeOffset? BirthDate { get; set; }
}
[UtilityType("Optional<Person>")]
public partial class PersonOptional;
// generates:
public partial class PersonOptional
{
public Guid Id { get; set; } = default!;
public string Name { get; set; } = default!;
public DateTimeOffset? BirthDate { get; set; }
}
Nullable
public class Person
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Name { get; set; } = "";
public DateTimeOffset BirthDate { get; set; } = DateTimeOffset.MinValue;
}
[UtilityType("Nullable<Person>")]
public partial class PersonWithNullableProperties;
// generates (note the default values are stripped!):
public partial class PersonWithNullableProperties
{
public Guid? Id { get; set; }
public string? Name { get; set; }
public DateTimeOffset? BirthDate { get; set; }
}
Union
TIP: If you are trying to Union just one type, use
Import<T>
instead.
Syntax: Union<T1, T2 [, T3] [...]>
Example
public class Person
{
public Guid Id { get; set; }
public string? Name { get; set; }
public DateTimeOffset? BirthDate { get; set; }
}
public class User
{
public required Guid Id { get; set; }
public required string? UserName { get; set; }
}
[UtilityType("Union<Person, User>")]
public partial class PersonAndUser;
// generates:
public partial class PersonAndUser
{
public Guid Id { get; set; }
public string? Name { get; set; }
public DateTimeOffset? BirthDate { get; set; }
public required string? UserName { get; set; }
}
Intersection
public class Person
{
public Guid Id { get; set; }
public string? Name { get; set; }
public DateTimeOffset? BirthDate { get; set; }
}
public class User
{
public required Guid Id { get; set; }
public required string? UserName { get; set; }
}
[UtilityType("Intersection<Person, User>")]
public partial class PersonAndUser;
// generates:
public partial class PersonAndUser
{
public Guid Id { get; set; }
}
A note on implementation choices
If this gets at all popular, I'll add more compiler messages, syntax highlighting & error checking (red-squiggles!), etc.
I chose to use a string argument instead of more C#-like syntax to allow for a more compact syntax that is identical in nearly every case to the TypeScript syntax. Under the covers, the generator uses ANTLR with a simple grammar to do the parsing, and extending it to support more selectors is fairly trivial.
If there's demand for a more verbose syntax, I'll consider adding it (or you can submit a PR).
Learn more about Target Frameworks and .NET Standard.
-
.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.