ProxyGen.NET 10.0.0-preview1

This is a prerelease version of ProxyGen.NET.
There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package ProxyGen.NET --version 10.0.0-preview1
                    
NuGet\Install-Package ProxyGen.NET -Version 10.0.0-preview1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ProxyGen.NET" Version="10.0.0-preview1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ProxyGen.NET" Version="10.0.0-preview1" />
                    
Directory.Packages.props
<PackageReference Include="ProxyGen.NET" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ProxyGen.NET --version 10.0.0-preview1
                    
#r "nuget: ProxyGen.NET, 10.0.0-preview1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#addin nuget:?package=ProxyGen.NET&version=10.0.0-preview1&prerelease
                    
Install ProxyGen.NET as a Cake Addin
#tool nuget:?package=ProxyGen.NET&version=10.0.0-preview1&prerelease
                    
Install ProxyGen.NET as a Cake Tool

ProxyGen.NET Build status AppVeyor tests Coverage Status Nuget (with prereleases) GitHub last commit (branch)

.NET proxy generator powered by Roslyn

This documentation refers the version 10.X of the library

ATTENTION: This version represents a complete revamp of the library. It introduces a brand new (more generic) API which is NOT compatible with the previous versions at all. If you are interested in the legacy -v9- API you can find its documentation here.

ProxyGen.NET is a lightweight, powerful proxy generator for .NET, built on top of Roslyn. It allows you to easily create dynamic proxies that can intercept interfaces, virtual methods and delegates as well as it has support for duck typing � meaning you can proxy objects without requiring them to implement a specific interface. Whether you're building AOP frameworks, logging systems, mock objects, or sophisticated middleware, ProxyGen.NET gives you full control over method interception with a clean and minimal API.

Features

  • Interface, virtual method, and delegate interception
  • Duck typing support (no explicit interfaces needed!)
  • Create proxies with or without a target object
  • Customize method call behavior via interceptors
  • Roslyn-powered dynamic code generation
  • Compile-time type embedding
  • Minimal runtime dependencies

To hook into interface member calls:

  1. Define your interceptor by implementing the IInterceptor interface:
using Solti.Utils.Proxy;
...
public class MyInterceptor: IInterceptor
{
  public object Invoke(IInvocationContext context) // Invoking the generated proxy instance will trigger this method
  {
    if (suppressOriginalMethod)
    {
      return something;    
    }

    context.Args[0] = someNewVal; // "someNewVal" will be forwarded to the target method
	  
    object? result = context.Dispatch(); // call the target

    context.Args[1] = outputVal;  // modify the "ref" or "out" parameters set by the target method

    return result;
  }  
}

The IInvocationContext provides the following essential properties:

  • Proxy: The generated proxy instance
  • Member: The member (property, event or method) being invoked by the caller
  • GenericArguments: Since the Member property always points to the generic definition, the user code should use this value to grab the generic arguments.
  • Args: Arguments passed by the caller. The user code is allowed to modify this array in order to change the input and output (ref or out) parameters
  1. Generate a proxy instance:
using Solti.Utils.Proxy.Generators;
...
IMyInterface target = new MyClass();
...
IMyInterface proxy;

// create a proxy without target
proxy = InterfaceProxyGenerator<IMyInterface>.Activate(new MyInterceptor());  // or ActivateAsync()

// create a proxy with target
proxy = InterfaceProxyGenerator<IMyInterface>.Activate(new MyInterceptor(), target);  // or ActivateAsync()
  1. Enjoy

For further usage examples see this.

To hook into virtual member invocations

Lets suppose we have a virtual/abstract class like this:

public class MyClass(int ctorParameter1, string ctorParameter2)
{
  public virtual void DoSomeStuff<T>(T param) {...}  // it could be abstract as well
}
  1. Create an interceptor in the same way as you could see in case of interface interception
  2. Activate the proxy instance
using Solti.Utils.Proxy.Generators;

// you can pick the desired constructor by providing the corresponding parameters.
// pass null when you want to call the parameterless constructor
MyClass proxy = ClassProxyGenerator<MyClass>.Activate(Tuple.Create(1986, "abc"));  // or ActivateAsync()
  1. Enjoy

