|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThe
Note: The word If there are any features that you think should be added (or any features you want to add yourself), just send me a copy of it so that I can look at it and merge it into the codebase that I currently have with me. Anyway, let's go back to the article… Language Independent?
Dynamic Proxies
Duck Typing and Late BindingDynamic languages such as Ruby and Python (and to some extent, VB.NET) have the amazing ability to call methods at runtime without knowing exactly which methods they will be using at compile time. Furthermore, those take that concept even further and actually "infer" which method should be called based on the arguments that are passed to the method at runtime (aka "Duck Typing"). It would be nice if there were a way to do that natively in C# (2.0), but unfortunately, that's not entirely possible... unless you use Ruby-style MixinsDynamic languages have the ability to seamlessly and dynamically modify their classes at runtime so that each class can practically add and subtract methods (and even add other classes and interface implementations) to and from each class implementation. Believe it or not, I've found a way to implement the same type of dynamic mixins in both C# and VB.NET and still be able to adhere to the strongly-typed rules of each of both languages! (Well, maybe not VB.NET, since sometimes it can be loosely typed, but I digress). With Delegates with Lambda Arguments (aka, "Currying")While languages such as C# 3.0 and the upcoming version of VB.NET have support for defining Lambda "Expressions," one feature that I've always wanted to have is the ability to take any delegate and actually hard-bind (or close) one or more of the delegate parameters to a specific arbitrary value. In other words, this feature allows you to "simulate" delegate calls by hard-wiring some (or all) of the arguments passed to a delegate when an event is fired. In other words, Universal Event HandlingOne of the biggest problems in writing an application that uses the MVC pattern (or the MVP pattern) is finding a uniform way to have the controller handle events that are fired from the Presentation/View without knowing "a priori" the signature of those events at compile time. A Very, Very Simple IoC ContainerImagine that you had an Inversion of Control (or Dependency Injection) container that didn't rely on an external configuration file (such as an XML file) to assemble itself. Furthermore, suppose that the only "configuration" that you would have to do would involve less than a few lines of code and a simple Design by ContractThere have been quite a few attempts at creating a DbC Framework in .NET (and even in Java, with JContract) such as eXtensible C#, Kevin McFarlane's DbC Framework and the like, but few of them have been able to effectively emulate the Eiffel language's DbC mechanism without tying their respective implementations to their particular language (in the case of XC#) or cluttering their domain methods with numerous precondition, postcondition and invariant checks (as is the case with McFarlane's library). As you can see, BackgroundThis article assumes that you're familiar with the concept of dynamic proxies and, for those of you who are interested in figuring out how it all works, I'll do my best to explain it in the next few sections without having to dive into the gory details of DynamicProxyThis article will show you how to use Using the Code
public class Greeter
{
public virtual void Greet()
{
Console.WriteLine("Hello, World!");
}
}
In this example, we're going to wrap the Choose Your FlavorWith public interface IInterceptor
{
object Intercept(InvocationInfo info);
}
public interface IInvokeWrapper
{
void BeforeInvoke(InvocationInfo info);
object DoInvoke(InvocationInfo info);
void AfterInvoke(InvocationInfo info, object returnValue);
}
Wrapping the Greeter ObjectDepending on what your needs might be, you'll have to implement one of these two interfaces to create your own interceptor. In this case, we're going to implement public class GreetInterceptor : IInvokeWrapper
{
private Greeter _target;
public GreetInterceptor(Greeter target)
{
_target = target;
}
public void BeforeInvoke(InvocationInfo info)
{
Console.WriteLine("BeforeGreet() called");
}
public object DoInvoke(InvocationInfo info)
{
// Make our own greeting,
// and ignore the old one
Console.WriteLine("Hello, CodeProject!");
object result = null;
// Note: If you wanted to call the original
// implementation, uncomment the following line:
//result = info.TargetMethod.Invoke(_target, info.Arguments);
return result;
}
public void AfterInvoke(InvocationInfo info, object returnValue)
{
Console.WriteLine("AfterGreet() called");
}
}
The first thing that you might notice here is that the interceptor needs to have a reference to an actual instance of a greeter object. That's because A Manually Written ProxyIf I were to write the proxy by hand, it would look something like this: public class GreeterProxy : Greeter, IProxy
{
private IInterceptor _interceptor;
public override void Greet()
{
if(_interceptor == null)
throw new NotImplementedException();
// The following is pseudocode:
InvocationInfo info = new InvocationInfo();
// Note: The actual proxy would fill the info
// object with the necessary method data
// Pass the call to the interceptor
_interceptor.Intercept(info);
}
public IInterceptor Interceptor
{
get
{
return _interceptor;
}
set
{
_interceptor = value;
}
}
}
As you can see here, InvocationInfo ObjectsThe public class InvocationInfo
{
// … Constructor omitted for brevity
public object Target
{
get
{
return _proxy;
}
}
public MethodInfo TargetMethod
{
get
{
return _targetMethod;
}
}
public StackTrace StackTrace
{
get
{
return _trace;
}
}
public MethodInfo CallingMethod
{
get
{
return (MethodInfo) _trace.GetFrame(0).GetMethod();
}
}
public Type[] TypeArguments
{
get
{
return _typeArgs;
}
}
public object[] Arguments
{
get
{
return _args;
}
}
// …
}
Most of the properties in this class are fairly self-explanatory. The What seems to be missing here, however, is any reference to the actual object that will provide the implementation for this proxy (in this case, a Note: I separated the proxy instance from the proxy implementation (and thus, the real object) to make it easier to manage multiple proxies at once. In theory, a handful of these proxies could be pooled together and reused to save memory, but such a task is way beyond the scope of this article. I'll leave it to the readers to come up with a scheme to make that work. Putting It All TogetherNow that there is a ProxyFactory factory = new ProxyFactory();
Greeter actualGreeter = new actualGreeter();
GreetInterceptor interceptor = new GreetInterceptor(actualGreeter);
Greeter greeter = factory.CreateProxy<Greeter>(interceptor);
Once the // Since the interceptor is in place, the original "Hello, World!" message
// will be replaced with "Hello, CodeProject!"
greeter.Greet();
After the The IProxy InterfaceAs it turns out, every proxy generated by IInterceptor otherInterceptor = new SomeOtherInterceptor();
IProxy proxy = (IProxy)greeter;
proxy.Interceptor = otherInterceptor;
// Call the new implementation
greeter.Greet();
Points of InterestOne thing about Benchmark SetupThis benchmark was run against both protected void AddToCache(CacheKey key, Type type)
{
// NOTE: This has been disabled for benchmarking purposes
//scope.RegisterInCache(key, type);
}
For …
ProxyFactory factory = new ProxyFactory();
// Disable the cache in order to prevent
// the factory from skewing the benchmark results
factory.Cache = null;
…
Benchmark Results
This test simulates a worst-case scenario where each proxy generator (or factory) effectively has to generate up to one thousand unique proxy types in succession. As the results of this benchmark show, both of these libraries scale quite well when generating up to one hundred unique proxy types. However, Castle's performance significantly slows down once the benchmark passes the one hundred type threshold. In fact, as it approaches the point where it has to generate a thousand unique types, it runs five times slower than Since type caching is enabled by default in both libraries, however, multiple calls to generate the same proxy type will be cached and those calls (in practice) will only incur a minimal amount of overhead. Nonetheless, this benchmark shows the speed differences between the two libraries and, as these numbers show, Note: If you should be inclined to perform benchmarks of your own, I've included the benchmarking code as part of the source code, as well as the Excel 2007 spreadsheet that I used to generate that chart for the benchmarks. I've also included a copy of the modified Castle.DynamicProxy2.dll with its caching disabled, just in case someone else on CodeProject would be interested in adding even more benchmarks. LimitationsThere are a few things that It cannot inherit from sealed types. By itself, LFDP cannot override a sealed type. However, once I get to discussing It cannot override non-virtual members. This one is pretty self-explanatory. Lately, I've been playing around with using the Special ThanksThanks to Jeff Brown and Julien Dagorn, LicenseThe entire Coming Up in the Next Article
public interface IPerson
{
string Name
{
get;
}
int Age
{
get;
}
}
public interface IDogOwner
{
int NumberOfDogsOwned
{
get;
}
}
Using // Notice that this DynamicObject is actually wrapping a *bare* System.Object
// and that there is initially no underlying implementation
DynamicObject dynamicObject = new DynamicObject(new object())
// Implement IPerson
dynamicObject.MixWith(new { Name="Me", Age=18 });
// Implement IDogOwner
dynamicObject.MixWith(new { NumberOfDogsOwned=1 });
// If it looks like a person…
IPerson person = dynamicObject.CreateDuck<IPerson>();
// …then it must be a person!
// This will return "Me"
string name = person.Name;
// This will return '18'
int age = person.Age;
// …or an IDogOwner
IDogOwner dogOwner = dynamicObject.CreateDuck<IDogOwner>();
// This will return '1'
int dogsOwned = dogOwner.NumberOfDogsOwned;
For now, I'll let you digest that last block of code in your mind for awhile. Suffice to say, History
| ||||||||||||||||||||