-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.html
214 lines (152 loc) · 16.4 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset='utf-8'>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,maximum-scale=2">
<link rel="stylesheet" type="text/css" media="screen" href="/avatar/assets/css/style.css?v=d806768ad099ddb4806d4bcaacd8747ae17bcdad">
<!-- Begin Jekyll SEO tag v2.6.1 -->
<title>avatar | A modern compile-time generated interception/proxy library</title>
<meta name="generator" content="Jekyll v3.9.0" />
<meta property="og:title" content="avatar" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="A modern compile-time generated interception/proxy library" />
<meta property="og:description" content="A modern compile-time generated interception/proxy library" />
<meta property="og:site_name" content="avatar" />
<script type="application/ld+json">
{"@type":"WebSite","url":"/avatar/","headline":"avatar","description":"A modern compile-time generated interception/proxy library","name":"avatar","@context":"https://schema.org"}</script>
<!-- End Jekyll SEO tag -->
</head>
<body>
<!-- HEADER -->
<div id="header_wrap" class="outer">
<header class="inner">
<a id="forkme_banner" href="https://github.com/devlooped/avatar">View on GitHub</a>
<h1 id="project_title">avatar</h1>
<h2 id="project_tagline">A modern compile-time generated interception/proxy library</h2>
</header>
</div>
<!-- MAIN CONTENT -->
<div id="main_content_wrap" class="outer">
<section id="main_content" class="inner">
<h1 id="avatar"><img src="https://github.com/kzu/avatar/raw/main/docs/images/icon.png" alt="Icon" height="48" width="48" style="vertical-align: text-top; border: 0px; padding: 0px; margin: 0px" /> Avatar</h1>
<p>Avatar is a modern interception library which implements the <a href="https://en.wikipedia.org/wiki/Proxy_pattern">proxy pattern</a> and runs everywhere, even where run-time code generation (Reflection.Emit) is forbidden or limitted, like physical iOS devices and game consoles, through compile-time code generation. The proxy behavior is configured in code using what we call a <em>behavior pipeline</em>.</p>
<blockquote>
<p><em>Avatars blend in with the Na’vi seamlessly, and you can control their behavior precisely by ‘driving’ them through a psionic link. Just like a <a href="https://en.wikipedia.org/wiki/Proxy_pattern">proxy</a>, with behavior driven through code.</em></p>
</blockquote>
<p><img src="https://github.com/kzu/avatar/raw/main/docs/images/AvatarIncubation.png" alt="Avatar Overloads" /></p>
<p><a href="https://www.nuget.org/packages/Avatar"><img src="https://img.shields.io/nuget/vpre/Avatar.svg?color=royalblue" alt="Version" /></a>
<a href="https://www.nuget.org/packages/Avatar"><img src="https://img.shields.io/nuget/dt/Avatar?color=darkmagenta" alt="Downloads" /></a>
<a href="https://github.com/devlooped/avatar/blob/main/LICENSE"><img src="https://img.shields.io/github/license/devlooped/avatar.svg?color=blue" alt="License" /></a>
<a href="https://discord.gg/AfGsdRa"><img src="https://img.shields.io/badge/chat-on%20discord-7289DA.svg" alt="Discord Chat" /></a>
<a href="https://github.com/devlooped/avatar"><img src="https://img.shields.io/badge/-source-181717.svg?logo=GitHub" alt="GitHub" /></a></p>
<p><a href="https://pkg.kzu.io/index.json"><img src="https://img.shields.io/endpoint?url=https://shields.kzu.io/vpre/devlooped/main&label=nuget.ci&color=brightgreen" alt="CI Version" /></a>
<a href="https://github.com/devlooped/avatar/actions?query=branch%3Amain+workflow%3Abuild+"><img src="https://github.com/kzu/avatar/workflows/build/badge.svg?branch=main" alt="GH CI Status" /></a></p>
<blockquote>
<p>NOTE: Avatar provides a fairly low-level API with just the essential building blocks on top of which higher-level APIs can be built, such as the upcoming Moq vNext API.</p>
</blockquote>
<h2 id="requirements">Requirements</h2>
<p>Avatar is a .NET Standard 2.0 library and runs on any runtime that supports that.</p>
<p>Compile-time proxy generation leverages <a href="https://github.com/dotnet/roslyn/blob/master/docs/features/source-generators.cookbook.md">Roslyn source generators</a> and therefore requires C# 9.0, which at this time is included in Visual Studio 16.8 (preview or later) and the .NET 5.0 SDK (RC or later). Compile-time generated proxies support the broadest possible run-time platforms since they don’t require any Reflection.Emit, and also don’t pay that performance cost either.</p>
<p>Whenever compile-time proxy generation is not available, a fallback generation strategy is used instead, which leverages <a href="https://github.com/castleproject/Core/blob/master/docs/dynamicproxy-introduction.md">Castle DynamicProxy</a> to provide the run-time code generation.</p>
<p>The client API for configuring proxy behaviors in either case is exactly the same.</p>
<blockquote>
<p>NOTE: even though generated proxies is the main usage for Avatar, the API was designed so that you can also consume the behavior pipeline easily from hand-coded proxies too.</p>
</blockquote>
<h2 id="usage">Usage</h2>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ICalculator</span> <span class="n">calc</span> <span class="p">=</span> <span class="n">Avatar</span><span class="p">.</span><span class="n">Of</span><span class="p"><</span><span class="n">ICalculator</span><span class="p">>();</span>
<span class="n">calc</span><span class="p">.</span><span class="nf">AddBehavior</span><span class="p">((</span><span class="n">invocation</span><span class="p">,</span> <span class="n">next</span><span class="p">)</span> <span class="p">=></span> <span class="p">...);</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">AddBehavior</code>/<code class="language-plaintext highlighter-rouge">InsertBehavior</code> overloads allow granular control of the avatar’s behavior pipeline, which is basically a <a href="https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern">chain of responsibility</a> that invokes all configured behaviors that apply to the current invocation. Individual behaviors can determine whether to short-circuit the call or call the next behavior in the chain.</p>
<p><img src="https://github.com/kzu/avatar/raw/main/docs/images/AddInsertBehavior.png" alt="Avatar Overloads" /></p>
<p>Behaviors can also dynamically determine whether they apply to a given invocation by providing the optional <code class="language-plaintext highlighter-rouge">appliesTo</code> argument. In addition to the delegate-based overloads (called <em>anonymous behaviors</em>), you can also create behaviors by implementing the <code class="language-plaintext highlighter-rouge">IAvatarBehavior</code> interface:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">interface</span> <span class="nc">IAvatarBehavior</span>
<span class="p">{</span>
<span class="kt">bool</span> <span class="nf">AppliesTo</span><span class="p">(</span><span class="n">IMethodInvocation</span> <span class="n">invocation</span><span class="p">);</span>
<span class="n">IMethodReturn</span> <span class="nf">Execute</span><span class="p">(</span><span class="n">IMethodInvocation</span> <span class="n">invocation</span><span class="p">,</span> <span class="n">ExecuteHandler</span> <span class="n">next</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="common-behaviors">Common Behaviors</h2>
<p>Some commonly used behaviors that are generally useful are provided in the library and can be added to avatars as needed:</p>
<ul>
<li>
<p><code class="language-plaintext highlighter-rouge">DefaultValueBehavior</code>: sets default values for method return and <em>out</em> arguments. In addition to the built-in supported default values, additional default value factories can be registered for any type.</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">DefaultEqualityBehavior</code>: implements the <em>Object.Equals</em> and <em>Object.GetHashCode</em> members just like <em>System.Object</em> implements them.</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">RecordingBehavior</code>: simple behavior that keeps track of all invocations, for troubleshooting or reporting.</p>
</li>
</ul>
<h2 id="customizing-avatar-creation">Customizing Avatar Creation</h2>
<p>If you want to centrally configure all your avatars, the easiest way is to simply provide your own factory method (i.e. <code class="language-plaintext highlighter-rouge">Stub.Of<T></code>), which in turn calls the <code class="language-plaintext highlighter-rouge">Avatar.Of<T></code> provided. For example:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">Stub</span>
<span class="p">{</span>
<span class="p">[</span><span class="n">AvatarGenerator</span><span class="p">]</span>
<span class="k">public</span> <span class="k">static</span> <span class="n">T</span> <span class="n">Of</span><span class="p"><</span><span class="n">T</span><span class="p">>()</span> <span class="p">=></span> <span class="n">Avatar</span><span class="p">.</span><span class="n">Of</span><span class="p"><</span><span class="n">T</span><span class="p">>()</span>
<span class="p">.</span><span class="nf">AddBehavior</span><span class="p">(</span><span class="k">new</span> <span class="nf">RecordingBehavior</span><span class="p">())</span>
<span class="p">.</span><span class="nf">AddBehavior</span><span class="p">(</span><span class="k">new</span> <span class="nf">DefaultEqualityBehavior</span><span class="p">())</span>
<span class="p">.</span><span class="nf">AddBehavior</span><span class="p">(</span><span class="k">new</span> <span class="nf">DefaultValueBehavior</span><span class="p">());</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">[AvatarGenerator]</code> attribute is required if you want to leverage the built-in compile-time code generation, since that signals to the source generator that calls to your API end up creating an avatar at run-time and therefore a generated type will be needed for it during compile-time. You can actually explore how this very same behavior is implemented in the built-in Avatar API which is provided as a content file:</p>
<p><img src="https://github.com/kzu/avatar/raw/main/docs/images/AvatarApi.png" alt="avatar API source" /></p>
<p>The <code class="language-plaintext highlighter-rouge">Avatar.cs</code> contains, for example:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">AvatarGenerator</span><span class="p">]</span>
<span class="k">public</span> <span class="k">static</span> <span class="n">T</span> <span class="n">Of</span><span class="p"><</span><span class="n">T</span><span class="p">>(</span><span class="k">params</span> <span class="kt">object</span><span class="p">[]</span> <span class="n">constructorArgs</span><span class="p">)</span> <span class="p">=></span> <span class="n">Create</span><span class="p"><</span><span class="n">T</span><span class="p">>(</span><span class="n">constructorArgs</span><span class="p">);</span>
<span class="p">[</span><span class="n">AvatarGenerator</span><span class="p">]</span>
<span class="k">public</span> <span class="k">static</span> <span class="n">T</span> <span class="n">Of</span><span class="p"><</span><span class="n">T</span><span class="p">,</span> <span class="n">T1</span><span class="p">>(</span><span class="k">params</span> <span class="kt">object</span><span class="p">[]</span> <span class="n">constructorArgs</span><span class="p">)</span> <span class="p">=></span> <span class="n">Create</span><span class="p"><</span><span class="n">T</span><span class="p">>(</span><span class="n">constructorArgs</span><span class="p">,</span> <span class="k">typeof</span><span class="p">(</span><span class="n">T1</span><span class="p">));</span>
</code></pre></div></div>
<p>As you can see, the Avatar API itself uses the same extensibility mechanism that your own custom factory methods can use.</p>
<h3 id="static-vs-dynamic-avatars">Static vs Dynamic Avatars</h3>
<p>Depending on the project and platform, Avatars will automatically choose whether to use run-time proxies or compile-time ones (powered by Roslyn source generators). The latter are only supported when building C# 9.0+ projects.</p>
<p>You can opt out of the static avatars by setting <code class="language-plaintext highlighter-rouge">EnableCompiledAvatars=false</code> in your project file:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><PropertyGroup></span>
<span class="nt"><EnableCompiledAvatars></span>false<span class="nt"></EnableCompiledAvatars></span>
<span class="nt"></PropertyGroup></span>
</code></pre></div></div>
<p>This will switch the project to run-time proxies based on Castle.Core.</p>
<h2 id="debugging-optimizations">Debugging Optimizations</h2>
<p>There is nothing more frustrating than a proxy you have carefully configured that doesn’t behave the way you expect it to. In order to make this a less frustrating experience, avatars are carefully optimized for debugger display and inspection, so that it’s clear what behaviors are configured, and invocations and results are displayed clearly and concisely. Here’s the debugging display of the <code class="language-plaintext highlighter-rouge">RecordingBehavior</code> that just keeps track of invocations and their return values for example:</p>
<p><img src="https://github.com/kzu/avatar/raw/main/docs/images/DebuggerDisplay.png" alt="debugging display" /></p>
<p>And here’s the invocation debugger display from an anonymous behavior:</p>
<p><img src="https://github.com/kzu/avatar/raw/main/docs/images/DebuggingBehavior.png" alt="behavior debugging" /></p>
<h2 id="samples">Samples</h2>
<p>The <code class="language-plaintext highlighter-rouge">samples</code> folder in the repository contains a few interesting examples of how <em>Avatar</em> can be used to implement some fancy use cases. For example:</p>
<ul>
<li>
<p>Forwarding calls to matching interface methods/properties (by signature) to a static class. The example uses this to wrap calls to <em>System.Console</em> via an <em>IConsole</em> interface.</p>
</li>
<li>
<p>Forwarding calls to a target object using the DLR (that backs the <em>dynamic</em> keyword in C#) API for high-performance late binding.</p>
</li>
<li>
<p>Custom <code class="language-plaintext highlighter-rouge">Stub.Of<T></code> factory that creates avatars that have common behaviors configured automatically.</p>
</li>
<li>
<p>Custom avatar factory method that adds an int return value randomizer.</p>
</li>
<li>
<p>Configuring the built-in <em>DefaultValueBehavior</em> so that every time a string property is retrieved, it gets a random lorem ipsum value.</p>
</li>
<li>
<p>Logging all calls to an avatar to the Xunit output helper.</p>
</li>
</ul>
<h2 id="sponsors">Sponsors</h2>
<h3 style="vertical-align: text-top" id="by-clarius">
<img src="https://raw.githubusercontent.com/devlooped/oss/main/assets/images/sponsors.svg" alt="sponsors" height="36" width="36" style="vertical-align: text-top; border: 0px; padding: 0px; margin: 0px" /> by <a href="https://github.com/clarius">@clarius</a> <img src="https://raw.githubusercontent.com/clarius/branding/main/logo/logo.svg" alt="sponsors" height="36" width="36" style="vertical-align: text-top; border: 0px; padding: 0px; margin: 0px" />
</h3>
<p><em><a href="https://github.com/sponsors/devlooped">get mentioned here too</a>!</em></p>
</section>
</div>
<!-- FOOTER -->
<div id="footer_wrap" class="outer">
<footer class="inner">
<p class="copyright">avatar maintained by <a href="https://github.com/devlooped">devlooped</a></p>
<p>Published with <a href="https://pages.github.com">GitHub Pages</a></p>
</footer>
</div>
</body>
</html>