Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using ProxyGeneratorExtensions.CreateClassProxyWithTarget Like ActivatorUtilities.CreateInstance #532

Closed
lawrence-laz opened this issue Jul 27, 2020 · 10 comments

Comments

@lawrence-laz
Copy link

lawrence-laz commented Jul 27, 2020

To use ProxyGeneratorExtensions.CreateClassProxyWithTarget with non-parameterless constructors we have an overload with object[] constructorArguments.

However, I don't think there is a way to delegate argument gathering responsibility to an outside party (ex. dependency container), like done in ActivatorUtilities.CreateInstance.

Would it be feasible for ProxyGeneratorExtensions.CreateClassProxyWithTarget to provide an overload, which:

  • Accepts ServiceProvider instead of object[] constructorArguments?
  • Or a method factory, so that a caller could delegate to ActivatorUtilities.CreateInstance themselves?
@lawrence-laz lawrence-laz changed the title Using ProxyGeneratorExtensions.CreateClassProxyWithTarget together with ActivatorUtilities.CreateInstance Using ProxyGeneratorExtensions.CreateClassProxyWithTarget Like ActivatorUtilities.CreateInstance Jul 27, 2020
@stakx
Copy link
Member

stakx commented Jul 27, 2020

Hi @lawrence-laz, ProxyGeneratorExtensions, ActivatorUtilities etc. do not exist at this repository, so questions about those are out of scope... we can't help you without putting in extra time to familiarize ourselves with a third-party project, so I hope you'll understand that I'm going to close this issue.

@stakx stakx closed this as completed Jul 27, 2020
@lawrence-laz
Copy link
Author

Hi @stakx,

Sorry for not being more attentive about the class being reffered. But ProxyGenerator (which is in this repo) has the same method and the same limitation. Please let me rephrase this issue and if you still think this is out of scope, then that is fine.

Currently the following method:
public object CreateClassProxyWithTarget(Type classToProxy, object target, object[] constructorArguments, params IInterceptor[] interceptors)
accepts constructor arguments for creating a proxy object. However, the constructor or the target type might not be known at compile time, hence making it quite difficult to figure out the right constructor and parameters on the go.

Question is would it be feasible to add an overload with Fun<Type, object> method factory instead of object[] constructor arguments. You could use this method factory to build an object instead of calling the constructor yourself. Then the user can figure out how to create an instance of Type object themselves (in my case that would be to delegate to dependency injection container).

@jonorossi
Copy link
Member

However, the constructor or the target type might not be known at compile time

It doesn't need to be known at compile time, but you'll have to use reflection to work that out if you want your application to proxy anything. I don't follow how a Func overload would help here.

I'd suggest taking a read of the DynamicProxy docs (e.g. https://github.com/castleproject/Core/blob/master/docs/dynamicproxy-kinds-of-proxy-objects.md) to familiarise yourself with how DP works under the hood, creating the proxy instance isn't trivial which is why DP needs to do it for you. DP code generates the proxy type and creates an instance populating internal fields, for example the reference to your target object.

@stakx
Copy link
Member

stakx commented Jul 28, 2020

You could use this method factory to build an object instead of calling the constructor yourself. Then the user can figure out how to create an instance of Type object themselves

