Wednesday, May 14, 2008

Anonymous Interface Implementation

With the introduction of lambdas, it's now incredibly easy to pass tiny bits of functionality around. Unfortunately, some existing methods in the Framework take parameters of an interface type, instead of a delegate. Contains, for example, takes an IEqualityComparer instead of a Predicate. This makes using these methods in an ad-hoc manner difficult. What we need is the ability to quickly throw together a temporary class that implements the interface, basically an anonymous type only with method implementation.

I submitted a Connect item about this (here), but as you can see it won't be making it into this release.

To tide myself over I wrote a version that is built on anonymous types, Reflection.Emit, and Funcs. You can download it here. This is a proof of concept...no warranties...don't use in production...blah blah blah.

As a sample, here's how to create an instance of IEqualityComparer that thinks strings are equal if they are the same length:

Make.Instance<IEqualityComparer<string>>(
new {
Equals = Make.Func(
(string x, string y) => x.Length == y.Length)
})


I had to use Eric Lippert's trick to get lambdas and inferencing working in anonymous types.

3 comments:

Kurt Harriger said...

First, I agree with you complete, this functionality is practically essential and I'm really bummed out they stopped short on this one. This is useful not just for a quick and dirty simple interfaces, but also for sharing var types with other methods strongly typing vars. The var keyword is great but with such limited scope it discourages many common refactorings such as replace temp with query resulting in longer methods. You can of course define a class and use object initializers, but then the class must have lots all the properties you need to use within the method where the interface can be smaller and less code clutter due to having lots extra concrete projection classes.

Another workaround would be to use transparent proxies to avoid creating temp assemblies. You can probably combine the IL method generation with transparent proxy to get a best of both type thing.

Here is quick prototype. I not sure how to invoke delegate w/ ref/out parameters so these probably don't work at the moment, but thats probably be easily fixed. It doesn't work with overloaded methods since the binding occurs by name, but should handle most interfaces.


using System;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Messaging;

namespace AnonInterface
{

class AdaptToInterfaceProxy<T> : RealProxy where T : class /* interface */
{
readonly Type objType;
readonly object obj;
public AdaptToInterfaceProxy(object obj)
: base(typeof(T))
{
if (obj == null) throw new ArgumentNullException("obj");

this.obj = obj;
this.objType = obj.GetType();
}
public override System.Runtime.Remoting.Messaging.IMessage Invoke(System.Runtime.Remoting.Messaging.IMessage msg)
{
IMethodCallMessage mcm = msg as IMethodCallMessage;
try
{
MemberInfo memberInfo = objType.GetMember(mcm.MethodName).Single<MemberInfo>();
// is the member info a method (property getter)
MethodInfo methodInfo = memberInfo as MethodInfo;
// if not check if it is a property and if that property is a delegate
if (methodInfo == null)
{
PropertyInfo pi = memberInfo as PropertyInfo;
if (pi != null)
{
object propValue = pi.GetValue(obj, null);
Delegate func = propValue as Delegate;
if (func != null) methodInfo = func.Method;
}
}
if (methodInfo != null)
{
object result = methodInfo.Invoke(obj, mcm.Args);
return new ReturnMessage(result, null, 0, mcm.LogicalCallContext, mcm);
}
else return new ReturnMessage(new System.NotImplementedException(String.Format("Unable to find property \"{0}\" on anonymous object.", mcm.MethodName)), mcm);
}
catch(Exception ex)
{
return new ReturnMessage(ex, mcm);
}
}
}
static class ExtensionMethods
{

public static T AdaptToInterface<T>(this object obj) where T : class
{
return (T)(new AdaptToInterfaceProxy<T>(obj)).GetTransparentProxy();
}
}

interface ITestContract
{
string SimpleProperty { get; }
string PropUsingGetter { get; }
void DoSomething();
string GetSomething();
void UseSomething(string s);
}

class Program
{
static void Main(string[] args)
{
ITestContract c =
new
{
SimpleProperty = "value",
get_PropUsingGetter = new Func<string>(() => "prop getter"),
DoSomething = new Action(() => Console.WriteLine("hello")),
GetSomething = new Func<string>(() => "Hello"),
UseSomething = new Action<string>((s) => Console.WriteLine(s))
}.AdaptToInterface<ITestContract>();


string property = c.SimpleProperty;
Console.WriteLine(property);
string property2 = c.PropUsingGetter;
Console.WriteLine(property2);

c.DoSomething();
string str = c.GetSomething();

Console.WriteLine(str);

c.UseSomething("using stuff");

Console.ReadLine();
}
}
}


kurtharriger@comcast.net

alwin said...

For the equaltity comparer I use this:

public class FuncEqualityComparer{T} : IEqualityComparer{T}
{
private readonly Func{T, T, bool} predicate;

public FuncEqualityComparer(Func{T, T, bool} predicate)
{
this.predicate = predicate;
}

public bool Equals(T x, T y)
{
return predicate(x, y);
}

public int GetHashCode(T obj)
{
return obj.GetHashCode();
}

}

(from http://www.codeplex.com/umbrella)

anonymous transactions said...

Yes! Its quiet sensitive and appealing. It’s so vivid in description that my mind was swayed in imagination while reading it. It’s also very informative so thanks, and keep writing. tax avoidance