{"id":28825733,"url":"https://github.com/doraku/defaultundo","last_synced_at":"2026-02-21T16:03:29.384Z","repository":{"id":119786390,"uuid":"286251082","full_name":"Doraku/DefaultUnDo","owner":"Doraku","description":"Undo/redo library aiming for ease of integration and usage simplicity.","archived":false,"fork":false,"pushed_at":"2025-05-30T22:20:56.000Z","size":418,"stargazers_count":21,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-19T02:06:44.362Z","etag":null,"topics":["command-pattern","csharp","dotnet","memento-pattern","undo-redo"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit-0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Doraku.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","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,"zenodo":null},"funding":{"custom":"https://paypal.me/LaszloPaillat"}},"created_at":"2020-08-09T14:17:08.000Z","updated_at":"2025-05-30T22:20:59.000Z","dependencies_parsed_at":null,"dependency_job_id":"f535dcd8-401a-47b6-8523-1be2d5208ff7","html_url":"https://github.com/Doraku/DefaultUnDo","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/Doraku/DefaultUnDo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Doraku%2FDefaultUnDo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Doraku%2FDefaultUnDo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Doraku%2FDefaultUnDo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Doraku%2FDefaultUnDo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Doraku","download_url":"https://codeload.github.com/Doraku/DefaultUnDo/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Doraku%2FDefaultUnDo/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260669595,"owners_count":23044317,"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":["command-pattern","csharp","dotnet","memento-pattern","undo-redo"],"created_at":"2025-06-19T02:06:47.526Z","updated_at":"2026-02-21T16:03:29.377Z","avatar_url":"https://github.com/Doraku.png","language":"C#","funding_links":["https://paypal.me/LaszloPaillat"],"categories":[],"sub_categories":[],"readme":"# DefaultUnDo\nDefaultUnDo is a simple [Command pattern](https://en.wikipedia.org/wiki/Command_pattern) implementation to ease the addition of an undo/redo feature.\n\n[![NuGet](https://img.shields.io/nuget/v/DefaultUnDo)](https://www.nuget.org/packages/DefaultUnDo)\n[![preview package](https://img.shields.io/badge/preview-package-blue?style=flat\u0026logo=github)](https://github.com/Doraku/DefaultUnDo/pkgs/nuget/DefaultUnDo)\n![continuous integration status](https://github.com/doraku/defaultundo/workflows/continuous%20integration/badge.svg)\n[![Coverage Status](https://coveralls.io/repos/github/Doraku/DefaultUnDo/badge.svg?branch=master)](https://coveralls.io/github/Doraku/DefaultUnDo?branch=master)\n\n- [Api documentation](./documentation/api/index.md 'Api documentation')\n\u003ca/\u003e\n\n- [Requirement](#Requirement)\n- [Overview](#Overview)\n- [Dependencies](#Dependencies)\n\n\u003ca name='Requirement'\u003e\u003c/a\u003e\n# Requirement\nCompatible from .NETStandard 2.0.  \nFor development, net framework 4.8 and net8.0 are required to build and run all tests.\n\n\u003ca name='Overview'\u003e\u003c/a\u003e\n# Overview\nEasy to use, just instanciate a `UnDoManager` and get going. Numerous extension methods are available to ease the integration.\n```csharp\nIUnDoManager manager = new UnDoManager();\n\n// do an action and record it in the manager, undoAction being the undo equivalent of the action\nmanager.Do(doAction, undoAction);\n\nif (manager.CanUndo)\n{\n    manager.Undo();\n}\n\nif (Manager.CanRedo)\n{\n    manager.Redo();\n}\n\n// clean any recorded action\nmanager.Clear();\n```\n\nExample of how to set a value\n```csharp\nIUnDoManager manager = new UnDoManager();\n\nint field = 42;\n\nmanager.Do(v =\u003e field = v, 1337, field);\n\n// In mvvm we all have some kind of base type\npublic abstract class ANotifyPropertyChanged : INotifyPropertyChanged\n{\n    private sealed class UnDoProperty\u003cT\u003e : UnDoField\u003cT\u003e\n    {\n        private readonly ANotifyPropertyChanged _parent;\n        private readonly string _propertyName;\n\n        public UnDoProperty(ANotifyPropertyChanged parent, IUnDoManager unDoManager, string propertyName)\n            : base(unDoManager, _ =\u003e $\"Changed {typeof(T).GetFriendlyShortName()} {propertyName}\")\n        {\n            _parent = parent;\n            _propertyName = propertyName;\n        }\n\n        // to call PropertyChanged when changing value\n        protected override void PostSet(T value) =\u003e _parent.NotifyPropertyChanged(_propertyName);\n    }\n\n    protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null) =\u003e PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));\n\n    protected bool SetProperty\u003cT\u003e(ref T field, T value, [CallerMemberName] string propertyName = null)\n    {\n        if ((field is IEquatable\u003cT\u003e equatable \u0026\u0026 equatable.Equals(value))\n            || (typeof(T).IsValueType \u0026\u0026 Equals(field, value))\n            || ReferenceEquals(field, value))\n        {\n            return false;\n        }\n\n        field = value;\n\n        NotifyPropertyChanged(propertyName);\n\n        return true;\n    }\n\n    // need to pass the unDoManager in case the UnDoField is not initialised, this is mainly to keep the same signature between normal field and UnDoField (ref field, value)\n    // but you can ommit the IUnDoManager and ref if you choose to initialize UnDoField in the constructor\n    protected bool SetProperty\u003cT\u003e(IUnDoManager unDoManager, ref UnDoField\u003cT\u003e field, T value, [CallerMemberName] string propertyName = null)\n    {\n        field ??= new UnDoProperty\u003cT\u003e(this, unDoManager, propertyName);\n        T oldValue = field;\n\n        if ((oldValue is IEquatable\u003cT\u003e equatable \u0026\u0026 equatable.Equals(value))\n            || (typeof(T).IsValueType \u0026\u0026 Equals(oldValue, value))\n            || ReferenceEquals(oldValue, value))\n        {\n            return false;\n        }\n\n        field.Value = value;\n\n        return true;\n    }\n\n    public event PropertyChangedEventHandler PropertyChanged;\n}\n\n// usage in derrived types\nprivate UnDoField\u003cstring\u003e _field;\n\npublic string Field\n{\n    get =\u003e _field;\n    set =\u003e SetProperty(manager, ref _field, value);\n}\n\n// events interraction\nmanager.Do(\n    () =\u003e PropertyChanged += OnPropertyChanged,  // executed on Do/Redo\n    () =\u003e PropertyChanged -= OnPropertyChanged); // executed on Undo\n\n// Need something to only happen in Do/Redo\nmanager.DoOnDo(() =\u003e NotifyPropertyChanged(nameof(MyProperty)));\n\n// Or only on Undo\nmanager.DoOnUndo(() =\u003e NotifyPropertyChanged(nameof(MyProperty)));\n```\n\n`ICollection\u003cT\u003e`, `IList\u003cT\u003e`, `IDictionary\u003cTKey, TValue\u003e` and `ISet\u003cT\u003e` can be coverterted to an undo instance so that any action performed on them will generate a `IUnDo` action on the manager.\n```csharp\nIUnDoManager manager = new UnDoManager();\n\n// use myList as you would use your list normaly\nIList\u003cint\u003e myList = new List\u003cint\u003e().AsUnDo(manager);\n\n// use myCollection as you would use your collection normaly\n// note than the returned collection also implement INotifyCollectionChanged\nICollection\u003cint\u003e myCollection = new ObservableCollection\u003cint\u003e().AsUnDo(manager);\n\n// use myDictionary as you would use your dictionary normaly\nIDictionary\u003cint, string\u003e myDictionary = new Dictionary\u003cint, string\u003e().AsUnDo(manager);\n\n// use mySet as you would use your set normaly\nISet\u003cint\u003e mySet = new HashSet\u003cint\u003e().AsUnDo(manager);\n```\n\nTo generate a custom description when changes occure on those undo collection, the `AsUnDo` extension method can take a `Func\u003cUnDoCollectionOperation, object\u003e descriptionFactory` parameter.\n\nIt is possible to declare a transaction scope for your operations so a single undo/redo will execute all the contained operations.\n```csharp\nIUnDoManager manager = new UnDoManager();\n\nusing (IUnDoTransaction transaction = manager.BeginTransaction())\n{\n    manager.Do(action1, undo1);\n    manager.Do(action2, undo2);\n\n    // if you do not commit the transaction, all operations inside the scope will be undone on transaction dispose\n    transaction.Commit();\n}\n\n// both undo2 and undo1 will be called in this order\nmanager.Undo();\n\n// both action1 and action2 will be called in this order\nmanager.Redo();\n```\n\nIf a group scope is declared inside an other group scope, all operations will be grouped in the same undo/redo operation.\n```csharp\nIUnDoManager manager = new UnDoManager();\n\nusing (IUnDoTransaction transaction1 = manager.BeginTransaction())\n{\n    manager.Do(action1, undo1);\n\n    using (IUnDoTransaction transaction2 = manager.BeginTransaction())\n    {\n        manager.Do(action2, undo2);\n\n        transaction2.Commit();\n    }\n\n    transaction1.Commit();\n}\n\n// both undo2 and undo1 will be called in this order\nmanager.Undo();\n\n// both action1 and action2 will be called in this order\nmanager.Redo();\n```\n\n`IUnDoManager.Undo` and `IUnDoManager.Redo` calls are not supported when inside a transaction.\n\nTo keep track of the modification, a `Version` property is available on the manager.\n\nMissing something? you can easily extend what you need by creating your own implementations of the `IUnDo` interface and extension methods to ease the usage. Feel free to open an issue or send a pull request.\n\n\u003ca name='Dependencies'\u003e\u003c/a\u003e\nRelies on these awesome projects:\n- [Coverlet](https://github.com/coverlet-coverage/coverlet)\n- [Polyfill](https://github.com/SimonCropp/Polyfill)\n- [Roslynator](https://github.com/JosefPihrt/Roslynator)\n- [XUnit](https://github.com/xunit/xunit)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoraku%2Fdefaultundo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdoraku%2Fdefaultundo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoraku%2Fdefaultundo/lists"}