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;
}
精彩评论