|
| 1 | +# Table of Contents |
| 2 | +- [What is a Deconstructor?](#what-is-a-deconstructor?) |
| 3 | +- [AutoDeconstruct Features](#autodeconstruct-rationale) |
| 4 | + |
| 5 | +## What is a Deconstructor? |
| 6 | + |
| 7 | +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: |
| 8 | + |
| 9 | +```csharp |
| 10 | +using System; |
| 11 | + |
| 12 | +public sealed class Customer |
| 13 | +{ |
| 14 | + public Customer(Guid id, string name) => |
| 15 | + (this.Id, this.Name) = (id, name); |
| 16 | + |
| 17 | + public void Deconstruct(out Guid id, out string name) => |
| 18 | + (id, name) = (this.Id, this.Name); |
| 19 | + |
| 20 | + public Guid Id { get; } |
| 21 | + public string Name { get; } |
| 22 | +} |
| 23 | + |
| 24 | +var customer = new Customer(Guid.NewGuid(), "Jason"); |
| 25 | +var (id, name) = customer; |
| 26 | + |
| 27 | +public struct Point |
| 28 | +{ |
| 29 | + public Point(int x, int y) => |
| 30 | + (this.X, this.Y) = (x, y); |
| 31 | + |
| 32 | + public int X { get; } |
| 33 | + public int Y { get; } |
| 34 | +} |
| 35 | + |
| 36 | +public static class PointExtensions |
| 37 | +{ |
| 38 | + public static void Deconstruct(this Point self, out int x, out int y) => |
| 39 | + (x, y) = (self.X, self.Y); |
| 40 | +} |
| 41 | + |
| 42 | +var point = new Point(2, 3); |
| 43 | +var (x, y) = point; |
| 44 | +``` |
| 45 | + |
| 46 | +## AutoDeconstruct Features |
| 47 | + |
| 48 | +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: |
| 49 | + |
| 50 | +```csharp |
| 51 | +namespace Maths.Geometry; |
| 52 | + |
| 53 | +public struct Point |
| 54 | +{ |
| 55 | + public Point(int x, int y) => |
| 56 | + (this.X, this.Y) = (x, y); |
| 57 | + |
| 58 | + public int X { get; } |
| 59 | + public int Y { get; } |
| 60 | +} |
| 61 | +``` |
| 62 | + |
| 63 | +Then the library generates this: |
| 64 | + |
| 65 | +```csharp |
| 66 | +#nullable enable |
| 67 | + |
| 68 | +namespace Maths.Geometry |
| 69 | +{ |
| 70 | + public static partial class PointExtensions |
| 71 | + { |
| 72 | + public static void Deconstruct(this global::Maths.Geometry.Point @self, out int @x, out int @y) => |
| 73 | + (@x, @y) = (@self.X, @self.Y); |
| 74 | + } |
| 75 | +} |
| 76 | +``` |
| 77 | + |
| 78 | +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: |
| 79 | + |
| 80 | +```csharp |
| 81 | +namespace Models; |
| 82 | + |
| 83 | +public sealed class Person |
| 84 | +{ |
| 85 | + public void Deconstruct(out Guid id) => |
| 86 | + id = this.Id; |
| 87 | + |
| 88 | + public uint Age { get; init; } |
| 89 | + public Guid Id { get; init; } |
| 90 | + public string Name { get; init; } |
| 91 | +} |
| 92 | + |
| 93 | +public static class PersonExtensions |
| 94 | +{ |
| 95 | + public static void Deconstruct(this Person self, out Guid id, out string name) => |
| 96 | + (id, name) = (self.Id, self.Name); |
| 97 | +} |
| 98 | +``` |
| 99 | + |
| 100 | +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: |
| 101 | + |
| 102 | +```csharp |
| 103 | +#nullable enable |
| 104 | + |
| 105 | +namespace Models |
| 106 | +{ |
| 107 | + public static partial class PersonExtensions |
| 108 | + { |
| 109 | + public static void Deconstruct(this global::Models.Person @self, out global::System.Guid @id, out string @name, out uint @age) |
| 110 | + { |
| 111 | + global::System.ArgumentNullException.ThrowIfNull(@self); |
| 112 | + (@id, @name, @age) = (@self.Id, @self.Name, @self.Age); |
| 113 | + } |
| 114 | + } |
| 115 | +} |
| 116 | +``` |
| 117 | + |
| 118 | +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: |
| 119 | + |
| 120 | +```csharp |
| 121 | +namespace AutoDeconstruct; |
| 122 | +namespace Models; |
| 123 | + |
| 124 | +[NoAutoDeconstruct] |
| 125 | +public sealed class Person |
| 126 | +{ |
| 127 | + public uint Age { get; init; } |
| 128 | + public Guid Id { get; init; } |
| 129 | + public string Name { get; init; } |
| 130 | +} |
| 131 | +``` |
| 132 | + |
| 133 | +In this case, AutoDeconstruct will not generate a `Deconstruct` method for `Person`. |
0 commit comments