UtilityTypeGenerator 0.0.4

There is a newer version of this package available.
See the version list below for details.
dotnet add package UtilityTypeGenerator --version 0.0.4
NuGet\Install-Package UtilityTypeGenerator -Version 0.0.4
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="UtilityTypeGenerator" Version="0.0.4" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add UtilityTypeGenerator --version 0.0.4
#r "nuget: UtilityTypeGenerator, 0.0.4"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install UtilityTypeGenerator as a Cake Addin
#addin nuget:?package=UtilityTypeGenerator&version=0.0.4

// Install UtilityTypeGenerator as a Cake Tool
#tool nuget:?package=UtilityTypeGenerator&version=0.0.4

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

  1. Add the UtilityTypeGenerator NuGet package to your project:
    • <PackageReference Include="UtilityTypeGenerator" Version="0.0.4" PrivateAssets="all" IncludeAssets="build; analyzers" />
  2. Add the [UtilityType("selector")] attribute to a partial type, replacing "selector" with the selector(s) of your choice.

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

public class Person
{
    public Guid Id { get; set; }
    public string? Name { get; set; }
    public DateTimeOffset? BirthDate { get; set; }
}

[UtilityType("Import<Person>")]
public partial class Foo
{
    public required string SomeOtherProperty { get; }
}
// generates:
public partial class Foo
{
    public Guid Id { get; set; }
    public string? Name { get; set; }
    public DateTimeOffset? BirthDate { get; set; }
}

// since this is a partial class, SomeOtherProperty is also defined.

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> allows undefined values for the properties.

TIP: This should be combined with Nullable<T> to avoid NullReferenceExceptions for any reference type properties. Composition with Pick<T> and Omit<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).

There are no supported framework assets in this package.

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.

Version Downloads Last updated
0.0.9 114 1/27/2024
0.0.8 112 1/27/2024
0.0.7 91 1/27/2024
0.0.6 73 1/27/2024
0.0.5 99 1/27/2024
0.0.4 117 1/24/2024