{"id":13629522,"url":"https://github.com/archi-Doc/ValueLink","last_synced_at":"2025-04-17T09:34:31.120Z","repository":{"id":41589496,"uuid":"340571331","full_name":"archi-Doc/ValueLink","owner":"archi-Doc","description":"ValueLink is a C# Library for creating and managing multiple links between objects.","archived":false,"fork":false,"pushed_at":"2024-05-28T16:19:41.000Z","size":920,"stargazers_count":7,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-05-29T01:06:56.153Z","etag":null,"topics":["csharp","csharp-sourcegenerator","dotnet","source-generators"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/archi-Doc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-02-20T05:37:06.000Z","updated_at":"2024-05-30T02:55:22.300Z","dependencies_parsed_at":"2023-10-15T02:59:33.480Z","dependency_job_id":"e606c406-4f3a-42dd-84cd-b50bf4c5ebec","html_url":"https://github.com/archi-Doc/ValueLink","commit_stats":{"total_commits":206,"total_committers":1,"mean_commits":206.0,"dds":0.0,"last_synced_commit":"454fb81f3c96db215d9eec8d9cdf0f68edf93cad"},"previous_names":["archi-doc/crosslink"],"tags_count":52,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/archi-Doc%2FValueLink","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/archi-Doc%2FValueLink/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/archi-Doc%2FValueLink/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/archi-Doc%2FValueLink/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/archi-Doc","download_url":"https://codeload.github.com/archi-Doc/ValueLink/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223751199,"owners_count":17196588,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","csharp-sourcegenerator","dotnet","source-generators"],"created_at":"2024-08-01T22:01:12.730Z","updated_at":"2024-11-08T20:31:08.633Z","avatar_url":"https://github.com/archi-Doc.png","language":"C#","readme":"## ValueLink\n![Nuget](https://img.shields.io/nuget/v/ValueLink) ![Build and Test](https://github.com/archi-Doc/ValueLink/workflows/Build%20and%20Test/badge.svg)\n\nValueLink is a C# Library for creating and managing multiple links between objects.\n\nIt's like generic collections for objects, like ```List\u003cT\u003e``` for ```T```, but ValueLink is more flexible and faster than generic collections.\n\n\n\nThis document may be inaccurate. It would be greatly appreciated if anyone could make additions and corrections.\n\n日本語ドキュメントは[こちら](/doc/README.jp.md)\n\n\n\n## Table of Contents\n\n- [Requirements](#requirements)\n- [Quick Start](#quick-start)\n- [Performance](#performance)\n- [How it works](#how-it-works)\n- [Chains](#chains)\n- [Features](#features)\n  - [Serialization](#serialization)\n  - [Isolation level](#isolation-level)\n  - [Additional methods](#additional-methods)\n  - [TargetMember](#targetmember)\n  - [AutoNotify](#autonotify)\n  - [AutoLink](#autolink)\n  - [ObservableCollection](#observablecollection)\n\n\n\n## Requirements\n\n**Visual Studio 2022** or later for Source Generator V2.\n\n**C# 12** or later for generated codes.\n\n**.NET 8** or later target framework.\n\n\n\n## Quick Start\n\nFirst, install ValueLink using Package Manager Console.\n\n```\nInstall-Package ValueLink\n```\n\nThis is a sample code to use ValueLink.\n\n```csharp\nusing System;\nusing System.Collections.Generic;\nusing ValueLink;\n\nnamespace ConsoleApp1;\n\n[ValueLinkObject] // Annote a ValueLinkObject attribute.\npublic partial class TestClass // Partial class is required for source generator.\n{\n    [Link(Type = ChainType.Ordered)] // Sorted link associated with id.\n    private int id; // Generated value name: IdValue (Name + Value), chain name: IdChain (Name + Chain)\n    // Generated value is for changing values and updating links.\n    // Generated link is for storing information between objects, similar to a node in a collection.\n\n    [Link(Type = ChainType.Ordered)] // Sorted link associated with name.\n    public string Name { get; private set; } = string.Empty; // Generated property name: NameValue, chain name: NameChain\n\n    [Link(Type = ChainType.Ordered, Accessibility = ValueLinkAccessibility.Public)] // Sorted link associated with age.\n    [Link(Name = \"AgeRev\", Type = ChainType.ReverseOrdered)] // Specify a different name for the target in order to set up multiple links.\n    private int age; // Generated property name: AgeValue, chain name: AgeChain\n\n    [Link(Type = ChainType.StackList, Name = \"Stack\")] // Stack\n    [Link(Type = ChainType.List, Name = \"List\")] // List\n    public TestClass(int id, string name, int age)\n    {\n        this.id = id;\n        this.Name = name;\n        this.age = age;\n    }\n\n    public override string ToString() =\u003e $\"ID:{this.id,2}, {this.Name,-5}, {this.age,2}\";\n}\n\npublic class Program\n{\n    public static void Main(string[] args)\n    {\n        Console.WriteLine(\"ValueLink Quick Start.\");\n        Console.WriteLine();\n\n        var g = new TestClass.GoshujinClass(); // Create a Goshujin (Owner) instance\n        new TestClass(1, \"Hoge\", 27).Goshujin = g; // Create a TestClass and associate with the Goshujin (Owner)\n        new TestClass(2, \"Fuga\", 15).Goshujin = g;\n        new TestClass(1, \"A\", 7).Goshujin = g;\n        new TestClass(0, \"Zero\", 50).Goshujin = g;\n\n        ConsoleWriteIEnumerable(\"[List]\", g.ListChain); // ListChain is virtually List\u003cTestClass\u003e\n        /* Result;  displayed in the order in which they were created.\n             ID: 1, Hoge , 27\n             ID: 2, Fuga , 15\n             ID: 1, A    ,  7\n             ID: 0, Zero , 50 */\n\n        Console.WriteLine(\"ListChain[2] : \"); // ListChain can be accessed by index.\n        Console.WriteLine(g.ListChain[2]); // ID: 1, A    ,  7\n        Console.WriteLine();\n\n        ConsoleWriteIEnumerable(\"[Sorted by Id]\", g.IdChain);\n        /* Sorted by Id\n             ID: 0, Zero , 50\n             ID: 1, Hoge , 27\n             ID: 1, A    ,  7\n             ID: 2, Fuga , 15 */\n\n        ConsoleWriteIEnumerable(\"[Sorted by Name]\", g.NameChain);\n        /* Sorted by Name\n             ID: 1, A    ,  7\n             ID: 2, Fuga , 15\n             ID: 1, Hoge , 27\n             ID: 0, Zero , 50 */\n\n        ConsoleWriteIEnumerable(\"[Sorted by Age]\", g.AgeChain);\n        /* Sorted by Age\n             ID: 1, A    ,  7\n             ID: 2, Fuga , 15\n             ID: 1, Hoge , 27\n             ID: 0, Zero , 50 */\n\n        ConsoleWriteIEnumerable(\"[Sorted by Age in reverse order]\", g.AgeRevChain);\n        /* Sorted by Age\n             ID: 0, Zero , 50\n             ID: 1, Hoge , 27\n             ID: 2, Fuga , 15\n             ID: 1, A    ,  7\n              */\n\n        var t = g.ListChain[1];\n        Console.WriteLine($\"{t.NameValue} age {t.AgeValue} =\u003e 95\"); // Change Fuga's age to 95.\n        t.AgeValue = 95;\n        ConsoleWriteIEnumerable(\"[Sorted by Age]\", g.AgeChain);\n        /* AgeChain will be updated automatically.\n             ID: 1, A    ,  7\n             ID: 1, Hoge , 27\n             ID: 0, Zero , 50\n             ID: 2, Fuga , 95 */\n\n        ConsoleWriteIEnumerable(\"[Stack]\", g.StackChain);\n        /* Stack chain\n             ID: 1, Hoge , 27\n             ID: 2, Fuga , 95\n             ID: 1, A    ,  7\n             ID: 0, Zero , 50 */\n\n        t = g.StackChain.Pop(); // Pop an object. Note that only StackChain is affected.\n        Console.WriteLine($\"{t.NameValue} =\u003e Pop\");\n        t.Goshujin = null; // To remove the object from other chains, you need to set Goshujin to null.\n        Console.WriteLine();\n\n        ConsoleWriteIEnumerable(\"[Stack]\", g.StackChain);\n        /* Zero is removed.\n             ID: 1, Hoge , 27\n             ID: 2, Fuga , 95\n             ID: 1, A    ,  7 */\n\n        var g2 = new TestClass.GoshujinClass(); // New Goshujin2\n        t = g.ListChain[0];\n        Console.WriteLine($\"{t.Name} Goshujin =\u003e Goshujin2\");\n        Console.WriteLine();\n        t.Goshujin = g2; // Change from Goshujin to Goshujin2.\n        ConsoleWriteIEnumerable(\"[Goshujin]\", g.ListChain);\n        ConsoleWriteIEnumerable(\"[Goshujin2]\", g2.ListChain);\n        /*\n         * [Goshujin]\n             ID: 2, Fuga , 95\n             ID: 1, A    ,  7\n            [Goshujin2]\n             ID: 1, Hoge , 27*/\n\n        // g.IdChain.Remove(t); // Exception is thrown because this object belongs to Goshujin2.\n        // t.Goshujin.IdChain.Remove(t); // No exception.\n\n        Console.WriteLine(\"[IdChain First/Next]\");\n        t = g.IdChain.First; // Enumerate objects using Link interface.\n        while (t != null)\n        {\n            Console.WriteLine(t);\n            t = t.IdLink.Next; // Note that Next is not a Link, but an object.\n        }\n\n        Console.WriteLine();\n        Console.WriteLine(\"Goshujin.Remove\");\n        g.Remove(g.ListChain[0]); // You can use Remove() instead of 'g.ListChain[0].Goshujin = null;'\n        ConsoleWriteIEnumerable(\"[Goshujin]\", g.ListChain);\n\n        static void ConsoleWriteIEnumerable\u003cT\u003e(string? header, IEnumerable\u003cT\u003e e)\n        {\n            if (header != null)\n            {\n                Console.WriteLine(header);\n            }\n\n            foreach (var x in e)\n            {\n                Console.WriteLine(x!.ToString());\n            }\n\n            Console.WriteLine();\n        }\n    }\n}\n```\n\n\n\n## Performance\n\nPerformance is the top priority.\n\nAlthough ValueLink do a little bit complex process than generic collections, ValueLink works faster than generic collections.\n\nThis is a benchmark with the generic collection ```SortedDictionary\u003cTKey, TValue\u003e```.\nThe following code creates an instance of a collection, creates a H2HClass and adds to the collection in sorted order.\n\n```csharp\nvar g = new SortedDictionary\u003cint, H2HClass\u003e();\nforeach (var x in this.IntArray)\n{\n    g.Add(x, new H2HClass(x));\n}\n```\n\nThis is the ValueLink version and it does almost the same process (In fact, ValueLink is more scalable and flexible).\n\n```csharp\nvar g = new H2HClass2.GoshujinClass();\nforeach (var x in this.IntArray)\n{\n    new H2HClass2(x).Goshujin = g;\n}\n```\n\nThe result; ValueLink is faster than plain ```SortedDictionary\u003cTKey, TValue\u003e```.\n\n| Method                     | Length |       Mean |    Error |   StdDev |  Gen 0 |  Gen 1 | Gen 2 | Allocated |\n| -------------------------- | ------ | ---------: | -------: | -------: | -----: | -----: | ----: | --------: |\n| NewAndAdd_SortedDictionary | 100    | 7,209.8 ns | 53.98 ns | 77.42 ns | 1.9379 |      - |     - |    8112 B |\n| NewAndAdd_ValueLink        | 100    | 4,942.6 ns | 12.28 ns | 17.99 ns | 2.7084 | 0.0076 |     - |   11328 B |\n\nWhen it comes to modifying an object (remove/add), ValueLink is much faster than the generic collection.\n\n| Method                        | Length |       Mean |    Error |   StdDev |  Gen 0 | Gen 1 | Gen 2 | Allocated |\n| ----------------------------- | ------ | ---------: | -------: | -------: | -----: | ----: | ----: | --------: |\n| RemoveAndAdd_SortedDictionary | 100    | 1,491.1 ns | 13.01 ns | 18.24 ns | 0.1335 |     - |     - |     560 B |\n| RemoveAndAdd_ValueLink        | 100    |   524.1 ns |  3.76 ns |  5.63 ns | 0.1717 |     - |     - |     720 B |\n\n\n\n## How it works\n\nValueLink works by adding an inner class and some properties to the existing class. \n\nThe actual behavior is\n\n1. Adds an inner class named ```GoshujinClass``` to the target object.\n2. Adds a property named ```Goshujin``` to the target object.\n3. Creates a property which corresponds to the member with a Link attribute. The first letter of the property will be capitalized. For example, ```id``` becomes ```Id```. \n4. Creates a ```Link``` field. The name of the field will the concatenation of the property name and ```Link```. For example, ```Id``` becomes ```IdLink```.\n\n\n\nThe terms\n\n- ```Object```: An object that stores information and is the target to be connected.\n- ```Goshujin```: An owner class of the objects.  It's for storing and manipulating objects.\n- ```Chain```: Chain is like a generic collection. Goshujin can have multiple Chains that manage objects in various ways.\n- ```Link```: Link is like a node. An object can have multiple Links that hold information about relationships between objects.\n\n\n\nThis is a tiny class to demonstrate how ValueLink works.\n\n```csharp\npublic partial class TinyClass // Partial class is required for source generator.\n{\n    [Link(Type = ChainType.Ordered)] // Add a Link attribute to a member.\n    private int Id;\n}\n```\n\nWhen building a project, ValueLink first creates an inner class called ```GoshujinClass```. ```GoshujinClass``` is an owner class for storing and manipulating multiple ```TinyClass``` instances.\n\n```csharp\npublic sealed class GoshujinClass : IGoshujin // IGoshujin is a base interface for Goshujin\n{// Goshujin-sama means an owner in Japanese.\n    \n    public GoshujinClass()\n    {\n        // IdChain is a collection of TinyClass that are maintained in a sorted order.\n        this.IdChain = new(this, static x =\u003e x.__gen_cl_identifier__001, static x =\u003e ref x.IdLink);\n    }\n\n    public OrderedChain\u003cint, TinyClass\u003e IdChain { get; }\n}\n```\n\nThe following code adds a field and a property that holds a ```Goshujin``` instance.\n\n```csharp\nprivate GoshujinClass? __gen_cl_identifier__001; // Actual Goshujin instance.\n\npublic GoshujinClass? Goshujin\n{\n    get =\u003e this.__gen_cl_identifier__001; // Getter\n    set\n    {// Set a Goshujin instance.\n        if (value != this.__gen_cl_identifier__001)\n        {\n            if (this.__gen_cl_identifier__001 != null)\n            {// Remove the TinyClass from the previous Goshujin.\n                this.__gen_cl_identifier__001.IdChain.Remove(this);\n            }\n\n            this.__gen_cl_identifier__001 = value;// Set a new value.\n            if (value != null)\n            {// Add the TinyClass to the new Goshujin.\n                value.IdChain.Add(this.Id, this);\n            }\n        }\n    }\n}\n```\n\nFinally, ValueLink adds a link and a property which is used to modify the collection and change the value.\n\n```csharp\npublic OrderedChain\u003cint, TinyClass\u003e.Link IdLink; // Link is like a Node.\n\npublic int IdValue\n{// Property \"IdValue\" is created from a member \"Id\".\n    get =\u003e this.Id;\n    set\n    {\n        if (value != this.Id)\n        {\n            this.Id = value;\n            // IdChain will be updated when the value is changed.\n            this.Goshujin.IdChain.Add(this.Id, this);\n        }\n    }\n}\n```\n\n\n\n## Chains\n\nChain is like a generic collection. `Goshujin` can have multiple chains corresponding to the Link attributes.\n\nValueLink provides several kinds of chains.\n\n| Name                  | Structure   | Access | Add      | Remove   | Search   | Sort       | Enum.    |\n| --------------------- | ----------- | ------ | -------- | -------- | -------- | ---------- | -------- |\n| ```ListChain```       | Array       | Index  | O(1)     | O(n)     | O(n)     | O(n log n) | O(1)     |\n| ```LinkedListChain``` | Linked list | Node   | O(1)     | O(1)     | O(n)     | O(n log n) | O(1)     |\n| ```QueueListChain```  | Linked list | Node   | O(1)     | O(1)     | O(n)     | O(n log n) | O(1)     |\n| ```StackListChain```  | Linked list | Node   | O(1)     | O(1)     | O(n)     | O(n log n) | O(1)     |\n| ```OrderedChain```    | RB Tree     | Node   | O(log n) | O(log n) | O(log n) | Sorted     | O(log n) |\n| `ReverseOrderedChain` | RB Tree     | Node   | O(log n) | O(log n) | O(log n) | Sorted     | O(log n) |\n| ```UnorderedChain```  | Hash table  | Node   | O(1)     | O(1)     | O(1)     | -          | O(1)     |\n| ```ObservableChain``` | Array       | Index  | O(1)     | O(n)     | O(n)     | O(n log n) | O(1)     |\n\nIf you want a new chain to be implemented, please let me know with a GitHub issue.\n\n\n\n## Link\n\nLink is like a node. An object can have multiple Links that hold information about relationships between objects.\n\nEach link corresponds to a chain.\n\n\n\n## Features\n\n### Serialization\n\nSerializing multiple linked objects is a complicated task. However, with [Tinyhand](https://github.com/archi-Doc/Tinyhand), you can easily serialize/deserialize objects.\n\nAll you need to do is install ```Tinyhand``` package and add a ```TinyhandObject``` attribute and ```Key``` attributes to the existing object.\n\n```\nInstall-Package Tinyhand\n```\n\n```csharp\n[ValueLinkObject]\n[TinyhandObject] // Add a TinyhandObject attribute to use TinyhandSerializer.\npublic partial class SerializeClass\n{\n    [Link(Type = ChainType.Ordered, Primary = true)] // Set primary link that is guaranteed to holds all objects in the collection in order to maximize the performance of serialization.\n    [Key(0)] // Add a Key attribute to specify the key for serialization as a number or string.\n    private int id;\n\n    [Link(Type = ChainType.Ordered)]\n    [Key(1)]\n    private string name = default!;\n\n    public SerializeClass()\n    {// Default constructor is required for Tinyhand.\n    }\n\n    public SerializeClass(int id, string name)\n    {\n        this.id = id;\n        this.name = name;\n    }\n}\n```\n\nTest code:\n\n```csharp\nvar g = new SerializeClass.GoshujinClass(); // Create a new Goshujin.\nnew SerializeClass(1, \"Hoge\").Goshujin = g; // Add an object.\nnew SerializeClass(2, \"Fuga\").Goshujin = g;\n\nvar st = TinyhandSerializer.SerializeToString(g); // Serialize the Goshujin to string.\nvar g2 = TinyhandSerializer.Deserialize\u003cSerializeClass.GoshujinClass\u003e(TinyhandSerializer.Serialize(g)); // Serialize to a byte array and deserialize it.\n```\n\n\n\n### Isolation level\n\nValueLink offers several different isolation levels.\n\n\n\n#### IsolationLevel.None\n\nThere is no additional code generated for isolation\n\n\n\n#### IsolationLevel.Serializable\n\nFor lock-based concurrency control, the following code is added to the `Goshujin` class.\n\nPlease lock the `SyncObject` on the user side to perform exclusive operations.\n\n```csharp\npublic object SyncObject { get; }\n```\n\n```csharp\n[ValueLinkObject(Isolation = IsolationLevel.Serializable)]\npublic partial record SerializableRoom\n{\n    [Link(Primary = true, Type = ChainType.Ordered, AddValue = false)]\n    public int RoomId { get; set; }\n\n    public SerializableRoom(int roomId)\n    {\n    }\n}\n```\n\n\n\n#### IsolationLevel.RepeatableRead\n\nUnlike the above-mentioned Isolation levels, a lot of code is added.\n\nEssentially, Objects become immutable, allowing for arbitrary reads. To write, you need to retrieve the object by calling `TryLock()` from the `Goshujin` class and then invoke `Commit()`.\n\n```csharp\n// An example of an object with the IsolationLevel set to RepeatableRead.\n[TinyhandObject]\n[ValueLinkObject(Isolation = IsolationLevel.RepeatableRead)]\npublic partial record RepeatableClass\n{// Record class is required for IsolationLevel.RepeatableRead.\n    public RepeatableClass()\n    {// Default constructor is required.\n    }\n\n    public RepeatableClass(int id)\n    {\n        this.Id = id;\n    }\n\n    // A unique link is required for IsolationLevel.RepeatableRead, and a primary link is preferred for TinyhandSerializer.\n    [Key(0)]\n    [Link(Primary = true, Unique = true, Type = ChainType.Ordered)]\n    public int Id { get; private set; }\n\n    [Key(1)]\n    public string Name { get; private set; } = string.Empty;\n\n    [Key(2)]\n    public List\u003cint\u003e IntList { get; private set; } = new();\n\n    public override string ToString()\n        =\u003e $\"Id: {this.Id.ToString()}, Name: {this.Name}\";\n\n    public static void Test()\n    {\n        var g = new RepeatableClass.GoshujinClass(); // Create a goshujin.\n\n        g.Add(new RepeatableClass(0)); // Adds an object with id 0.\n\n        using (var w = g.TryLock(1, TryLockMode.Create))\n        {// Alternative: adds an object with id 1.\n            w?.Commit(); // Commit the change.\n        }\n\n        var r0 = g.TryGet(0);\n        Console.WriteLine(r0?.ToString()); // Id: 0, Name:\n        Console.WriteLine();\n\n        using (var w = g.TryLock(0))\n        {\n            if (w is not null)\n            {\n                w.Name = \"Zero\";\n                w.Commit();\n            }\n        }\n    }\n}\n```\n\n\n\n### Additional methods\n\nBy adding methods within the class, you can determine whether to link or not, and add code to perform actions after the link has been added or removed.\n\n```csharp\n[ValueLinkObject]\npublic partial class AdditionalMethodClass\n{\n    public static int TotalAge;\n\n    [Link(Type = ChainType.Ordered)]\n    private int age;\n\n    protected bool AgeLinkPredicate()\n    {// bool Name+Link+Predicate(): Determines whether to add the object to the chain or not.\n        return this.age \u003e= 20;\n    }\n\n    protected void AgeLinkAdded()\n    {// void Name+Link+Added(): Performs post-processing after the object has been added to the chain.\n        TotalAge += this.age;\n    }\n\n    protected void AgeLinkRemoved()\n    {// void Name+Link+Removed(): Performs post-processing after the object has been removed from the chain.\n        TotalAge -= this.age;\n    }\n}\n```\n\n\n\n### TargetMember\n\nIf you want to create multiple goshujins from a single class, use `TargetMember` property.\n\n```csharp\npublic class BaseClass\n{// Base class is not ValueLinkObject.\n    protected int id;\n\n    protected string name = string.Empty;\n}\n\n[ValueLinkObject]\npublic partial class DerivedClass : BaseClass\n{\n    // Add Link attribute to constructor and set TargetMember.\n    [Link(TargetMember = nameof(id), Type = ChainType.Ordered)]\n    [Link(TargetMember = nameof(name), Type = ChainType.Ordered)]\n    public DerivedClass()\n    {\n    }\n}\n\n[ValueLinkObject]\npublic partial class DerivedClass2 : BaseClass\n{\n    // Multiple ValueLinkObject can be created from the same base class.\n    [Link(TargetMember = nameof(id), Type = ChainType.Unordered)]\n    [Link(TargetMember = nameof(name), Type = ChainType.ReverseOrdered)]\n    public DerivedClass2()\n    {\n    }\n}\n\n/*[ValueLinkObject] // Error! Derivation from other ValueLink objects is not supported.\npublic partial class DerivedClass3 : DerivedClass\n{\n    [Link(Type = ChainType.Ordered)]\n    protected string name2 = string.Empty;\n}*/\n\n```\n\n\n\n### AutoNotify\n\nBy adding a ```Link``` attribute and setting ```AutoNotify``` to true, ValueLink can implement the `INotifyPropertyChanged` pattern automatically.\n\n```csharp\n[ValueLinkObject]\npublic partial class AutoNotifyClass\n{\n    [Link(AutoNotify = true)] // Set AutoNotify to true.\n    private int id;\n\n    public void Reset()\n    {\n        this.SetProperty(ref this.id, 0); // Change the value manually and invoke PropertyChanged.\n    }\n}\n```\n\nTest code:\n\n```csharp\nvar c = new AutoNotifyClass();\nc.PropertyChanged += (s, e) =\u003e { Console.WriteLine($\"Id changed: {((AutoNotifyClass)s!).idValue}\"); };\nc.idValue = 1; // Change the value and automatically invoke PropertyChange.\nc.Reset(); // Reset the value.\n```\n\nGenerated code:\n\n```csharp\npublic partial class AutoNotifyClass : System.ComponentModel.INotifyPropertyChanged\n{\n    public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged;\n\n    protected virtual bool SetProperty\u003cT\u003e(ref T storage, T value, [CallerMemberName] string? propertyName = null)\n    {\n        if (EqualityComparer\u003cT\u003e.Default.Equals(storage, value))\n        {\n            return false;\n        }\n        \n        storage = value;\n        this.PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));\n        return true;\n    }\n\n    public int idValue\n    {\n        get =\u003e this.id;\n        set\n        {\n            if (value != this.id)\n            {\n                this.id = value;\n                this.PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(\"idValue\"));\n            }\n        }\n    }\n}\n```\n\n\n\n### AutoLink\n\nBy default, ValueLink will automatically link the object when a goshujin is set or changed.\n\nYou can change this behavior by setting AutoLink to false.\n\n ```csharp\n[ValueLinkObject]\npublic partial class ManualLinkClass\n{\n    [Link(Type = ChainType.Ordered, AutoLink = false)] // Set AutoLink to false.\n    private int id;\n\n    public ManualLinkClass(int id)\n    {\n        this.id = id;\n    }\n\n    public static void Test()\n    {\n        var g = new ManualLinkClass.GoshujinClass();\n\n        var c = new ManualLinkClass(1);\n        c.Goshujin = g;\n        Debug.Assert(g.idChain.Count == 0, \"Chain is empty.\");\n\n        g.IdChain.Add(c.id, c); // Link the object manually.\n        Debug.Assert(g.idChain.Count == 1, \"Object is linked.\");\n    }\n}\n ```\n\n\n\n### ObservableCollection\n\nYou can make the collection available for binding by adding ```ObservableChain```.\n\n```ObservableChain``` is actually a wrapper class of ```ObservableCollection\u003cT\u003e```.\n\n```csharp\n[ValueLinkObject]\npublic partial class ObservableClass\n{\n    [Link(Type = ChainType.Ordered, AutoNotify = true)]\n    private int Id { get; set; }\n\n    [Link(Type = ChainType.Observable, Name = \"Observable\")]\n    public ObservableClass(int id)\n    {\n        this.Id = id;\n    }\n}\n```\n\nTest code:\n\n```csharp\nvar g = new ObservableClass.GoshujinClass();\nListView.ItemSource = g.ObservableChain;// You can use ObservableChain as ObservableCollection.\nnew ObservableClass(1).Goshujin = g;// ListView will be updated.\n```\n\n","funding_links":[],"categories":["Contributors Welcome for those","Source Generators"],"sub_categories":["1. [ThisAssembly](https://ignatandrei.github.io/RSCG_Examples/v2/docs/ThisAssembly) , in the [EnhancementProject](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#enhancementproject) category","Other"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farchi-Doc%2FValueLink","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farchi-Doc%2FValueLink","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farchi-Doc%2FValueLink/lists"}