how can I code my own decorator utility for public interface types (how do dynamic proxies work)?
I've noticed that sometimes entire frame works exist for things that have very simple basic cases (not a critisism). For example you can make a service locator with a few lines of code and a hashtable or you can use an entire framework.
That said, I'm curious if there's an equally simple way to do-it-yourself decorator. (correct me if I use the name of this pattern improperly, i'm new-sh to the term).
Motivation: I'm curious how things like Castle Dynamic Proxy work...
How开发者_高级运维 could you code MyContainer.Get(Action before, Action after)?
public interface IFoo {
void F();
}
public class Foo : IFoo {
public void F() {
Console.WriteLine("foo");
}
}
IFoo foo = MyContainer.Get<IFoo>(
() => { Console.WriteLine("before foo"); },
() => { Console.WriteLine("after foo"); });
foo.F();
Output would be:
before foo
foo
after foo
As I understand it, there are at least two ways of doing runtime code generation - if you're comfortable using IL you can use Reflection.Emit, otherwise you can use a CSharpCodeProvider which enables you to compile code at runtime from strings or from a series of objects which describe code in a DOM style.
This is literally the first I've used CSharpCodeProvider
, but here's my stab at using it to create a proxy class for an interface at runtime. This is not a complete solution, but with the following provisos it should be a decent start:
It doesn't include converting the
Lambdas
into strings. As I understand it though, this can be done.Creating a new compiler with every call will not perform well; you could cache compilers on a per-interface-type basis.
After you compile the source code you can (and should) check the
results
object to make sure the compilation worked:
Here's the code:
public static T Get<T>(Action beforeMethodCall, Action afterMethodCall)
{
Type interfaceType = typeof(T);
// I assume MyContainer is wrapping an actual DI container, so
// resolve the implementation type for T from it:
T implementingObject = _myUnderlyingContainer.Resolve<T>();
Type implementingType = implementingObject.GetType();
// Get string representations of the passed-in Actions: this one is
// over to you :)
string beforeMethodCode = GetExpressionText(beforeMethodCall);
string afterMethodCode = GetExpressionText(afterMethodCall);
// Loop over all the interface's methods and create source code which
// contains a method with the same signature which calls the 'before'
// method, calls the proxied object's method, then calls the 'after'
// method:
string methodImplementations = string.Join(
Environment.NewLine,
interfaceType.GetMethods().Select(mi =>
{
const string methodTemplate = @"
public {0} {1}({2})
{{
{3}
this._wrappedObject.{1}({4});
{5}
}}";
// Get the arguments for the method signature, like
// 'Type1' 'Name1', 'Type', 'Name2', etc.
string methodSignatureArguments = string.Join(
", ",
mi.GetParameters()
.Select(pi => pi.ParameterType.FullName + " " + pi.Name));
// Get the arguments for the proxied method call, like 'Name1',
// 'Name2', etc.
string methodCallArguments = string.Join(
", ",
mi.GetParameters().Select(pi => pi.Name));
// Get the method return type:
string returnType = (mi.ReturnType == typeof(void)) ?
"void"
:
mi.ReturnType.FullName;
// Create the method source code:
return string.Format(
CultureInfo.InvariantCulture,
methodTemplate,
returnType, // <- {0}
mi.Name, // <- {1}
methodSignatureArguments, // <- {2}
beforeMethodCode, // <- {3}
methodCallArguments, // <- {4}
afterMethodCode); // <- {5}
}));
// Our proxy type name:
string proxyTypeName = string.Concat(implementingType.Name, "Proxy");
const string proxySourceTemplate = @"
namespace Proxies
{{
public class {0} : {1}
{{
private readonly {1} _wrappedObject;
public {0}({1} wrappedObject)
{{
this._wrappedObject = wrappedObject;
}}
{2}
}}
}}";
// Get the proxy class source code:
string proxySource = string.Format(
CultureInfo.InvariantCulture,
proxySourceTemplate,
proxyTypeName, // <- {0}
interfaceType.FullName, // <- {1}
methodImplementations); // <- {2}
// Create the proxy in an in-memory assembly:
CompilerParameters codeParameters = new CompilerParameters
{
MainClass = null,
GenerateExecutable = false,
GenerateInMemory = true,
OutputAssembly = null
};
// Add the assembly that the interface lives in so the compiler can
// use it:
codeParameters.ReferencedAssemblies.Add(interfaceType.Assembly.Location);
// Compile the proxy source code:
CompilerResults results = new CSharpCodeProvider()
.CompileAssemblyFromSource(codeParameters, proxySource);
// Create an instance of the proxy from the assembly we just created:
T proxy = (T)Activator.CreateInstance(
results.CompiledAssembly.GetTypes().First(),
implementingObject);
// Hand it back:
return proxy;
}
精彩评论