DynamicProxy has support for by-reference parameters (that is, those having a ref or out modifier in C#, or a ByRef modifier in Visual Basic). However, those parameters work slightly differently during an intercepted invocation than they would during a regular (non-intercepted) method call. In most use cases, you will probably not notice this difference at all; but when you do, it can be helpful to understand why it exists.
During a regular method invocation, a by-reference parameter is a perfect alias for the variable being passed by the caller: Changing the value of a ref parameter, for instance, instantly changes the aliased variable.
During a DynamicProxy method interception, on the other hand, changes to a ref parameter (which you would perform using the Arguments property or SetArgumentValue method of the IInvocation instance) are not immediately reflected in the aliased variable. The change in the aliased variable will only become visible upon completion of the interception. This is because DynamicProxy essentially "buffers" all invocation arguments in a separate storage location (IInvocation.Arguments) for the whole duration of a method interception.
-
When interception begins, DynamicProxy copies all arguments provided by the caller over into the
Argumentsarray of theIInvocationobject. This copying happens "by value". ThoseArgumentsindices representing a by-reference parameter are not aliased to a caller variable; they are distinct storage locations. Therefore, changingArgumentsis not immediately propagated. -
When interception ends, DynamicProxy will copy the final values from
Argumentsback to the originalrefandoutparameters. This is the moment when any changes become visible in the aliased variables of the caller.
In short, modifying a by-reference parameter during interception will be reflected in the aliased variable eventually, but not immediately.
Let's look at this difference in behavior in action. The following code examples will be based on this interface:
public interface IService
{
void Execute(ref int n);
}As stated above, by-reference parameters are normally perfect aliases for other variables, and changes to them should become immediately observable on those aliased variables.
We can see this behavior using the following implementation of IService:
sealed class Service : IService
{
private ExecuteDelegate execute;
public Service(ExecuteDelegate execute) => this.execute = execute;
public void Execute(ref int n) => this.execute?.Invoke(ref n);
}
delegate void ExecuteDelegate(ref int n);and then using and invoking IService.Execute as follows:
int n = 0;
var service = new Service(
execute: (ref int alias) =>
{
Console.WriteLine(n); // => 0
alias = 42;
Console.WriteLine(n); // => 42
});
Console.WriteLine(n); // => 0
service.Execute(ref n);
Console.WriteLine(n); // => 42Note that inside the lambda, we change the alias parameter but can observe the change to n (to which it is aliased) immediately.
As explained above, changes to ref parameters will be observable in the aliased variable only after, but not during interception. Let's see an example. Instead of implementing IService ourselves, we will let DynamicProxy create an instance. So we will need an interceptor:
sealed class Interceptor : IInterceptor
{
private Action<IInvocation> intercept;
public Interceptor(Action<IInvocation> intercept) => this.intercept = intercept;
public void Intercept(IInvocation invocation) => this.intercept?.Invoke(invocation);
}Note the similarity to the above example's implementation of IService. The basic idea here is the same: We want to be able to define what should happen during method invocation inside a lambda that has access to the aliased variable:
int n = 0;
var service = new ProxyGenerator().CreateInterfaceProxyWithoutTarget<IService>(new Interceptor(
intercept: invocation =>
{
Console.WriteLine(n); // => 0
invocation.SetArgumentValue(0, 42);
Console.WriteLine(n); // => 0 !!!
}));
Console.WriteLine(n); // => 0
service.Execute(ref n);
Console.WriteLine(n); // => 42Note that this time, changing the argument value during invocation does not immediately affect the aliased variable n,
even though it is eventually updated.