{"id":13629214,"url":"https://github.com/Otaman/Copycat","last_synced_at":"2025-04-17T04:33:15.378Z","repository":{"id":213002220,"uuid":"732789151","full_name":"Otaman/Copycat","owner":"Otaman","description":"Source generators for creating decorators by templates","archived":false,"fork":false,"pushed_at":"2024-02-11T17:26:23.000Z","size":279,"stargazers_count":1,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-11T06:05:31.223Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/Otaman.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":"2023-12-17T20:35:07.000Z","updated_at":"2024-01-14T14:04:45.000Z","dependencies_parsed_at":"2024-01-15T23:28:45.748Z","dependency_job_id":"29decdda-5a86-4e91-828b-cf174c0eb63e","html_url":"https://github.com/Otaman/Copycat","commit_stats":null,"previous_names":["otaman/copycat"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Otaman%2FCopycat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Otaman%2FCopycat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Otaman%2FCopycat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Otaman%2FCopycat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Otaman","download_url":"https://codeload.github.com/Otaman/Copycat/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249316056,"owners_count":21249885,"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":[],"created_at":"2024-08-01T22:01:04.731Z","updated_at":"2025-04-17T04:33:14.688Z","avatar_url":"https://github.com/Otaman.png","language":"C#","funding_links":[],"categories":["Content"],"sub_categories":["95. [CopyCat](https://ignatandrei.github.io/RSCG_Examples/v2/docs/CopyCat) , in the [Interface](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#interface) category"],"readme":"# Copycat [![NuGet Badge](https://buildstats.info/nuget/Copycat?includePreReleases=true)](https://www.nuget.org/packages/Copycat)\nSource generator for creating decorators by templates.\nThe source generator intents to simplify implementation of a [Decorator Pattern](https://en.wikipedia.org/wiki/Decorator_pattern).\n\n## Use Cases\n\nLes't begin from simple scenario. We need to decorate ISomeInterface:\n```C#\npublic interface ISomeInterface\n{\n    void DoSomething();\n    void DoSomethingElse(int a, string b);\n}\n```\n\nTo activate generator, use \\[Decorate\\] attribute on a class. The class must be partial and have exactly one interface to decorate:\n```C#\nusing Copycat;\n\n[Decorate]\npublic partial class SimpleDecorator : ISomeInterface { }\n```\n\nIn this example, Copycat generates pass-through decorator:\n```C#\n// \u003cauto-generated/\u003e\npublic partial class SimpleDecorator\n{\n    private ISomeInterface _decorated;\n    public SimpleDecorator(ISomeInterface decorated)\n    {\n        _decorated = decorated;\n    }\n\n    public void DoSomething() =\u003e _decorated.DoSomething();\n\n    public void DoSomethingElse(int a, string b) =\u003e _decorated.DoSomethingElse(a, b);\n}\n```\n\nPass-through decorators don't do much, but still can be useful for changing behaviour of particular methods without touching others:\n\u003e Here and after we skip `using Copycat;` and combine user-defined and auto-generated code for brevity \n```C#\n[Decorate]\npublic partial class SimpleDecorator : ISomeInterface \n{ \n    public void DoSomething()\n    {\n        // actually, do nothing\n    }\n}\n\n// \u003cauto-generated/\u003e\npublic partial class SimpleDecorator\n{\n    private ISomeInterface _decorated;\n    public SimpleDecorator(ISomeInterface decorated)\n    {\n        _decorated = decorated;\n    }\n\n    public void DoSomethingElse(int a, string b) =\u003e _decorated.DoSomethingElse(a, b);\n}\n```\nAs we see, Copycat now generates pass-through only for non-implemented methods (DoSomethingElse), allowing us to concentrate on important changes.\n\nBut what if we want to override behaviour for one method, but throw for all others (assuming we got some huge legacy interface, where most methods are useless for us)? \nNow it's time to play with templates :sunglasses:\n\nTo make Copycat generate something different from pass-through we need to define a template:\n```C#\npublic interface IAmPartiallyUseful\n{   \n    void DoSomethingUseful();\n    void DoSomething();\n    void DoSomethingElse();\n}\n\n[Decorate]\npublic partial class ThrowDecorator : IAmPartiallyUseful\n{\n    public void DoSomethingUseful() =\u003e Console.WriteLine(\"I did some work!\");\n\n    [Template]\n    private void Throw(Action action) =\u003e throw new NotImplementedException();\n}\n\n// \u003cauto-generated/\u003e\npublic partial class ThrowDecorator\n{\n    private IAmPartiallyUseful _decorated;\n    public ThrowDecorator(IAmPartiallyUseful decorated)\n    {\n        _decorated = decorated;\n    }\n\n    /// \u003csee cref = \"ThrowDecorator.Throw(Action)\"/\u003e\n    public void DoSomething() =\u003e throw new NotImplementedException();\n    /// \u003csee cref = \"ThrowDecorator.Throw(Action)\"/\u003e\n    public void DoSomethingElse() =\u003e throw new NotImplementedException();\n}\n```\nThat's better, now we do some work on DoSomethingUseful and throw on DoSomething or DoSomethingElse, but how?\nWe defined a template:\n```C#\n[Template]\nprivate void Throw(Action action) {...}\n```\nTemplate is a method that takes parameterless delegate which has the same return type as the method itself.\nWe can use any names for the template method and a delegate (as usual, it's better to keep them self-explanatory).\n\nWe didn't use the delegate in the pevious example because we limited ourselves to simple examples where it wasn't needed. \nNow it's time to explore more real-world scenarios. Decorators fit nicely for aspect-oriented programming (AOP) when using them as wrappers.\n\n### Logging\nOne of the aspects, than can be separated easily is logging. For example:\n```C#\nusing System.Diagnostics;\n\npublic interface ISomeInterface\n{\n    void DoNothing();\n    void DoSomething();\n    void DoSomethingElse(int a, string b);\n}\n\n[Decorate]\npublic partial class SimpleDecorator : ISomeInterface\n{\n    private readonly ISomeInterface _decorated;\n\n    public SimpleDecorator(ISomeInterface decorated) =\u003e \n        _decorated = decorated;\n\n    [Template]\n    public void CalculateElapsedTime(Action action)\n    {\n        var sw = Stopwatch.StartNew();\n        action();\n        Console.WriteLine($\"{nameof(action)} took {sw.ElapsedMilliseconds} ms\");\n    }\n    \n    public void DoNothing() { }\n}\n\npublic partial class SimpleDecorator\n{\n    /// \u003csee cref = \"SimpleDecorator.CalculateElapsedTime(Action)\"/\u003e\n    public void DoSomething()\n    {\n        var sw = Stopwatch.StartNew();\n        _decorated.DoSomething();\n        Console.WriteLine($\"{nameof(DoSomething)} took {sw.ElapsedMilliseconds} ms\");\n    }\n\n    /// \u003csee cref = \"SimpleDecorator.CalculateElapsedTime(Action)\"/\u003e\n    public void DoSomethingElse(int a, string b)\n    {\n        var sw = Stopwatch.StartNew();\n        _decorated.DoSomethingElse(a, b);\n        Console.WriteLine($\"{nameof(DoSomethingElse)} took {sw.ElapsedMilliseconds} ms\");\n    }\n}\n```\nHere DoSomething and DoSomething else are generated as specified by the template CalculateElapsedTime. \nCopycat has convention to replace delegate invocation with decorated method invocation (includes passing all parameters). For convenience, *nameof(delegate)* also replaced with nameof(MethodName) for easier use in templating.\n\n### Retries\nLet's make our generator do some more interesting task. In most situations Polly nuget package is the best choice for retries. But for simple cases it may bring unnecessary complexity, like here:\n```C#\npublic interface ICache\u003cT\u003e\n{\n    Task\u003cT\u003e Get(string key);\n    Task\u003cT\u003e Set(string key, T value);\n}\n\n[Decorate]\npublic partial class CacheDecorator\u003cT\u003e : ICache\u003cT\u003e\n{\n    private readonly ICache\u003cT\u003e _decorated;\n    \n    public CacheDecorator(ICache\u003cT\u003e decorated) =\u003e _decorated = decorated;\n    \n    [Template]\n    public async Task\u003cT\u003e RetryOnce(Func\u003cTask\u003cT\u003e\u003e action, string key)\n    {\n        try\n        {\n            return await action();\n        }\n        catch (Exception e)\n        {\n            Console.WriteLine($\"Retry {nameof(action)} for {key} due to {e.Message}\");\n            return await action();\n        }\n    }\n}\n\npublic partial class CacheDecorator\u003cT\u003e\n{\n    /// \u003csee cref = \"CacheDecorator.RetryOnce(Func{Task{T}}, string)\"/\u003e\n    public async Task\u003cT\u003e Get(string key)\n    {\n        try\n        {\n            return await _decorated.Get(key);\n        }\n        catch (Exception e)\n        {\n            Console.WriteLine($\"Retry {nameof(Get)} for {key} due to {e.Message}\");\n            return await _decorated.Get(key);\n        }\n    }\n\n    /// \u003csee cref = \"CacheDecorator.RetryOnce(Func{Task{T}}, string)\"/\u003e\n    public async Task\u003cT\u003e Set(string key, T value)\n    {\n        try\n        {\n            return await _decorated.Set(key, value);\n        }\n        catch (Exception e)\n        {\n            Console.WriteLine($\"Retry {nameof(Set)} for {key} due to {e.Message}\");\n            return await _decorated.Set(key, value);\n        }\n    }\n}\n```\nCaching should be fast, so we can't retry many times. One is ok, especially with some log message about the problem.\nPay attention to *key* parameter in the template, it matches nicely our interface methods parameter. \n\u003e If additional parameters defined in template, then generator applies this template only for methods that have same exact parameter. \nActually, we can implement more complext retry patterns, too:\n```C#\n[Template]\npublic async Task\u003cT\u003e Retry\u003cT\u003e(Func\u003cTask\u003cT\u003e\u003e action)\n{\n    var retryCount = 0;\n    while (true)\n    {\n        try\n        {\n            return await action();\n        }\n        catch (Exception e)\n        {\n            if (retryCount++ \u003e= 3)\n                throw;\n            Console.WriteLine($\"Retry {nameof(action)} {retryCount} due to {e.Message}\");\n        }\n    }\n}\n```\n\n### Advanced\nThere are plenty use cases, than can be covered with Copycat. Feel free to explore them in `src/Copycat/Copycat.IntegrationTests` (and `Generated` folder inside).\nFor instance, defining template in base class (see RetryWrapperWithBase.cs) or using multiple template to match methods with different signature see TestMultipleTemplates.cs).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FOtaman%2FCopycat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FOtaman%2FCopycat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FOtaman%2FCopycat/lists"}