The constructorArguments are plugged into the constructor of the proxy object being built (not into the target object's ctor, the target is already instantiated). So your factory method would have to be able to build proxy objects, i.e. do DynamicProxy's main job.

Maybe I still misunderstand, so let's look at that Func<Type, object> more closely:

  1. What Type would you pass to it?
  2. Which object would you get back? Proxy, or target?
  3. What would you expect DynamicProxy (CreateClassProxyWithTarget) to do with that object?

@lawrence-laz
Copy link
Author

lawrence-laz commented Jul 28, 2020

I see that I underestimated the underying complexity of creating a proxy instance. Makes sense that you also need to initialize internal proxy state and a given type constructor arguments are just a part of it.

It doesn't need to be known at compile time, but you'll have to use reflection to work that out if you want your application to proxy anything. I don't follow how a Func overload would help here.

I hoped to avoid using reflection in this case by delegating object construction to Microsoft.Extensions.DependencyInjection using a Func like this:

type => ActivatorUtilities.CreateInstance(serviceProvider, type)

To answer your questions:

  1. What Type would you pass to it?

I was thinking the generated proxy type.

  1. Which object would you get back? Proxy, or target?

Proxy.

  1. What would you expect DynamicProxy (CreateClassProxyWithTarget) to do with that object?

Use it in the place where proxy is normally generated after calling a constructor with constructorArguments.

@stakx
Copy link
Member

stakx commented Jul 28, 2020

@lawrence-laz, perhaps the following will help when thinking about this. Imagine you want to create a proxy for the following type (let's just ignore the target business for now, whether your proxy has a target or not is mostly unrelated to the issue at hand):

abstract class Frobbler
{
    protected Frobbler(object arg) { ... }
}

When DynamicProxy is asked to create a proxy, it has to create a proxy type first (which it then instantiates using Activator.CreateInstance to give you the proxy object). The proxy type is going to be a subclass or implementation (in the case of interfaces) of the type to be proxied. Think this:

class FrobblerProxy : Frobbler { ... }

Now if you did this yourself, C# would at this point force you to add a constructor that calls one of the base class' constructors. So you'd have to add:

class FrobblerProxy : Frobbler
{
    public FrobblerProxy(object arg, ...) : base(arg) { ... }
}

And there's your constructorArguments. Neither you, nor DynamicProxy, nor Activator.CreateInstance can instantiate a FrobblerProxy (to hand you back the proxy object) without having those ready.

However, the constructor or the target type might not be known at compile time, hence making it quite difficult to figure out the right constructor and parameters on the go.

I didn't reply to this earlier, but note that #480 is possibly related to this.

@lawrence-laz
Copy link
Author

lawrence-laz commented Jul 28, 2020

(which it then instantiates using Activator.CreateInstance to give you the proxy object)

Interesting. So using Activator.CreateInstance is a current strategy for creating proxy object.

And there's your constructorArguments. Neither you, nor DynamicProxy, nor Activator.CreateInstance can instantiate a FrobblerProxy (to hand you back the proxy object) without having those ready.

Yes, but dependency container can! What I, as a user, would like to do is to replace proxy instantiation strategy with another in a format of Func<Type, object>.

So the default (currrent) would be:
(type, constructorArguments) => Activator.CreateInstance(type, constructorArguments)
and it could replaced (configured) in user code with this:
(type, constructorArguments) => ActivatorUtilities.CreateInstance(serviceProvider, type , constructorArguments), which would not require to use reflection to resolve constructor arguments on my part, as Microsoft's DependencyInjection can do that automatically (with constructorArguments parameter being empty).

@stakx
Copy link
Member

stakx commented Jul 28, 2020

In that case you need to go one level lower. If you don't want DynamicProxy to create the proxy object for you, but the proxy type, then you'd use ProxyBuilder instead of ProxyGenerator. The former creates proxy types, the latter creates proxy objects. You can then instantiate the created proxy type however you wish.

I should also mention that DefaultProxyBuilder et. al, while public, perhaps aren't meant to be called by client code directly... mostly because you'll need to know exactly what constructor arguments to pass (there's not just the base type ctor's constructorArguments, there's also parameters for interceptors, targets, mixins, etc.), and that knowledge resides inside ProxyGenerator—it's not documented, so you'll be on your own if you choose to use DefaultProxyBuilder directly.

Also, you'll still need to pass constructorArguments, if the proxied type has parameterized constructors.

@jonorossi
Copy link
Member

@lawrence-laz to give a bit of perspective, Entity Framework Core uses this DynamicProxy API for all entity types and also passes constructorArguments.

https://github.com/dotnet/efcore/blob/main/src/EFCore.Proxies/Proxies/Internal/ProxyFactory.cs

@lawrence-laz
Copy link
Author

lawrence-laz commented Jul 28, 2020

That gives me some ideas, thanks!

Also, you'll still need to pass constructorArguments, if the proxied type has parameterized constructors.

Yes, sorry, that would be Func<Type, object[], object>. Then user-defined params could be resolved by DI, while proxy-defined params provided by ProxyGenerator.
Pseudo example:

class UserDependency { }

class UserClass 
{
    public UserClass(UserDependency userDependency) { ... }
}

class ProxyDependency { }

class UserClassProxy : UserClass
{
    public UserClassProxy(ProxyDependency proxyDependency, UserDependency userDependency)
        : base(userDependency) { ... }
}

class PseudoProxyGenerator
{
    Func<Type, object[], object> ProxyFactory = (type, ctorArguments) => Activator.CreateInstance(type, ctorArguments);

    public object CreateProxy(Type typeToProxy, object[] constructorArguments)
    {
        // ... creates proxy type, gather proxy arguments
       var finalArguments = proxyArguments.Append(constructorArguments).ToArray();

        return CreateProxyInternal(proxyType, finalArguments);
    }

    object CreateProxyInternal(Type proxyType, object[] constructorArguments)
    {
        return ProxyFactory.Invoke(proxyType, constructorArguments);
    }
}

// User code
var serviceProvider = new ServiceCollection()
    .AddTransient<UserDependency>()
    .BuildServiceProvider();

var proxyGenerator = new ProxyGenerator();
proxyGenerator.ProxyFactory = (type, ctorArguments) => ActivatorUtilities.CreateInstance(serviceProvider, type , ctorArguments);

// User can provide an empty arguments array, because DI container will resolve UserDependency.
// While package makes sure to append ProxyDependency (not registered in user code/DI).
var proxy = ProxyGenerator.CreateProxy(typeof(UserClass), new [] { }); 

Does this make sense?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants