DynamicProxy, Equals and Hashes oh my...

1 view
Skip to first unread message

gigs

unread,
Jul 31, 2008, 1:37:39 PM7/31/08
to LinFu.Framework
Hello all,

I ran in to an issue with using a proxied object as a key in a
hashtable. I've included some code that works around the issue, but it
doesn't feel like the best way to handle it. As a minor point of
discussion using equivilant Castle.DynamicProxy2 code passes these
tests.

If anyone would like I have this code wrapped up in a VS20008 solution
that I can post if needed.

Here some test code that demos the problem.

using System.Collections;
using LinFu.DynamicProxy;
using NUnit.Framework;

namespace LinfuEquals
{
[TestFixture]
public class LinfuEqualsTests
{
[Test]
public void Failing_Equals_Test()
{
ProxyFactory factory = new ProxyFactory();
Entity entity = new Entity();

BadEnitityInterceptor interceptor = new
BadEnitityInterceptor(entity);
Entity proxy = factory.CreateProxy<Entity>(interceptor);

Assert.IsTrue(proxy.Equals(proxy), "The objects are not
equal");
}

[Test]
public void Passing_Equals_Test()
{
ProxyFactory factory = new ProxyFactory();
Entity entity = new Entity();

GoodEnitityInterceptor interceptor = new
GoodEnitityInterceptor(entity);
Entity proxy = factory.CreateProxy<Entity>(interceptor);

Assert.IsTrue(proxy.Equals(proxy), "The objects are not
equal");
}

[Test]
public void Why_Does_This_Matter()
{
ProxyFactory factory = new ProxyFactory();
Entity entity = new Entity();

BadEnitityInterceptor interceptor = new
BadEnitityInterceptor(entity);
Entity proxy = factory.CreateProxy<Entity>(interceptor);

Hashtable table = new Hashtable();
table.Add(proxy, new object());

Assert.IsTrue(table.ContainsKey(proxy), "The hash does not
contain the key");
}
}

internal class GoodEnitityInterceptor : EnitityInterceptorBase
{
public GoodEnitityInterceptor(object target) : base(target){}

public override object DoInvoke(InvocationInfo info)
{
if (info.TargetMethod.Name == "Equals")
{
return info.Arguments[0].Equals(_target);
}

return info.TargetMethod.Invoke(_target, info.Arguments);
}

}

public class BadEnitityInterceptor : EnitityInterceptorBase
{
public BadEnitityInterceptor(object target) : base(target){}

public override object DoInvoke(InvocationInfo info)
{
return info.TargetMethod.Invoke(_target, info.Arguments);
}
}

public abstract class EnitityInterceptorBase : IInvokeWrapper
{
protected readonly object _target;

protected EnitityInterceptorBase(object target)
{
_target = target;
}

public abstract object DoInvoke(InvocationInfo info);
public void BeforeInvoke(InvocationInfo info) { }
public void AfterInvoke(InvocationInfo info, object
returnValue) { }
}

public class Entity
{
public virtual string Name { get; set; }
}
}

Philip_L

unread,
Jul 31, 2008, 5:45:39 PM7/31/08
to LinFu.Framework
Hi gigs,

The reason why your test is failing is because the proxy forwards all
of its calls (including the ones made to System.Object) back to the
interceptor. This is done by design because there might be times where
you might have two different proxies pointing back to the same
interceptor instance, and if you have to proxies pointing back to the
same interceptor, then DynamicProxy needs to make it look like they're
both pointing to the same object instance despite the fact that you're
actually working with two separate proxy instances.

I can't speak for Castle, but I assume that the reason why this works
for them is because they probably don't override System.Object, and
that might give you some problems if you need to determine if two
proxies are actually pointing back to the same interceptor instance.

gigs

unread,
Jul 31, 2008, 10:08:02 PM7/31/08
to LinFu.Framework
Thanks for the reply,

I understand what is happening, but at first glance when adding a
proxied object to a hash as the key you get a very unexpected
behavior. I just wanted to be sure this was the intended behavior and
that others agree that the implementation in GoodEnitityInterceptor
above was the best way to deal with the issue?

I hope I didn't come off as being negative by comparing LinFu to
Castle, it was just that doing an equivilant operation produces the
expected hash behavior. If the same thing had happened using Castle I
would have assumed there was no issue.

Hopefully this post can enlighten others when they run into the same
situation.

Thanks for the great lib and keep up the good work :-)

Philip_L

unread,
Jul 31, 2008, 10:30:53 PM7/31/08
to LinFu.Framework
Hi gigs,

Thanks for the kind words. Running into edge cases is always useful,
and there's certainly no harm done in letting everyone else know about
this issue.

Anyway, I can't speak for everyone else, but I'll just throw my two
cents in and say that this is the intended behavior of LinFu's
DynamicProxy. On the other hand, I can't say much about Castle's
DynamicProxy since I practically gave up on trying to decipher their
AST model to generate IL. It was way too complicated. To me, it seemed
a little counterintiutive to wrap an entire API around
Reflection.Emit. It took them at least a few months to add support for
generics when in reality, it only takes a few more IL instructions to
save the parameters of a generic method or generic class. Anyway,
that's enough of my ranting. Suffice to say, the two libraries serve
the same function, and as you've pointed out, they do have some slight
differences.

As per your issue, there's no right or wrong way to do it--it all
depends on what your business requirements might be for a given
project.
Reply all
Reply to author
Forward
0 new messages