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

Support "with" for record proxies #671

Open
Evengard opened this issue Oct 24, 2023 · 4 comments
Open

Support "with" for record proxies #671

Evengard opened this issue Oct 24, 2023 · 4 comments

Comments

@Evengard
Copy link

Is it possible to have support for the with syntax for proxies?

I want to "enrich" my existing record with a mixin implementing a custom property, and make it persist across modifications of that record.

Unfortunately, it seems that when I use the with keyword, the proxy is gone and I end up with a completely new object.
At the same time, if I do the same thing manually, aka

Example 1
public record Base(int Value);

public interface IMixin
{
    string? MixedString { get; }
}

public record Inherited(int Value) : Base(Value), IMixin
{
    public string? MixedString { get; init; }
}

Base rec = new Inherited(1)
{
    MixedString = "Hello"
};
Console.WriteLine(rec);
var updated = rec with
{
    Value = 2
};
Console.WriteLine(updated);

Or even:

Example 2
public record Base(int Value);

public interface IMixin
{
    string? MixedString { get; }
}

public record Inherited(int Value) : Base(Value), IMixin
{
    string IMixin.MixedString => "Hello";
}

Base rec = new Inherited(1);
Console.WriteLine(rec);
Console.WriteLine((rec as IMixin).MixedString);
var updated = rec with
{
    Value = 2
};
Console.WriteLine(updated);
Console.WriteLine((updated as IMixin).MixedString);

The mixed in property is preserved when using with keyword, because the actual underlying class is preserved when using the with keyword.

Unfortunately, it seems not to be the case for proxies.

Is there a way to achieve something similar to that but completely in runtime with DynamicProxy?

@stakx
Copy link
Member

stakx commented Oct 24, 2023

AFAIK, C# 9's r with { P1 = x1, P2 = x2, ... } under the hood is implemented as a call to r.<Clone>$(), which is supposed to return a copy rc of r (by means of calling its type's copy constructor), and then executing assignments rc.P1 = x1, rc.P2 = x2, ...

Which means that your proxy interceptor needs to intercept these calls. When intercepting <Clone>$, you'd have to create a new proxy of the same object type using your existing ProxyGenerator, and then initialize that new proxy so it's semantically going to appear like a copy of the original one. You may also want to intercept the assignments in a meaningful way.

This will only work if the C# compiler chooses to mark the <Clone>$ method as virtual. The specification for with does not require that to be the case (possibly to support record structs) but normally the Roslyn compiler does appear to make that method virtual.

Note also that the <Clone>$ method name is an implementation detail of the Roslyn compiler, relying on it therefore carries a (perhaps mostly hypothetical, but negligible in practice) risk of brittleness.

@Evengard
Copy link
Author

Evengard commented Oct 25, 2023

I actually attempted going this way. The problem I got is that I didn't manage to transfer the mixin from the old object to the new without recreating it from scratch. There's no methods to retrieve the mixins like there's a way to retrieve a target. And when attempting to attach the proxied mixin I just got an exception.

@stakx
Copy link
Member

stakx commented Oct 25, 2023

Could you add a mixin to your proxies that returns the mixins that you added to them?

@Evengard
Copy link
Author

That's one hell of a workaround =) That would probably work, although that's really hacky...

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

2 participants