UtilityTypes 1.0.5
dotnet add package UtilityTypes --version 1.0.5
NuGet\Install-Package UtilityTypes -Version 1.0.5
<PackageReference Include="UtilityTypes" Version="1.0.5" />
<PackageVersion Include="UtilityTypes" Version="1.0.5" />
<PackageReference Include="UtilityTypes" />
paket add UtilityTypes --version 1.0.5
#r "nuget: UtilityTypes, 1.0.5"
#:package UtilityTypes@1.0.5
#addin nuget:?package=UtilityTypes&version=1.0.5
#tool nuget:?package=UtilityTypes&version=1.0.5
UtilityTypes
C# incremental source generator that brings TypeScript-style utility types to .NET — zero runtime cost, full IDE support, compile-time safety.
Motivation
TypeScript ships Partial<T>, Pick<T, K>, Omit<T, K>, and Required<T> as first-class language constructs. In TypeScript you write:
type UserDto = Pick<User, "id" | "name">;
type UserPatch = Partial<User>;
type UserPublic = Omit<User, "password" | "secret">;
These are not macro tricks — they are part of the type system. The compiler resolves them structurally, keeps them in sync with the source type, and flags stale references immediately.
C# has no equivalent. The standard pattern is to write every DTO and projection type by hand:
// Hand-written — diverges silently whenever User changes
public class UserDto {
public int Id { get; set; }
public string Name { get; set; }
}
This is repetitive and brittle. When User gains a field, UserDto does not. When User renames a property, the rename does not propagate. The mismatch builds up invisibly until it surfaces as a runtime bug or a forgotten migration.
UtilityTypes closes this gap. You declare the relationship between types, not the types themselves:
[Pick(typeof(User), nameof(User.Id), nameof(User.Name))]
public partial class UserDto { }
The rest is generated at compile time, in your IDE, with full IntelliSense — no reflection, no build scripts, no separate tooling step.
Design Philosophy
Declarative over imperative. The attribute on a type is the specification. There is no separate configuration file, no fluent builder, no XML schema. The declaration is co-located with the type it describes.
Compile-time, not runtime. All work happens during compilation via Roslyn's incremental generator API. The generated .g.cs files are ordinary C# source. The shipped NuGet contains no runtime library beyond the attribute definitions. There is no performance cost at application startup or request time.
Additive, not intrusive. The generator only adds members to your partial type. It never modifies your hand-written code. You can always write a member manually and use [MapIgnore] to suppress the generated equivalent — your code takes precedence.
Structural, not behavioral. UtilityTypes projects member declarations. It does not copy method bodies, expression trees, or any runtime logic. The generated output is a set of property and field declarations — the same code you would have written by hand, with no hidden behavior.
Scope: What This Library Does
- Copies member declarations (properties and fields) from a source type into a target
partialtype. - Filters members by accessibility (
public,private,protected,internal), scope (static/ instance), and kind (get-set property, read-only property, field, etc.). - Transforms member types across the entire projection: make every type nullable (
T?), non-nullable (T), orrequired. - Selects members by name (
[Pick]includes by name;[Omit]excludes by name). - Adapts member format via a composable token system — change accessor visibility, force
{ get; init; }, add a name prefix, etc. - Detects source type changes via an optional
SourceSignaturehash. When the source type gains or loses members, the generator emits a warning with the updated hash. - Supports composition — multiple attributes on the same target type, chained source types,
FromTypesfor interface-driven name selection. - Covers all declaration forms:
class,struct,interface,record,record struct, file-scoped namespaces.
Scope: What This Library Does NOT Do
- Object-to-object mapping at runtime. UtilityTypes is not AutoMapper, Mapperly, or any equivalent. It does not emit
MapToDto(source)methods. Use a runtime mapper for that. - Deep / nested type composition. Each attribute generates one flat set of member declarations. There is no recursive unwrapping of nested types, no JSON-path-style member selection.
- Behavioral code generation. No constructors, no conversion operators, no interface implementations, no
ToString()overrides. - Enum projection. Enums have no properties or fields in the Roslyn model; mapping an enum source yields zero members under default filters.
- Runtime reflection. The library has no dependency on
System.ReflectionorSystem.Linq.Expressions. All metadata is consumed at compile time via Roslyn symbols. - ORM or persistence concerns. UtilityTypes is type-shape tooling. It has no concept of database columns, navigation properties, or serialization contracts.
- Cross-assembly code generation. The generator runs within a single compilation. The source type and target type must be resolvable in the same compilation unit.
Installation
dotnet add package UtilityTypes
Targets netstandard2.0. Works in any .NET project that supports C# source generators (Visual Studio 2022+, Rider 2022+, dotnet build with .NET SDK 6+).
Quick Start
using UtilityTypes;
public class User {
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Password { get; set; }
}
// Copy all public instance properties from User
[Map(typeof(User))]
public partial class UserDto { }
// Copy only Id and Name ←→ TypeScript: Pick<User, "Id" | "Name">
[Pick(typeof(User), nameof(User.Id), nameof(User.Name))]
public partial class UserSummary { }
// Copy everything except Password ←→ TypeScript: Omit<User, "Password">
[Omit(typeof(User), nameof(User.Password))]
public partial class UserPublic { }
// Make all member types nullable (PATCH-style DTO) ←→ TypeScript: Partial<User>
[Partial(typeof(User))]
public partial class UserPatch { }
The partial modifier on the target type is required — it is how the C# compiler merges your hand-written members with the generated ones.
Table of Contents
- Attributes
- Filter Options
- Member Declaration Formats
- Multiple Attributes on the Same Type
- Supported Target Types
- Output File Naming
- Diagnostics and Errors
- Edge Cases and Behavior Details
- Default Values Summary
1. Attributes
All four generator attributes live in namespace UtilityTypes and are AllowMultiple = true. [MapIgnore] is a companion attribute applied to hand-written members, not to the type itself.
1.1 [Map]
Copies all members that pass the filter from the source type into the target type. Equivalent to TypeScript type Target = Source.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface,
AllowMultiple = true)]
public class MapAttribute : Attribute
Constructor
MapAttribute(Type sourceType)
| Parameter | Type | Required | Description |
|---|---|---|---|
sourceType |
Type |
Yes | The type whose members are read |
Named parameters (all optional — see Default Values)
| Parameter | Type | Description |
|---|---|---|
MemberDeclarationFormat |
string |
Controls how each member is written in the output |
IncludeBaseTypes |
bool |
Also include members from base classes / parent interfaces |
MemberAccessibilitySelection |
MemberAccessibilityFlags |
Filter by access modifier |
MemberScopeSelection |
MemberScopeFlags |
Filter by static vs. instance |
MemberKindSelection |
MemberKindFlags |
Filter by property/field kind |
MemberTypeTransform |
MemberTypeTransform |
Transform every member's type: None / Nullable / NonNullable / Required |
SourceSignature |
string? |
SHA256-based structural hash of the source type's members. When set, a mismatch triggers TU010 warning. |
SourceSignature usage: leave it unset on first annotation; after a TU010 warning appears, copy the "current signature" value from the warning message and paste it here to re-establish the baseline.
[Map(typeof(User), SourceSignature = "a1b2c3d4")]
public partial class UserDto { }
Example
[Map(typeof(SourceType),
IncludeBaseTypes = true,
MemberKindSelection = MemberKindFlags.AnyProperty | MemberKindFlags.AnyField,
MemberTypeTransform = MemberTypeTransform.NonNullable,
MemberDeclarationFormat = MemberDeclarationFormats.PublicGetSetProp)]
public partial class Target { }
1.2 [Pick]
Copies only the explicitly named members from the source type. Equivalent to TypeScript Pick<Source, "a" | "b">.
public class PickAttribute : MapAttribute
Constructor
PickAttribute(Type sourceType, params string[] fields)
| Parameter | Type | Required | Description |
|---|---|---|---|
sourceType |
Type |
Yes | The type to pick from |
fields |
string[] |
Yes | Names of members to include (variadic / params) |
Inherits all named parameters from MapAttribute, plus:
| Parameter | Type | Description |
|---|---|---|
FromTypes |
Type[]? |
Extract member names from these interfaces/types and merge them with fields (deduplicated) |
Example
[Pick(typeof(User), nameof(User.Id), nameof(User.Name))]
public partial class UserSummary { }
// With options
[Pick(typeof(User), "Id", "Name",
IncludeBaseTypes = true,
MemberDeclarationFormat = MemberDeclarationFormats.GetProp)]
public partial class UserReadonly { }
// FromTypes: pull member names from an interface contract
interface IIdentified { int Id { get; set; } }
[Pick(typeof(User), FromTypes = new[] { typeof(IIdentified) })]
public partial class UserById { } // equivalent to Pick(typeof(User), "Id")
Note: The
fieldsnames (including those derived fromFromTypes) are matched against members that have already passed theMemberKindSelection,MemberAccessibilitySelection, andMemberScopeSelectionfilters. A name filtered out before the name check will not appear in the output, and diagnostic TU004 is emitted.
1.3 [Omit]
Copies all members that pass the filter except the explicitly named ones. Equivalent to TypeScript Omit<Source, "a" | "b">.
public class OmitAttribute : MapAttribute
Constructor
OmitAttribute(Type sourceType, params string[] fields)
| Parameter | Type | Required | Description |
|---|---|---|---|
sourceType |
Type |
Yes | The type to omit from |
fields |
string[] |
No | Names of members to exclude (empty = copy all) |
Inherits all named parameters from MapAttribute, plus:
| Parameter | Type | Description |
|---|---|---|
FromTypes |
Type[]? |
Extract member names from these interfaces/types and merge them into the exclude set (deduplicated) |
Example
[Omit(typeof(User), nameof(User.Password))]
public partial class UserPublic { }
// Omit with no field names = copy everything (functionally identical to Map)
[Omit(typeof(User))]
public partial class UserAll { }
// FromTypes: exclude all members defined in a "sensitive" interface contract
interface ISensitive { string Password { get; set; } string Token { get; set; } }
[Omit(typeof(User), FromTypes = new[] { typeof(ISensitive) })]
public partial class UserSafe { } // equivalent to Omit(typeof(User), "Password", "Token")
1.4 [Partial]
Makes every selected member's type nullable. Equivalent to TypeScript Partial<T>. Optionally accepts member names (or FromTypes) to behave as Partial<Pick<T, K>>.
public class PartialAttribute : MapAttribute
Constructor
PartialAttribute(Type sourceType, params string[] fields)
| Parameter | Type | Required | Description |
|---|---|---|---|
sourceType |
Type |
Yes | The type to map from |
fields |
string[] |
No | If provided, only these members are mapped (Pick semantics applied first) |
Inherits all named parameters from MapAttribute. MemberTypeTransform is always forced to Nullable and cannot be overridden.
Additional named parameter:
| Parameter | Type | Description |
|---|---|---|
FromTypes |
Type[]? |
Extract member names from these interfaces/types and merge into the include set (deduplicated) |
Example
// All members become nullable — Partial<User>
[Partial(typeof(User))]
public partial class UserPatch { }
// Only Id and Name become nullable — Partial<Pick<User, "Id" | "Name">>
[Partial(typeof(User), nameof(User.Id), nameof(User.Name))]
public partial class UserIdNamePatch { }
// FromTypes: use an interface to declare which members to make nullable
interface IPatchable { string Name { get; set; } string Email { get; set; } }
[Partial(typeof(User), FromTypes = new[] { typeof(IPatchable) })]
public partial class UserContactPatch { }
1.5 [MapIgnore]
Applied to hand-written members inside the target partial type to suppress the generator from emitting a same-named member from the source type. Use this to avoid CS0102 duplicate-member errors when you need to override or re-declare a member manually.
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Method,
AllowMultiple = false, Inherited = false)]
public sealed class MapIgnoreAttribute : Attribute { }
Example
[Map(typeof(User))]
public partial class UserDto {
// Hand-written Id with a different type — suppress the generator's User.Id
[MapIgnore]
public string Id => _userId.ToString();
}
// Without [MapIgnore] the generator would also emit "public int Id { get; set; }"
// causing CS0102 (duplicate member).
Matching rule: name-only, StringComparer.Ordinal. Type differences are ignored — if the hand-written member has the same name as a source member, the source member is excluded from generation.
2. Filter Options
All four attributes share the same filtering options. Filters are applied in sequence:
Accessibility → Scope → Kind → (Pick/Omit name filter)
2.1 MemberAccessibilityFlags
Controls which access modifiers are included. Values can be combined with |.
| Value | Meaning |
|---|---|
Public |
Include public members (default) |
Private |
Include private members |
Protected |
Include protected members |
Internal |
Include internal members |
Any |
Include all of the above (Public \| Private \| Protected \| Internal) |
// Include public and internal members
[Map(typeof(Source),
MemberAccessibilitySelection = MemberAccessibilityFlags.Public | MemberAccessibilityFlags.Internal)]
public partial class Target { }
2.2 MemberScopeFlags
Controls whether instance or static members are included.
| Value | Meaning |
|---|---|
Instance |
Non-static members only (default) |
Static |
Static members only |
Any |
Both static and instance |
[Map(typeof(Source), MemberScopeSelection = MemberScopeFlags.Any)]
public partial class Target { }
When Static is included, the {scope} token in the format string expands to " static" (leading space). For instance members it expands to "".
2.3 MemberKindFlags
Controls which kinds of properties and fields are included. Values can be combined with |.
Properties
| Value | Meaning |
|---|---|
ReadonlyProperty |
Properties with get but no set accessor |
WriteonlyProperty |
Properties with set but no get accessor |
GetSetProperty |
Properties with both get and set accessors |
GetProperty |
ReadonlyProperty \| GetSetProperty |
SetProperty |
WriteonlyProperty \| GetSetProperty |
AnyProperty |
All property kinds (default) |
Fields
| Value | Meaning |
|---|---|
WritableField |
Non-readonly fields |
ReadonlyField |
readonly fields |
AnyField |
Both writable and readonly fields |
Combined
| Value | Meaning |
|---|---|
Any |
All properties and all fields |
// Only readonly properties and readonly fields
[Map(typeof(Source),
MemberKindSelection = MemberKindFlags.ReadonlyProperty | MemberKindFlags.ReadonlyField)]
public partial class ImmutableTarget { }
// Include fields as well as properties
[Map(typeof(Source), MemberKindSelection = MemberKindFlags.AnyProperty | MemberKindFlags.AnyField)]
public partial class AllMembersTarget { }
Note:
initaccessors are treated as setters. A{ get; init; }property matchesGetSetPropertyandSetProperty.
2.4 MemberTypeTransform
An orthogonal axis that transforms every mapped member's type (or adds a modifier). Set via the MemberTypeTransform named parameter on any of the four attributes.
| Value | Effect |
|---|---|
None |
Types are copied unchanged (default) |
Nullable |
T → T? (value types become Nullable<T>; reference types get ? NRT annotation; already-nullable types are unchanged) |
NonNullable |
T? → T (strips ?; Nullable<T> unwraps to T; already non-nullable types are unchanged) |
Required |
Every generated member gains the required modifier (C# 11+) |
// Strip nullable — map from a nullable model to a strict DTO
[Map(typeof(NullableModel), MemberTypeTransform = MemberTypeTransform.NonNullable)]
public partial class StrictDto { }
// All generated members must be set in object initializers
[Map(typeof(User), MemberTypeTransform = MemberTypeTransform.Required)]
public partial class RequiredUserDto { }
Note on
[Partial]:[Partial]always forcesMemberTypeTransform.Nullableregardless of what you write on the attribute. This cannot be overridden.
3. Member Declaration Formats
The MemberDeclarationFormat named parameter controls the exact text written for every mapped member.
3.1 Tokens
Tokens are placeholders inside the format string, replaced per-member at generation time.
using static UtilityTypes.Abstractions.MemberDeclarationFormats;
| Token | Constant | Expands to |
|---|---|---|
{required} |
Tokens.Required |
"required " (trailing space) if member has required modifier; "" otherwise |
{accessibility} |
Tokens.Accessibility |
Access modifier: public, private, protected, etc. |
{scope} |
Tokens.Scope |
" static" (leading space) for static; "" for instance |
{fieldAccess} |
Tokens.FieldAccess |
" readonly" (leading space) for readonly fields; "" otherwise |
{type} |
Tokens.Type |
Fully-qualified type name including generics, e.g. System.Guid |
{name} |
Tokens.Name |
Member name |
{accessors} |
Tokens.Accessors |
{ get; set; } / { get; } / { set; } for properties; ; for fields |
{required} precedes {accessibility}. {scope} and {fieldAccess} include a leading space so they can be placed immediately after {accessibility} without introducing double-spaces.
3.2 Built-in Format Constants
Defined in MemberDeclarationFormats (namespace UtilityTypes.Abstractions). All constants include {scope} so static members automatically carry the static keyword.
Preserve original accessibility
| Constant | Effect |
|---|---|
Source |
Preserves original declaration exactly (default) |
GetSetProp |
Forces get+set property |
GetProp |
Forces get-only property |
SetProp |
Forces set-only (write-only) property |
GetPrivateSetProp |
External read-only, internal writable |
GetInitProp |
Init-only after construction (C# 9+) |
Force public
| Constant | Effect |
|---|---|
PublicGetSetProp |
Public read+write |
PublicGetProp |
Public read-only |
PublicGetPrivateSetProp |
Public read, private write (common DTO) |
PublicGetProtectedSetProp |
Public read, subclass-writable |
PublicGetInternalSetProp |
Public read, assembly-writable |
PublicGetInitProp |
Public immutable (record style, C# 9+) |
PublicGetPrivateInitProp |
Constructor-only init (C# 9+) |
Force required public (C# 11+)
| Constant | Effect |
|---|---|
RequiredPublicGetSetProp |
Required public read+write |
RequiredPublicGetInitProp |
Required public init-only |
Fields
| Constant | Effect |
|---|---|
Field |
Field declaration, preserves static / readonly |
PublicField |
Public field, preserves static / readonly |
3.3 Custom Formats
Any string using the tokens above is valid. Use $"" interpolation with the Tokens constants for compile-time safety.
using static UtilityTypes.Abstractions.MemberDeclarationFormats;
// Add "Mapped" prefix to every member name
[Map(typeof(Source),
MemberDeclarationFormat = $"{Tokens.Accessibility} {Tokens.Type} Mapped{Tokens.Name}{Tokens.Accessors}")]
public partial class PrefixedTarget { }
// Force all mapped members to public string properties
[Map(typeof(Source),
MemberDeclarationFormat = "public string {name} { get; set; }")]
public partial class StringTarget { }
4. Multiple Attributes on the Same Type
All attributes are AllowMultiple = true. Any number of them may be stacked on a single target type. Each attribute generates an independent output file; the C# partial mechanism merges them.
interface IA { int Id { get; set; } }
interface IB { string Name { get; set; } }
[Omit(typeof(IA))] // → IC.omit.IA.g.cs
[Omit(typeof(IB))] // → IC.omit.IB.g.cs
public partial interface IC { }
[Pick(typeof(User), "Id")] // → Profile.pick.User.g.cs
[Map(typeof(Address))] // → Profile.map.Address.g.cs
public partial class Profile { }
Rules:
- Each attribute is processed independently; filter options and format are per-attribute.
- Your hand-written members in the
partialtype are never modified. - Different source types can be mixed freely across attributes on the same target.
- Mixing
[Map],[Pick],[Omit], and[Partial]on the same target is allowed. - Two attributes that would generate a member with the same name produce a CS0102 compiler error — this is by design and is the user's responsibility to resolve (use
[MapIgnore]or choose non-overlapping member sets).
5. Supported Target Types
The attributes apply to class, struct, and interface declarations. record and record struct are covered by the Class and Struct targets respectively.
[Map(typeof(Source))] public partial class Target { }
[Map(typeof(Source))] public partial struct Target { }
[Map(typeof(Source))] public partial interface Target { }
[Map(typeof(Source))] public partial record Target { }
[Map(typeof(Source))] public partial record struct Target { }
Both regular and file-scoped namespaces are supported:
namespace MyApp; // file-scoped — OK
namespace MyApp { partial class T { } } // block-scoped — OK
The source type (the argument to typeof(...)) can be any resolved named type: class, struct, interface, record. Enums are valid as source types but have no properties or fields in the Roslyn model, so under default filters nothing is mapped.
partial modifier is mandatory
If the target type is missing partial, the generator emits diagnostic TU001 (error) and produces no output for that annotation.
6. Output File Naming
Each attribute application generates exactly one .g.cs file:
{TargetTypeName}.{toolName}.{SourceTypeName}.g.cs
| Attribute | toolName |
|---|---|
[Map] |
map |
[Pick] |
pick |
[Omit] |
omit |
[Partial] |
partial |
Examples
| Annotation | Generated file |
|---|---|
[Map(typeof(Source))] |
Target.map.Source.g.cs |
[Pick(typeof(User), "Id")] |
Target.pick.User.g.cs |
[Omit(typeof(Entity), "Id")] |
Target.omit.Entity.g.cs |
[Omit(typeof(IA))] on IC |
IC.omit.IA.g.cs |
[Omit(typeof(IB))] on IC |
IC.omit.IB.g.cs |
7. Diagnostics and Errors
| Code | Severity | Trigger |
|---|---|---|
| TU000 | Warning | Unexpected internal generator exception |
| TU001 | Error | Target type is missing the partial modifier |
| TU002 | Warning | Filter combination yields zero members from source type |
| TU003 | Warning | One or more [Omit] field names not found in the filtered member set |
| TU004 | Warning | One or more [Pick] / [Partial] field names not found in the filtered member set |
| TU005 | Warning | Cyclic type reference detected during chained member resolution |
| TU006 | Warning | Same source type appears in duplicate attributes on the same target type |
| TU010 | Warning | SourceSignature hash does not match the source type's current structure |
TU001 — Missing partial
[Map(typeof(Source))]
public class Target { } // ❌ TU001 error; no output generated
TU002 — No members selected
// Source has only public members; requesting private yields nothing
[Map(typeof(Source), MemberAccessibilitySelection = MemberAccessibilityFlags.Private)]
public partial class Target { } // ⚠ TU002; generated type has no members
TU003 — Omit name not found
[Omit(typeof(Source), "NonExistent")]
public partial class Target { } // ⚠ TU003; remaining members are still mapped
TU004 — Pick name not found
[Pick(typeof(Source), "Id", "Ghost")]
public partial class Target { } // ⚠ TU004; only "Id" is mapped
TU005 — Cyclic type reference
[Omit(typeof(IB))]
partial interface IA { }
[Omit(typeof(IA))]
partial interface IB { } // ⚠ TU005 — IA → IB → IA cycle detected; broken gracefully
TU006 — Duplicate attribute
[Omit(typeof(Source))]
[Omit(typeof(Source))] // ⚠ TU006 — duplicate; this one is skipped
public partial class Target { }
TU010 — SourceSignature mismatch
[Map(typeof(User), SourceSignature = "a1b2c3d4")]
public partial class UserDto { }
// If User gains or loses a member:
// ⚠ TU010: "Source type 'User' has changed.
// Recorded: 'a1b2c3d4', current: 'e5f6a7b8'. Update SourceSignature to suppress."
Copy the current signature from the warning and update the attribute to restore the baseline.
8. Edge Cases and Behavior Details
8.1 IncludeBaseTypes
- Default
false: only members declared directly on the source type. true: walks the full inheritance / interface chain.- Classes/structs: walks
BaseTypeup to (but not including)System.Object/System.ValueType. - Interfaces: walks
AllInterfaces(full transitive set of parent interfaces).
- Classes/structs: walks
public class Base { public int Score { get; } }
public class Derived : Base { public string Name { get; set; } }
[Map(typeof(Derived))] // → Name only
[Map(typeof(Derived), IncludeBaseTypes = true)] // → Name + Score
interface IBase { string Bar { get; set; } }
interface IA : IBase { int Id { get; set; } }
[Omit(typeof(IA))] // → Id only
[Omit(typeof(IA), IncludeBaseTypes = true)] // → Id + Bar
8.2 Accessor modifiers on properties
Mixed accessor accessibility is preserved exactly under the Source format:
public class Source {
public Guid Id { get; private set; }
public int Value { get; init; }
public DateTime Created { protected get; set; }
}
8.3 Static members
Static members are excluded by default. Include them with MemberScopeFlags.Static or Any. The {scope} token produces " static" for static members.
8.4 Fields
Fields are excluded by default (MemberKindFlags.AnyProperty). Enable with MemberKindFlags.WritableField, ReadonlyField, or AnyField.
8.5 Resilient generation on attribute syntax errors
The generator recovers gracefully from malformed attribute syntax.
| Scenario | Behavior |
|---|---|
typeof(MisspelledType) — unresolved |
Emits an empty partial skeleton; no members |
| Completely malformed syntax | No output file generated for that annotation |
| Typo in named parameter name | Parameter ignored; default value used |
| Typo in named parameter value | Parameter ignored; default value used |
8.6 Pick / Omit name matching
Name matching is case-sensitive (StringComparer.Ordinal). Use nameof(...) to avoid typos.
[Pick(typeof(Source), nameof(Source.Id))] // safe
[Pick(typeof(Source), "id")] // ⚠ TU004 — "id" ≠ "Id"
8.7 Implicitly declared members
Members synthesized by the compiler (backing fields, event accessors, record equality members, etc.) are always excluded. Only members that appear explicitly in source are eligible.
8.8 Chained source types
If the source type itself has UtilityTypes attributes, the generator automatically resolves its effective members by following the chain. This mirrors TypeScript utility type composition.
interface IBase { string Bar { get; set; } }
interface IA : IBase { int Id { get; set; } }
[Omit(typeof(IA), IncludeBaseTypes = true)] // → IC gets: Id, Bar
[Omit(typeof(IB))] // → IC gets: Name
partial interface IC { }
[Omit(typeof(IC))] // resolves IC's effective members: Id, Bar, Name
partial class C { }
Rules:
- Explicit members on the source type take priority over chained members.
- Cycles (
A→B→A) are detected at compile time, reported as TU005, and broken gracefully — no infinite recursion, no build failure. - Diamond references (same type reachable via multiple paths) are allowed; each distinct path is resolved once.
8.9 Duplicate attributes
Applying the same attribute with the same source type more than once is detected at the syntax layer before code generation. The first occurrence generates normally; subsequent duplicates emit TU006 and are skipped, preventing build failures from duplicate hint names.
8.10 Type names in generated output
Type names are fully-qualified display strings as reported by Roslyn (e.g. System.Guid, System.Collections.Generic.List<string>). Short names are not used to avoid ambiguity.
9. Default Values Summary
The defaults represent the most common use case: copy all public, instance, property members, preserving their original declaration with no type transformation.
| Parameter | Type | Default |
|---|---|---|
MemberDeclarationFormat |
string |
MemberDeclarationFormats.Source |
IncludeBaseTypes |
bool |
false |
MemberAccessibilitySelection |
MemberAccessibilityFlags |
MemberAccessibilityFlags.Public |
MemberScopeSelection |
MemberScopeFlags |
MemberScopeFlags.Instance |
MemberKindSelection |
MemberKindFlags |
MemberKindFlags.AnyProperty |
MemberTypeTransform |
MemberTypeTransform |
MemberTypeTransform.None |
SourceSignature |
string? |
null (change detection disabled) |
FromTypes |
Type[]? |
null (Pick / Omit / Partial only) |
Appendix A — TypeScript Parallel
| TypeScript | UtilityTypes C# |
|---|---|
type D = T |
[Map(typeof(T))] |
Pick<T, "a" \| "b"> |
[Pick(typeof(T), "a", "b")] |
Omit<T, "a" \| "b"> |
[Omit(typeof(T), "a", "b")] |
Partial<T> |
[Partial(typeof(T))] |
Required<T> |
[Map(typeof(T), MemberTypeTransform = MemberTypeTransform.Required)] |
Readonly<T> |
[Map(typeof(T), MemberDeclarationFormat = MemberDeclarationFormats.GetProp)] |
Partial<Pick<T, "a" \| "b">> |
[Partial(typeof(T), "a", "b")] |
Appendix B — Attribute Target Matrix
| Attribute | class |
struct |
interface |
record |
record struct |
AllowMultiple | Applies to |
|---|---|---|---|---|---|---|---|
[Map] |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | type |
[Pick] |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | type |
[Omit] |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | type |
[Partial] |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | type |
[MapIgnore] |
— | — | — | — | — | ✗ | member (property / field / method) |
Appendix C — MemberKindFlags Composition
ReadonlyProperty = { get; } (get, no set)
WriteonlyProperty = { set; } (set, no get)
GetSetProperty = { get; set; } (both)
GetProperty = ReadonlyProperty | GetSetProperty (any property with get)
SetProperty = WriteonlyProperty | GetSetProperty (any property with set)
AnyProperty = ReadonlyProperty | WriteonlyProperty | GetSetProperty
WritableField = non-readonly field
ReadonlyField = readonly field
AnyField = WritableField | ReadonlyField
Any = AnyProperty | AnyField
Appendix D — Format Token Quick Reference
| Token | Instance prop | Static prop | Required prop | Writable field | Readonly field |
|---|---|---|---|---|---|
{required} |
(empty) | (empty) | required |
(empty) | (empty) |
{accessibility} |
public |
public |
public |
public |
public |
{scope} |
(empty) | static |
(empty) | (empty) | (empty) |
{fieldAccess} |
(empty) | (empty) | (empty) | (empty) | readonly |
{type} |
System.Guid |
int |
string |
double |
string |
{name} |
Id |
Counter |
Name |
count |
label |
{accessors} |
{ get; set; } |
{ get; set; } |
{ get; set; } |
; |
; |
License
| 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. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.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.0.5 | 53 | 5/27/2026 |