ProxyGen.NET 9.1.0

dotnet add package ProxyGen.NET --version 9.1.0                
NuGet\Install-Package ProxyGen.NET -Version 9.1.0                
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="9.1.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add ProxyGen.NET --version 9.1.0                
#r "nuget: ProxyGen.NET, 9.1.0"                
#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.
// Install ProxyGen.NET as a Cake Addin
#addin nuget:?package=ProxyGen.NET&version=9.1.0

// Install ProxyGen.NET as a Cake Tool
#tool nuget:?package=ProxyGen.NET&version=9.1.0                

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 9.X of the library

Purposes

This library currently supports generating proxies for interface interception and duck typing.

To hook into interface method calls:

  1. Create the interceptor class (which is an InterfaceInterceptor descendant):
using Solti.Utils.Proxy;
...
public class MyInterceptor: InterfaceInterceptor<IMyInterface>
{
  public MyInterceptor(IMyInterface target) : base(target) {}

  public MyInterceptor(IMyInterface target, MyParam myParam) : base(target) {}  // overloaded constructor

  public override object? Invoke(InvocationContext context) // Invoking the generated proxy instance will trigger this method
  {
	  if (suppressOriginalMethod)
	  {
	    return something;
      // ref|out parameters can be assigned by setting the corresponding "context.Args[]" item 
	  }
	  
	  context.Args[0] = someNewVal; // "someNewVal" will be forwarded to the original method
	  
	  return base.Invoke(context); // Let the original method do its work
  }  
}
// OR
public class MyInterceptorTargetingTheImplementation: InterfaceInterceptor<IMyInterface, MyInterfaceImplementation>
{
    public MyInterceptor(MyInterfaceImplementation target) : base(target) {}

    public override object? Invoke(InvocationContext context)
    {
        MemberInfo
            ifaceMember  = context.InterfaceMember,  // Will point to the invoked IMyInterface member (e.g.: IMyInterface.Foo())
            targetMember = context.TargetMember; // Will point to the underlying MyInterfaceImplementation member (e.g. MyInterfaceImplementation.Foo())

        return base.Invoke(context);
    }
}
  1. Generate a proxy instance invoking the desired constructor:
using System;
...
IMyInterface target = new MyClass();
...
IMyInterface proxy;

proxy = ProxyGenerator<IMyInterface, MyInterceptor>.Activate(Tuple.Create(target)); // or ActivateAsync()
proxy = ProxyGenerator<IMyInterface, MyInterceptor>.Activate(Tuple.Create(target, new MyParam()));
  1. Enjoy

Remarks:

  • The target can access its most outer enclosing proxy. To achieve this it just has to implement the IProxyAccess<IMyInterface> interface:
    using Solti.Utils.Proxy;
    
    public class MyClass : IMyInterface, IProxyAccess<IMyInterface>
    {
        ...
        public IMyInterface Proxy { get; set; }
    }
    
  • Starting from v9.1 partial interface implementations are also supported:
    using Solti.Utils.Proxy;
    
    public interface IMyInterface
    {
        void Intercepted();
        void NotInterceptred();
    } 
    
    public class MyInterceptor: InterfaceInterceptor<IMyInterface>
    {
        public void NotInterceptred() {...}
    
        // will be triggered by Intercepted() only as NotInterceptred() has it's own implementation
        public override object Invoke(InvocationContext context) {...}
    
        ...
    }
    

For further usage examples see this or that.

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(Tuple.Create(target)); // or ActivateAsync()
  1. Quack

Related tests can be seen 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(ProxyGenerator<IMyInterface, MyInterceptor<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 consumig operations already happened in compile time, requesting embedded types can singificantly 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.SourceDump property (in the same way you could see above) to the desired directory (note that environment variables are supported):

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

    Extend your .csproj with the following:

    <PropertyGroup>
      <ProxyGen_SourceDump>$(OutputPath)Logs</ProxyGen_SourceDump>
    </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.

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 and net8.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. 
.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
9.1.0 436 12/28/2023
9.1.0-RoslynV3 113 12/28/2023
9.0.0 229 12/9/2023
9.0.0-RoslynV3 108 12/9/2023
8.2.1 321 5/29/2023
8.2.1-RoslynV3 109 5/29/2023
8.2.0 430 3/12/2023
8.2.0-RoslynV3 124 3/12/2023
8.1.1 264 2/26/2023
8.1.0 550 2/12/2023
8.0.1 431 1/22/2023
8.0.0 546 12/21/2022
8.0.0-preview1 137 11/23/2022
7.1.0 423 11/5/2022
7.0.0 779 8/30/2022
7.0.0-preview6 319 6/4/2022
7.0.0-preview5 250 5/20/2022
7.0.0-preview4 201 5/11/2022
7.0.0-preview3 179 4/28/2022
7.0.0-preview2 167 4/26/2022
7.0.0-preview1 205 3/19/2022
6.0.1 1,252 3/16/2022
6.0.0 1,153 3/13/2022
6.0.0-preview2 161 3/13/2022
6.0.0-preview1 163 3/5/2022
5.0.1 2,353 12/12/2021
5.0.0 2,094 7/19/2021
4.0.2 1,734 4/14/2021
4.0.1 2,390 3/17/2021
4.0.0 939 3/13/2021
4.0.0-preview8 1,033 1/27/2021
4.0.0-preview7 297 1/24/2021
4.0.0-preview6 206 1/23/2021
4.0.0-preview5 238 1/22/2021
4.0.0-preview4 232 1/22/2021
4.0.0-preview3 254 1/17/2021
4.0.0-preview2 223 1/14/2021
4.0.0-preview1 343 11/17/2020
3.1.4 1,187 10/7/2020
3.1.3 1,284 9/25/2020
3.1.2 437 9/24/2020
3.1.1 712 9/8/2020
3.1.0 452 8/31/2020
3.0.3 515 8/27/2020
3.0.2 3,310 7/9/2020
3.0.1 435 7/6/2020
3.0.0 834 6/15/2020
2.1.1 766 6/2/2020
2.1.0 456 6/2/2020
2.0.3 1,034 5/21/2020
2.0.2 461 5/19/2020
2.0.1 433 5/19/2020
2.0.0 1,174 4/2/2020
1.1.1 1,848 1/31/2020
1.1.0 899 1/23/2020
1.0.0 675 1/18/2020
1.0.0-preview2 450 1/14/2020
1.0.0-preview1 341 1/8/2020