The curious case of PowerShell, Castle DynamicProxy and indexers
UPDATE: This issue is now tracked in uservoice. If it's affecting you, please upvote!
Prerequisites
As I usually like to do, here's the prerequisites for the article. Instructions and techniques likely apply to other versions, but if you're having problems replicating my results, ensure your environment matches.
- PowerShell 5 (comes with Windows 10)
- Castle DynamicProxy version 3.3.3 in your current working directory
The Problem
I seem to have hit some sort of edge case between the Castle Project's DynamicProxy object and the binding / dispatching of method calls in PowerShell. When an object with an indexer is proxied, that object's indexer is no longer available in PowerShell, however, the underlying get_Item()
method is still available.
The Setup
Here's a simple scenario to showcase the issue. First we need an interface that has an indexer in it, along with a simple class that implements that interface
public interface MyInterface
{
double this[int i] { get; }
}
public class MyClass : MyInterface
{
private double[] _array = new [] { 1.1, 2.2 };
public double this[int i]
{
get
{
return _array[i];
}
}
}
This is the object we ultimately care about and want to expose to the world. Next we need a DyanmicProxy IInterceptor
to proxy the object
public class MyProxy : IInterceptor
{
public void Intercept(IInvocation invocation)
{
invocation.Proceed();
}
}
This proxy does nothing but forward the call along, but it's enough to trigger the behavior. Lastly, for convenience, we create a factory to create the objects for us
public static class MyFactory
{
public static MyInterface GetViaInterface()
{
return new MyClass();
}
public static MyInterface GetViaProxy()
{
var generator = new ProxyGenerator();
return (MyInterface)generator.CreateInterfaceProxyWithTarget(typeof(MyInterface), new MyClass(), new MyProxy());
}
}
So now we can bring it all together with a small PowerShell script that does the following
- Loads our classes into the AppDomain
- Creates an instance of
MyClass
three different ways- Directly via
New-Object
- As an instance of
MyInterface
via the factory - As an instance of
MyInterface
wrapped with a proxy via the factory
- Directly via
- Outputs the result of calling
$obj[1]
and$obj.get_Item(1)
for each instance
Here's the script:
$assemblies = (
"Castle.Core, Version=3.3.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc"
)
$source = @"
using System;
using Castle.DynamicProxy;
namespace Sample
{
public interface MyInterface
{
double this[int i] { get; }
}
public class MyClass : MyInterface
{
private double[] _array = new [] { 1.1, 2.2 };
public double this[int i]
{
get
{
return _array[i];
}
}
}
public class MyProxy : IInterceptor
{
public void Intercept(IInvocation invocation)
{
invocation.Proceed();
}
}
public static class MyFactory
{
public static MyInterface GetViaInterface()
{
return new MyClass();
}
public static MyInterface GetViaProxy()
{
var generator = new ProxyGenerator();
return (MyInterface)generator.CreateInterfaceProxyWithTarget(typeof(MyInterface), new MyClass(), new MyProxy());
}
}
}
"@
Add-Type -Path "$pwd\Castle.Core.dll"
Add-type -ReferencedAssemblies $assemblies -TypeDefinition $source -Language CSharp
function print
{
param($Text, $Obj)
$direct = $Obj[1]
$item = $Obj.get_Item(1)
echo "$Text --> `$Obj[1] = $direct `t `$Obj.get_Item(1) = $item"
}
print "Direct construction" (New-Object Sample.MyClass)
print " Via interface" ([Sample.MyFactory]::GetViaInterface())
print " Via proxy" ([Sample.MyFactory]::GetViaProxy())
which results in the following output
Direct construction --> $Obj[1] = 2.2 $Obj.get_Item(1) = 2.2
Via interface --> $Obj[1] = 2.2 $Obj.get_Item(1) = 2.2
Via proxy --> $Obj[1] = $Obj.get_Item(1) = 2.2
Note that in the proxy case using the indexer directly gives no result, but using the underlying get_Item()
method (which is how the indexer is actually implemented) works fine.
Here's the same sample setup in a C# console app
using System;
using Castle.DynamicProxy;
namespace Sample
{
public interface MyInterface
{
double this[int i] { get; }
}
public class MyClass : MyInterface
{
private double[] _array = new[] { 1.1, 2.2 };
public double this[int i]
{
get
{
return _array[i];
}
}
}
public class MyProxy : IInterceptor
{
public void Intercept(IInvocation invocation)
{
invocation.Proceed();
}
}
public static class MyFactory
{
public static MyInterface GetViaInterface()
{
return new MyClass();
}
public static MyInterface GetViaProxy()
{
var generator = new ProxyGenerator();
return (MyInterface)generator.CreateInterfaceProxyWithTarget(typeof(MyInterface), new MyClass(), new MyProxy());
}
}
public class Program
{
private static void Print(string text, dynamic obj)
{
var direct = obj[1];
var item = obj.get_Item(1);
Console.WriteLine(string.Format("{0} --> obj[1] = {1} \t obj.Item(1) = {2}", text, direct, item));
}
static void Main(string[] args)
{
Print("Direct construction", new MyClass());
Print(" Via interface", MyFactory.GetViaInterface());
Print(" Via proxy", MyFactory.GetViaProxy());
Console.ReadKey();
}
}
}
and the corresponding output
Direct construction --> obj[1] = 2.2 obj.Item(1) = 2.2
Via interface --> obj[1] = 2.2 obj.Item(1) = 2.2
Via proxy --> obj[1] = 2.2 obj.Item(1) = 2.2
In this case the indexer works as expected. Both samples try to run the same code, and the C# app uses the dynamic
keyword to get the same late-binding used by PowerShell, but obviously something's different.
Wrap Up
Of course as a workaround you can use get_Item()
for now, but I'm hopeful for a fix. Of course it's always possible my setup is incorrect and I have a bug, so if that's the case please let me know at @MattKotsenas, or send a PR with fixes!