For further usage examples see this.

To create delegate proxies

  1. Create an interceptor in the same way as you could see it above
  2. Activate the proxy instance
using Solti.Utils.Proxy.Generators;

Func<int, string> proxy = DelegateProxyGenerator<Func<int, string>>.Activate(i => string.Emtpy);  // or ActivateAsync()

For further usage examples see this.

To create ducks:

  1. Declare an interface that covers all the desired members of the target class:
public class TargetClass // does not implement IDuck
{
  public void Foo() {...}
}
...
public interface IDuck 
{
  void Foo();
}
  1. Generate the duck instance:
using Solti.Utils.Proxy.Generators;
...
TargetClass target = ...;
IDuck duck = DuckGenerator<IDuck, TargetClass>.Activate(target); // or ActivateAsync()
  1. Quack

Related tests can be found here.

Caching the generated assembly

By setting the ProxyGen.AssemblyCacheDir property in YourApp.runtimeconfig.json you can make the system cache the generated assembly, so next time your app starts and requests the proxy there won't be time consuming emitting operation.

You can do it easily by creating a template file named runtimeconfig.template.json in your project folder:

{
  "configProperties": {
    "ProxyGen.AssemblyCacheDir": "GeneratedAssemblies"
  }
}

Embedding the generated type

This library can be used as a source generator so you can embed the generated proxy type into the assembly that uses it. This is simply done by the Solti.Utils.Proxy.Attributes.EmbedGeneratedTypeAttribute:

[assembly: EmbedGeneratedType(typeof(InterfaceProxyGenerator<IMyInterface>))]
[assembly: EmbedGeneratedType(typeof(DuckGenerator<IMyInterface, MyClass>))]

The xXxGenerator.GetGeneratedType() method returns the embedded type if it is present in the assembly in which the GetGeneratedType() was called. Since all the time consuming operations already happened in compile time, requesting embedded types can significantly improve the performance.

Note that:

  • Open generics are not supported.
  • coveralls.io (and other coverage reporters) may crash if your project was augmented by a source generator. To work this issue around:
    • Ignore the generated sources in your coverage app (e.g.: in OpenCover use the -filter:-[*]Proxies.GeneratedClass_* switch)
    • Create an empty file for each generated class (e.g.: YourProject\Solti.Utils.Proxy\Solti.Utils.Proxy.Internals.ProxyEmbedder\Proxies.GeneratedClass_XxX.cs)
    • Exclude these files from your project:
    <ItemGroup>
      <Compile Remove="Solti.Utils.Proxy\**" />
      <EmbeddedResource Remove="Solti.Utils.Proxy\**" />
      <None Remove="Solti.Utils.Proxy\**" />
    </ItemGroup>
    

Inspecting the generated code

ProxyGen is able to dump the generated sources. Due to performance considerations it is disabled by default. To enable

  • In runtime:

    Set the ProxyGen.LogDirectory property (in the same way you could see above) to the desired directory (note that environment variables are supported):

    {
      "configProperties": {
        "ProxyGen.LogDirectory": "%TEMP%"
      }
    }
    
  • In compile time (source generator):

    Extend your .csproj with the following:

    <PropertyGroup>
      <ProxyGen_LogDirectory>$(OutputPath)Logs</ProxyGen_LogDirectory>
    </PropertyGroup>
    

The output should look like this.

