c# - 我如何为公共(public)接口(interface)类型编写我自己的装饰器实用程序(动态代理如何工作)?

标签 c# decorator

我注意到,有时对于具有非常简单的基本情况(不是批评)的事物,存在完整的框架作品。例如,您可以使用几行代码和哈希表制作服务定位器,或者您可以使用整个框架。

也就是说,我很好奇是否有一种同样简单的自己动手做装饰器的方法。 (如果我不正确地使用了这个模式的名称,请纠正我,我是这个词的新手)。

动机:我很好奇像 CaSTLe Dynamic Proxy 这样的东西是如何工作的......

如何编写 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();

输出将是:

before foo
foo
after foo

最佳答案

据我了解,至少有两种生成运行时代码的方法 - 如果您习惯使用 IL,则可以使用 Reflection.Emit , 否则你可以使用 CSharpCodeProvider这使您能够在运行时从字符串或一系列以 DOM 样式描述代码的对象编译代码。

这实际上是我第一次使用 CSharpCodeProvider,但这是我尝试使用它在运行时为接口(interface)创建代理类。这不是一个完整的解决方案,但如果有以下条件,它应该是一个不错的开始:

  1. 它不包括将 Lambdas 转换为字符串。据我了解,this can be done .

  2. 每次调用都创建一个新的编译器不会很好地执行;您可以在每个接口(interface)类型的基础上缓存编译器。

  3. 编译源代码后,您可以(并且应该)检查 results 对象以确保编译成功:

代码如下:

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;
}

关于c# - 我如何为公共(public)接口(interface)类型编写我自己的装饰器实用程序(动态代理如何工作)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6716132/

相关文章:

Python 装饰器与 CLOS "around"方法的比较

python - 如果用户位于特定网址且未通过测试,则阻止用户进入另一个 View

python - 使用装饰器向类添加方法

带有参数和访问类实例的 Python 装饰器

c# - 使用 ASP.net 凭据从 WinForms 应用程序登录

c# - SevenZipSharp 无法解压某些 tar 文件

c# - 两个列表框具有相同的值,我需要从一个列表框中删除相同的值

c# - 从 Winforms 迁移到 WPF

c# - Linq to Sql - 具有多个连接的不同项目的查询列表

c# - 如何将装饰器模式与 C# MEF 相结合?