diff --git a/README.md b/README.md index fbafbc1..b22ac29 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,4 @@ A library that automatically adds support for object deconstruction in C#. 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. -Read [the design document](docs/Design.md) for further details. \ No newline at end of file +Read [the overview document](docs/Overview.md) for further details. \ No newline at end of file diff --git a/changelog.md b/changelog.md index 421a709..d3270ef 100644 --- a/changelog.md +++ b/changelog.md @@ -5,13 +5,16 @@ 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.0.0] - Not Released Yest +## [1.0.0] - 2022.11.25 ### Added - Generated code now uses fully-qualified named (issue [#5](https://github.com/JasonBock/AutoDeconstruct/issues/5)) - Records are now considered for `Deconstruct()` generation (issue [#4](https://github.com/JasonBock/AutoDeconstruct/issues/4)) - Putting `[NoAutoDeconstruct]` on a type will signal AutoDeconstruct not to generate a `Deconstruct()` method (issue [#6](https://github.com/JasonBock/AutoDeconstruct/issues/6)) - All parameter names now have `@` in front to eliminate any potential keyword conflicts (issue [#9](https://github.com/JasonBock/AutoDeconstruct/issues/9)) + +### Changed +- Updated documentation (issue [#7](https://github.com/JasonBock/AutoDeconstruct/issues/7)) - Generated code is now stored in one file (issue [#2](https://github.com/JasonBock/AutoDeconstruct/issues/2)) ### Fixed diff --git a/docs/Overview.md b/docs/Overview.md new file mode 100644 index 0000000..d9a8ca6 --- /dev/null +++ b/docs/Overview.md @@ -0,0 +1,133 @@ +# Table of Contents +- [What is a Deconstructor?](#what-is-a-deconstructor?) +- [AutoDeconstruct Features](#autodeconstruct-rationale) + +## 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: + +```csharp +using System; + +public sealed class Customer +{ + public Customer(Guid id, string name) => + (this.Id, this.Name) = (id, name); + + public void Deconstruct(out Guid id, out string name) => + (id, name) = (this.Id, this.Name); + + public Guid Id { get; } + public string Name { get; } +} + +var customer = new Customer(Guid.NewGuid(), "Jason"); +var (id, name) = customer; + +public struct Point +{ + public Point(int x, int y) => + (this.X, this.Y) = (x, y); + + public int X { get; } + public int Y { get; } +} + +public static class PointExtensions +{ + public static void Deconstruct(this Point self, out int x, out int y) => + (x, y) = (self.X, self.Y); +} + +var point = new Point(2, 3); +var (x, y) = point; +``` + +## 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: + +```csharp +namespace Maths.Geometry; + +public struct Point +{ + public Point(int x, int y) => + (this.X, this.Y) = (x, y); + + public int X { get; } + public int Y { get; } +} +``` + +Then the library generates this: + +```csharp +#nullable enable + +namespace Maths.Geometry +{ + public static partial class PointExtensions + { + public static void Deconstruct(this global::Maths.Geometry.Point @self, out int @x, out int @y) => + (@x, @y) = (@self.X, @self.Y); + } +} +``` + +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 +namespace Models; + +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); +} +``` + +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: + +```csharp +#nullable enable + +namespace Models +{ + public static partial class PersonExtensions + { + public static void Deconstruct(this global::Models.Person @self, out global::System.Guid @id, out string @name, out uint @age) + { + global::System.ArgumentNullException.ThrowIfNull(@self); + (@id, @name, @age) = (@self.Id, @self.Name, @self.Age); + } + } +} +``` + +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: + +```csharp +namespace AutoDeconstruct; +namespace Models; + +[NoAutoDeconstruct] +public sealed class Person +{ + public uint Age { get; init; } + public Guid Id { get; init; } + public string Name { get; init; } +} +``` + +In this case, AutoDeconstruct will not generate a `Deconstruct` method for `Person`. \ No newline at end of file diff --git a/docs/Design.md b/docs/notes/Design.md similarity index 100% rename from docs/Design.md rename to docs/notes/Design.md