Skip to content

Commit

Permalink
#16 updated docs, added icon, updated changelog, time to merge
Browse files Browse the repository at this point in the history
  • Loading branch information
JasonBock committed Feb 1, 2025
1 parent 1001fa8 commit 00fcb3d
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 20 deletions.
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,32 @@

A library that automatically adds support for object deconstruction in C#.

## Getting Started

First, reference the `AutoDeconstruct` [NuGet package](https://www.nuget.org/packages/AutoDeconstruct).

Then, add `[AutoDeconstuct]` to a type so you can deconstruct it:

```c#
using AutoDeconstruct;

[AutoDeconstruct]
public sealed class Person
{
public uint Age { get; set; }
public required string Name { get; set; }
}

var person = new Person { Age = 22, Name = "Joe" };
var (age, name) = person;
```

Read [the overview document](docs/Overview.md) for further details.

### Prerequisites

The Rocks package targets .NET Standard 2.0 for host flexibility.

## Overview

The idea started with [this tweet](https://twitter.com/buhakmeh/status/1462106117564207104) - specifically, [this reply](https://twitter.com/dave_peixoto/status/1462181358248374278). I thought...how automatic can I make object deconstruction in C#? That's what this source generator is all about.
Expand Down
5 changes: 5 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.1.0] - 2025.02.01

### Added
- AutoDeconstruct no longer automatically scans for types; `[AutoDeconstruct]` must be used (this improves source generator performance) (issue [#16](https://github.com/JasonBock/AutoDeconstruct/issues/16))

## [1.0.0] - 2022.11.25

### Added
Expand Down
104 changes: 88 additions & 16 deletions docs/Overview.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
# Table of Contents
- [What is a Deconstructor?](#what-is-a-deconstructor?)
- [AutoDeconstruct Features](#autodeconstruct-rationale)
- [Motivation](#motivation)
- [What is a Deconstructor?](#what-is-a-deconstructor)
- [AutoDeconstruct Features](#autodeconstruct-features)
- [Marking Types](#marking-types)
- [Assembly-Level Support](#assembly-level-support)
- [Ignore Deconstruction Creation](#ignore-deconstruction-creation)

## Motivation

The idea started with [this tweet](https://twitter.com/buhakmeh/status/1462106117564207104) - specifically, [this reply](https://twitter.com/dave_peixoto/status/1462181358248374278). I thought...how automatic can I make object deconstruction in C#? That's what this source generator is all about.

## What is a Deconstructor?

Object deconstruction was added in C# 7.0. The documentation is [here](https://github.com/dotnet/roslyn/blob/main/docs/features/deconstruction.md), and there's another article [here](https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/deconstruct#user-defined-types). Basically, a deconstructor can be defined on either a type or as an extension method. In both cases, it has to be named "Deconstruct", it has to return `void`, and all of its parameters must be `out` parameters (the exception is with the extension method, where the first parameter is the object being extended). Furthermore, you can overload `Deconstruct` methods, but all `Deconstruct` methods must have a unique number of `out` parameters. Here are two examples:
Object deconstruction was added in C# 7.0. The documentation is [here](https://github.com/dotnet/roslyn/blob/main/docs/features/deconstruction.md), and there's another article [here](https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/deconstruct#user-defined-types). Basically, a deconstructor can be defined on either a type or as an extension method. In both cases, it has to be named "Deconstruct", it has to return `void`, and all of its parameters must be `out` parameters (the exception is with the extension method, where the first parameter is the type being extended). Furthermore, `Deconstruct()` methods can overloaded, but all `Deconstruct()` methods must have a unique number of `out` parameters. Here are two examples:

```csharp
using System;
Expand Down Expand Up @@ -43,13 +51,17 @@ var point = new Point(2, 3);
var (x, y) = point;
```

Note that what values are deconstructed is up to the developer. That is, deconstruction does not require one to deconstruct all property or field values.

## AutoDeconstruct Features

AutoDeconstruct finds all type and method definitions, and it'll look to see if the type has any `Deconstruct` methods, either as instance or extension methods. If none exist, then AutoDeconstruct looks to see how many public, readable, instance properties exist. If there's at least 1, the library generates a `Deconstruct` extension method in a static class defined in the same namespace as the target type. For example, if we have our `Point` type defined like this:
### Marking Types
AutoDeconstruct looks to see if the target type has any `Deconstruct()` methods, either as instance or extension methods (if extension methods are searched for - this is discussed later in this document). If none exist, then AutoDeconstruct looks to see how many accessible, readable, instance properties exist. If there's at least 1, the library generates a `Deconstruct()` extension method in a static class defined in the same namespace as the target type. For example, if we have our `Point` type defined like this:

```csharp
namespace Maths.Geometry;

[AutoDeconstruct]
public struct Point
{
public Point(int x, int y) =>
Expand All @@ -75,29 +87,26 @@ namespace Maths.Geometry
}
```

If the target type is a reference type, a null check will be generated. Furthermore, the `Deconstruct` extension method will also be created if a `Deconstruct` doesn't exist with the number of properties found. For example, let's say we have this:
If the target type is a reference type, a null check will be generated. Furthermore, the `Deconstruct()` extension method will also be created if a `Deconstruct()` doesn't exist with the number of properties found. For example, let's say we have this:

```csharp
using AutoDeconstruct;

namespace Models;

[AutoDeconstruct]
public sealed class Person
{
public void Deconstruct(out Guid id) =>
id = this.Id;

public uint Age { get; init; }
public Guid Id { get; init; }
public string Name { get; init; }
}

public static class PersonExtensions
{
public static void Deconstruct(this Person self, out Guid id, out string name) =>
(id, name) = (self.Id, self.Name);
public void Deconstruct(out Guid id) =>
id = this.Id;
}
```

AutoDeconstruct would see that there are three properties that could be used for a generated `Deconstruct`. The two `Deconstruct` methods that exist have one and two `out` parameters, so it will generate one that has all three properties as `out` parameters:
AutoDeconstruct would see that there are three properties that could be used for a generated `Deconstruct()`. The `Deconstruct()` method that exists has one `out` parameter, so it will generate one that has all three properties as `out` parameters:

```csharp
#nullable enable
Expand All @@ -115,19 +124,82 @@ namespace Models
}
```

While AutoDeconstruct will do a complete search for types to generate `Deconstruct` methods, a user may want to opt out of this search for specific types. Adding the `[NoAutoDeconstruct]` attribute to a type will tell AutoDeconstrct to ignore it:
### Assembly-Level Support

`[AutoDeconstruct]` can also be defined at the assembly level to inform AutoDeconstruct to add `Deconstruct()` extension methods for **every** type in the assembly:

```csharp
using AutoDeconstruct;

[assembly: AutoDeconstruct]

namespace Models;

public sealed class Person
{
public uint Age { get; init; }
public Guid Id { get; init; }
public string Name { get; init; }

public void Deconstruct(out Guid id) =>
id = this.Id;
}
```

While AutoDeconstruct will search the target type to see if an existing `Deconstruct()` method exists that matches what AutoDeconstruct would do, a user may want to opt int to a search to look through the entire assembly for `Deconstruct()` extension methods. By default, this is turned off as it's not common to create deconstruction methods this way, but if this completeness is desired, `SearchForExtensionMethods.Yes` can be passed into `[AutoDeconstruct]`:

```csharp
using AutoDeconstruct;

[assembly: AutoDeconstruct(SearchForExtensionMethods.Yes)]

namespace Models;

public sealed class Person
{
public uint Age { get; init; }
public Guid Id { get; init; }
public string Name { get; init; }

public void Deconstruct(out Guid id) =>
id = this.Id;
}

public static partial class PersonExtensions
{
public static void Deconstruct(this Person @self, out Guid @id, out string @name, out uint @age) =>
(@id, @name, @age) = (@self.Id, @self.Name, @self.Age);
}
```

In this case, AutoDeconstruct will detect an existing `Deconstruct()` method that already does what AutoDeconstruct would generate, so no code is generated. This search flag also works when `[AutoDeconstruct]` is defined on a specific type.

### Ignore Deconstruction Creation

The `[NoAutoDeconstruct]` attribute can be added to a type will tell AutoDeconstrct to ignore it. Note that this is only relevant when `[AutoDeconstruct]` is added at the assembly level:

```csharp
namespace AutoDeconstruct;
namespace Models;

[assembly: AutoDeconstruct]

[NoAutoDeconstruct]
public sealed class Person
{
public uint Age { get; init; }
public Guid Id { get; init; }
public string Name { get; init; }
}

public struct Point
{
public Point(int x, int y) =>
(this.X, this.Y) = (x, y);

public int X { get; }
public int Y { get; }
}
```

In this case, AutoDeconstruct will not generate a `Deconstruct` method for `Person`.
In this case, AutoDeconstruct will not generate a `Deconstruct` method for `Person`, but it will for `Point`. If a type has `[AutoDeconstruct]` and `[NoAutoDeconstruct]`, `[AutoDeconstruct]` "wins".
4 changes: 2 additions & 2 deletions docs/notes/16 - Go With FAWMN Approach.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
* Update docs
* DONE - Update docs
* DONE - I added `internal` properties to the list, as it's possible someone may want to deconstruct internally. But, now I need to make the static class and the extension method `internal` if either the target type is `internal`, or any of the properties are `internal`.
* DONE - Finding matching `Deconstruct()` extension methods isn't correct. We need to also look at the parameter types.
* DONE - I think I overdid it. If the `Deconstruct()` has the same number of property as parameters, then it has to be excluded, because deconstruct methods can't be overloaded with the same number of parmaters.
Expand All @@ -8,7 +8,7 @@
* DONE - Put a test in to show that a `Deconstruct()` is made even if both attributes exist on the type.
* DONE - Consider making the generated extension method part of the type declaration if the type is `partial`. See this for details: https://stackoverflow.com/questions/68906372/roslyn-analyzer-is-class-marked-as-partial
* DONE - Add code to change the name of extension type if there is a collision
* Add a flag to `[AutoDeconstruct]` to only do a full assembly search of extension method, something like `[AutoDeconstruct(SearchForExtensionMethods.Yes)]`. It would be `SearchForExtensionMethods.No` by default, you need to opt-in to do the search. Most of the time, the search is unnecessary.
* DONE - Add a flag to `[AutoDeconstruct]` to only do a full assembly search of extension method, something like `[AutoDeconstruct(SearchForExtensionMethods.Yes)]`. It would be `SearchForExtensionMethods.No` by default, you need to opt-in to do the search. Most of the time, the search is unnecessary.


Also...https://discord.com/channels/143867839282020352/598678594750775301/1068691525615239168
7 changes: 5 additions & 2 deletions src/AutoDeconstruct/AutoDeconstruct.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>AutoDeconstruct</AssemblyName>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
Expand All @@ -9,6 +9,7 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<IncludeSymbols>true</IncludeSymbols>
<PackageIcon>Icon.png</PackageIcon>
<PackageId>AutoDeconstruct</PackageId>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageProjectUrl>https://github.com/jasonbock/autodeconstruct</PackageProjectUrl>
Expand All @@ -17,17 +18,19 @@
<PackageTags>deconstruct;source generator</PackageTags>
<PackageVersion>$(Version)</PackageVersion>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/jasonbock/autodeconstruct</RepositoryUrl>
<RootNamespace>AutoDeconstruct</RootNamespace>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\README.md" Pack="true" PackagePath="" />
<None Include="..\..\LICENSE" Pack="true" PackagePath="" />
<None Include="..\Images\Icon.png" Pack="true" PackagePath="" Visible="false" />
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>
</Project>
Binary file added src/Images/Icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 00fcb3d

Please sign in to comment.