Migrating from version

  • 2.X
    • Delete all the cached assemblies (if the [Proxy|Duck]Generator.CacheDirectory is set somewhere)
    • InterfaceInterceptor.Invoke() returns the result of the original method (instead of CALL_TARGET) so in the override you may never need to invoke the method parameter directly.
  • 3.X
    • [Proxy|Duck]Generator.GeneratedType[Async] property has been removed. To get the generated proxy type call the [Proxy|Duck]Generator.GetGeneratedType[Async]() method.
    • [Proxy|Duck]Generator.CacheDirectory property has been removed. To set the cache directory tweak the runtimeconfig.json file.
  • 4.X
    • The layout of the InterfaceInterceptor<>.Invoke() has been changed. Invocation parameters can be grabbed from the InvocationContext passed to the Invoke() method.
    • The ConcurrentInterfaceInterceptor<> class has been dropped since the InterfaceInterceptor<> class was rewritten in a thread safe manner.
  • 5.X
    • You don't need to manually activate the generated proxy type, instead you may use the built-in Generator.Activate() method.
  • 6.X
    • The InvocationContext.InvokeTarget property has been removed but you should not be affected by it
    • As proxy embedder has been reimplemented using the v2 Source Generator API, this feature now requires VS 2022
  • 7.X
    • InterfaceInterceptor<TInterface>.Member|Method has been renamed to InterfaceMember|InterfaceMethod
  • 8.X
    • Generators have been demoted to class. To compare Generator instances use their Id property.
  • 9.X TODO

Resources

Supported frameworks

This project currently targets netstandard2.0 as well as netstandard2.1 and had been tested against net472, netcoreapp3.1, net5.0, net6.0, net7.0, net8.0 and net9.0.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 is compatible. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on ProxyGen.NET:

Package Downloads
Injector.NET

A featherweight dependency injector.

Solti.Utils.OrmLite.Extensions

OrmLite extensions

RPC.NET.Client

Lightweight client to invoke RPC services built with RPC.NET

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
10.0.0-preview1-RoslynV3 69 4/27/2025
10.0.0-preview1 66 4/27/2025
9.1.1 103 1/26/2025
9.1.1-RoslynV3 70 1/26/2025
9.1.0 473 12/28/2023
9.1.0-RoslynV3 121 12/28/2023
9.0.0 242 12/9/2023
9.0.0-RoslynV3 116 12/9/2023
8.2.1 341 5/29/2023
8.2.1-RoslynV3 123 5/29/2023
8.2.0 448 3/12/2023
8.2.0-RoslynV3 140 3/12/2023
8.1.1 284 2/26/2023
8.1.0 569 2/12/2023
8.0.1 450 1/22/2023
8.0.0 575 12/21/2022
8.0.0-preview1 155 11/23/2022
7.1.0 450 11/5/2022
7.0.0 805 8/30/2022
7.0.0-preview6 341 6/4/2022
7.0.0-preview5 272 5/20/2022
7.0.0-preview4 223 5/11/2022
7.0.0-preview3 203 4/28/2022
7.0.0-preview2 191 4/26/2022
7.0.0-preview1 229 3/19/2022
6.0.1 1,288 3/16/2022
6.0.0 1,183 3/13/2022
6.0.0-preview2 184 3/13/2022
6.0.0-preview1 187 3/5/2022
5.0.1 2,398 12/12/2021
5.0.0 2,124 7/19/2021
4.0.2 1,768 4/14/2021
4.0.1 2,425 3/17/2021
4.0.0 976 3/13/2021
4.0.0-preview8 1,070 1/27/2021
4.0.0-preview7 331 1/24/2021
4.0.0-preview6 238 1/23/2021
4.0.0-preview5 271 1/22/2021
4.0.0-preview4 266 1/22/2021
4.0.0-preview3 287 1/17/2021
4.0.0-preview2 256 1/14/2021
4.0.0-preview1 376 11/17/2020
3.1.4 1,226 10/7/2020
3.1.3 1,321 9/25/2020
3.1.2 474 9/24/2020
3.1.1 749 9/8/2020
3.1.0 491 8/31/2020
3.0.3 553 8/27/2020
3.0.2 3,349 7/9/2020
3.0.1 472 7/6/2020
3.0.0 874 6/15/2020
2.1.1 805 6/2/2020
2.1.0 496 6/2/2020
2.0.3 1,074 5/21/2020
2.0.2 500 5/19/2020
2.0.1 471 5/19/2020
2.0.0 1,216 4/2/2020
1.1.1 1,889 1/31/2020
1.1.0 942 1/23/2020
1.0.0 717 1/18/2020
1.0.0-preview2 486 1/14/2020
1.0.0-preview1 376 1/8/2020