Skip to main content

ProxyGen by Dénes Solti

Nuget / site data

Nuget GitHub last commit GitHub Repo stars

Details

Info

info

Name: ProxyGen

.NET proxy generator powered by Roslyn

Author: Dénes Solti

NuGet: https://www.nuget.org/packages/ProxyGen.net/

You can find more details at https://github.com/Sholtee/ProxyGen

Source : https://github.com/Sholtee/ProxyGen

Original Readme

note

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 8.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);
    }
    }
  2. 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()));
  3. Enjoy

Note that 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; }
}

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();
    }
  2. Generate the duck instance:
    using Solti.Utils.Proxy.Generators;
    ...
    TargetClass target = ...;
    IDuck duck = DuckGenerator<IDuck, TargetClass>.Activate(Tuple.Create(target)); // or ActivateAsync()
  3. 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

Resources

Supported frameworks

This project currently targets .NET Standard 2.0 and 2.1.

About

note

intercepting and duck typing

How to use

Example ( source csproj, source files )

This is the CSharp Project that references ProxyGen

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ProxyGen.NET" Version="8.2.1" />
</ItemGroup>

</Project>

Generated Files

Those are taken from $(BaseIntermediateOutputPath)\GX

#pragma warning disable
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("ProxyGen.NET", "8.2.1.0"), global::System.Diagnostics.DebuggerNonUserCodeAttribute, global::System.Runtime.CompilerServices.CompilerGeneratedAttribute]
internal sealed class Duck_BB1E45629CF5010E4068E5BFBB7EF53B : global::Solti.Utils.Proxy.Internals.DuckBase<global::ProxyGenDemo.Person>, global::ProxyGenDemo.IPerson
{
public Duck_BB1E45629CF5010E4068E5BFBB7EF53B(global::ProxyGenDemo.Person target) : base(target)
{
}

[global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
global::System.String global::ProxyGenDemo.IPerson.FullName() => this.Target.FullName();
global::System.String global::ProxyGenDemo.IPerson.FirstName {[global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
get => this.Target.FirstName; [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
set => this.Target.FirstName = value; }

global::System.String global::ProxyGenDemo.IPerson.LastName {[global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
get => this.Target.LastName; [global::System.Runtime.CompilerServices.MethodImplAttribute(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
set => this.Target.LastName = value; }

public static readonly global::System.Func<global::System.Object, global::System.Object> __Activator = tuple =>
{
switch (tuple)
{
case global::System.Tuple<global::ProxyGenDemo.Person> t0:
return new global::Duck_BB1E45629CF5010E4068E5BFBB7EF53B(t0.Item1);
default:
throw new global::System.MissingMethodException("Constructor with the given layout cannot be found.");
}
};
[global::System.Runtime.CompilerServices.ModuleInitializerAttribute]
public static void Initialize() => global::Solti.Utils.Proxy.Internals.LoadedTypes.Register(typeof(global::Duck_BB1E45629CF5010E4068E5BFBB7EF53B));
}

Usefull

Download Example (.NET C# )

Share ProxyGen

https://ignatandrei.github.io/RSCG_Examples/v2/docs/ProxyGen

In the same category (Interface) - 8 other generators

Biwen.AutoClassGen

CopyCat

Farskeptic.AutoCompose

MakeInterface.Generator

Matryoshki

NetAutomaticInterface

Roozie.AutoInterface

RSCG_Static