EnterLib PIAB又一个BUG?[续]——这是一个致命的BUG

在《EnterLib PIAB又一个BUG?》这篇文章中我们谈到:当我们通过应用DependencyAttribute特性定义需要自动注入的属性的时候,当这个属性为接口、抽象类或者没有定义无参的构造函数,无论我们调用PolicyInjection的Create方法去创建一个新的对象,还是调用Wrap方法对现有对象进行封装,都会抛出一个ResolutionFailedException异常。之后根据园友韦恩卑鄙的评论,又进行了后续的验证。如果说在前文中,我们还对这是否是个BUG抱着“谨慎”的态度,那么在这篇文章中,可以肯定地告诉你:这是一个BUG,而且是一个“致命”的BUG。

一、前景回顾

我们在重新回顾一下在《EnterLib PIAB又一个BUG?》中描述的问题。如果我们定义如下几个类型,Foo继承于MarshalByRefObject,里面具有一个类型为IBar的Bar属性,上面应用了DependencyAttribute特性使之成为一个“注入属性”。

   1: public class Foo : MarshalByRefObject
   2: {
   3:     [Dependency]
   4:     public IBar Bar { get; set; }   
   5: }
   6: public interface IBar { }
   7: public class Bar : IBar { }

当我们调用PolicyInjection.Create静态方法构建Foo对象的时候,会抛出如下图所示的ResolutionFailedException异常,错误信息表明缺乏对接口IBar的类型匹配所致。

二 、如何“解决”这个问题?

要解决这个问题就得解决对接口IBar的类型注册问题,但是PolicyInjection没有什么提供任何的API共我们进行类型的注册。不过,我们可以看看PolicyInjection的Create或者Wrap具体的实现原理。

   1: public static class PolicyInjection
   2: {
   3:     // Methods
   4:     public static TInterface Create<TObject, TInterface>(params object[] args) where TObject: TInterface
   5:     {
   6:         using (PolicyInjector policyInjector = new PolicyInjector(EnterpriseLibraryContainer.Current))
   7:         {
   8:             return policyInjector.Create<TObject, TInterface>(args);
   9:         }
  10:     }
  11:  
  12:     public static TObject Create<TObject>(params object[] args)
  13:     {
  14:         using (PolicyInjector policyInjector = new PolicyInjector(EnterpriseLibraryContainer.Current))
  15:         {
  16:             return policyInjector.Create<TObject>(args);
  17:         }
  18:     }
  19:  
  20:     public static object Create(Type typeToCreate, params object[] args)
  21:     {
  22:         using (PolicyInjector policyInjector = new PolicyInjector(EnterpriseLibraryContainer.Current))
  23:         {
  24:             return policyInjector.Create(typeToCreate, args);
  25:         }
  26:     }
  27:  
  28:     public static object Create(Type typeToCreate, Type typeToReturn, params object[] args)
  29:     {
  30:         using (PolicyInjector policyInjector = new PolicyInjector(EnterpriseLibraryContainer.Current))
  31:         {
  32:             return policyInjector.Create(typeToCreate, typeToReturn, args);
  33:         }
  34:     }
  35:  
  36:     public static TInterface Wrap<TInterface>(object instance)
  37:     {
  38:         using (PolicyInjector policyInjector = new PolicyInjector(EnterpriseLibraryContainer.Current))
  39:         {
  40:             return policyInjector.Wrap<TInterface>(instance);
  41:         }
  42:     }
  43:  
  44:     public static object Wrap(Type typeToReturn, object instance)
  45:     {
  46:         using (PolicyInjector policyInjector = new PolicyInjector(EnterpriseLibraryContainer.Current))
  47:         {
  48:             return policyInjector.Wrap(typeToReturn, instance);
  49:         }
  50:     }
  51: }

我们可以清楚地看到:最终完成对象的创建和封装的是通过一个叫做PolicyInjector的对象完成的。从下面的代码片断我们可以看出,PolicyInjector自己也定义了一系列Create和Wrap方法。

   1: public class PolicyInjector : IDisposable
   2: {
   3:     private IUnityContainer container;
   4:  
   5:     public PolicyInjector(IConfigurationSource configurationSource);
   6:     public PolicyInjector(IServiceLocator serviceLocator);
   7:     public TInterface Create<TObject, TInterface>(params object[] args) where TObject: TInterface;
   8:     public TObject Create<TObject>(params object[] args);
   9:     public object Create(Type typeToCreate, params object[] args);
  10:     public object Create(Type typeToCreate, Type typeToReturn, params object[] args);
  11:     public void Dispose();
  12:     public TInterface Wrap<TInterface>(object instance);
  13:     public object Wrap(Type typeToReturn, object instance);
  14: }

我们说,PIAB完全Unity的机制进行对象的创建和封装,实际上体现在PolicyInjector通过一个UnityContainer对象来完成对象的创建和封装工作。如果我们直接通过PolicyInjector对象,而不是通过PolicyInjection这个外观类进行对象的封装,并且通过UnityContainer对象进行类型的注册,就可以避免上述异常的出现。具体实现如下:

   1: static void Main(string[] args)
   2: {
   3:     using (PolicyInjector injector = new PolicyInjector(ConfigurationSourceFactory.Create()))
   4:     {
   5:         var containerField = typeof(PolicyInjector).GetField("container", BindingFlags.Instance | BindingFlags.NonPublic);
   6:         IUnityContainer container = (IUnityContainer)containerField.GetValue(injector);
   7:         container.RegisterType<IBar, Bar>();
   8:         var foo = injector.Create<Foo>();
   9:         Console.WriteLine("foo.Bar.GetType().Name: {0}",foo.Bar.GetType().Name);
  10:     }
  11: }

输出结果:

   1: foo.Bar.GetType().Name: Bar

三、这真的是解决方案吗?

如果你足够仔细的话,在上面一节的标题中“解决”二字是加上引号的。我实际上在挖一个坑,诱使你往里跳:)。如果你真采用这个解决方案的话,一个“致命”的错误将会产生。

我们说过,PIAB的Create方法最终也是调用Wrap方法,而Wrap方法就会自作主张地去完成相应的注入工作——这本质上就是PIAB的BUG。我们说这个BUG足以致命,我可以通过一个简单的例子来说明这一点。假设我们具有如下的类型定义,Foo和IBar定义没有改变,现在我们定义两个具体的类Bar1和Bar2去实现IBar这个接口。

   1: public class Foo : MarshalByRefObject
   2: {
   3:     [Dependency]
   4:     public IBar Bar { get; set; }
   5: }
   6: public interface IBar { }
   7: public class Bar1 : IBar { }
   8: public class Bar2 : IBar { }

现在我们执行如下的代码:首先我们对IBar这个接口注册了匹配关系,让它直接映射到Bar1。然后我们创建一个Foo对象,并将Bar属性初始化成一个Bar2类型的对象,然后调用PolicyInjector的Wrap方法对Foo对象进行封装。通过输出结果,我们清楚地看到:Wrap方法的执行会按照我们注册的类型匹配关系重新设置了注入属性Bar的值,即类型为Bar1的对象

   1: static void Main(string[] args)
   2: {
   3:     using (PolicyInjector injector = new PolicyInjector(ConfigurationSourceFactory.Create()))
   4:     {
   5:         var containerField = typeof(PolicyInjector).GetField("container", BindingFlags.Instance | BindingFlags.NonPublic);
   6:         IUnityContainer container = (IUnityContainer)containerField.GetValue(injector);
   7:         container.RegisterType<IBar, Bar1>();
   8:         var foo = new Foo();
   9:         foo.Bar = new Bar2();
  10:         Console.WriteLine("foo.Bar.GetType().Name: {0}", foo.Bar.GetType().Name);
  11:         foo = injector.Wrap<Foo>(foo);
  12:         Console.WriteLine("foo.Bar.GetType().Name: {0}", foo.Bar.GetType().Name);
  13:     }
  14: }

输出结果:

   1: foo.Bar.GetType().Name: Bar2
   2: foo.Bar.GetType().Name: Bar1

四、方法注入一样有问题

依赖诸如具有三种典型的表现形式:构造注入、属性(设置)注入和方法(调用)注入。既然PolicyInjection的Wrap方法会自作主张地完成属性注入的工作,对于方法(调用)注入也不能幸免。对于这个问题,我们直接调用PolicyInjection的Wrap方法就可以模拟。

重新定义类型Foo,让它具有一个Int类型的属性Count,该属性通过一个应用了InjectionMethodAttribute特性的方法Initialize被初始化成-1。

   1: public class Foo : MarshalByRefObject
   2: {
   3:     public int Count { get; set; }
   4:     [InjectionMethod]
   5:     public void Initialize()
   6:     {
   7:         Count = -1;
   8:     }
   9: }

现在,我们执行如下的代码,我们发现Wrap方法的调用篡改了我们实现设置的值(100-〉-1)。

   1: static void Main(string[] args)
   2: {
   3:     var foo = new Foo();
   4:     foo.Count = 100;
   5:     Console.WriteLine("Before Wrapping: foo.Count = {0}", foo.Count);
   6:     foo = PolicyInjection.Wrap<Foo>(foo);
   7:     Console.WriteLine("After Wrapping : foo.Count = {0}", foo.Count);
   8: }

输出结果:

   1: Before Wrapping: foo.Count = 100
   2: After Wrapping : foo.Count = -1

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏草根专栏

设计模式学习(四): 1.简单工厂 (附C#实现)

New ? ? 这是一个典型的情况, 我们需要在运行时来实例化一些具体的类. 在需要修改或者扩展的时候我们就需要改这段代码. 一个程序中可能会多次出现类似的代码...

2976
来自专栏大内老A

EnterLib PIAB又一个BUG?[续]——这是一个致命的BUG

在《EnterLib PIAB又一个BUG?》这篇文章中我们谈到:当我们通过应用DependencyAttribute特性定义需要自动注入的属性的时候,当这个属...

2419
来自专栏领域驱动设计DDD实战进阶

05-TypeScript中的方法新功能(下)

再TypeScript中,方法还有一些新功能能够让我们更好的控制方法执行。 1.Generator方法: yield关键字用于控制方法在执行的时候暂停住,后续方...

2805
来自专栏大内老A

深入探讨ASP.NET MVC的筛选器

在ActionInvoker对Action的执行过程中,除了通过利用ActionDescriptor对Action方法的执行,以及之前进行的Model绑定与验证...

2078
来自专栏草根专栏

C# 7.0简而言之 -- 01. C#和.NET Framework简介

C#里面所有的类型都有一个共享的基类, 这也意味之C#里面所有的类型都具备一些相同的基本功能, 例如任何类型都可以通过调用ToString()方法来转化成字符串...

4949
来自专栏程序员的SOD蜜

实体类的二进制序列化

在.NET中,我们可以将对象序列化从而保存对象的状态到内存或者磁盘文件中,或者分布式应用程序中用于系统通信,一般来说,二进制序列化的效率要高,所获得的字节数最小...

2299
来自专栏码农阿宇

用.Net Core控制台模拟一个ASP.Net Core的管道模型

在我的上几篇文章中降到了asp.net core的管道模型,为了更清楚地理解asp.net core的管道,再网上学习了.Net Core控制台应用程序对其的模...

2749
来自专栏逸鹏说道

【推荐】C#线程篇---Task(任务)和线程池不得不说的秘密(5.2)

ContinueWith? 啥东西~~??  要写可伸缩的软件,一定不能使你的线程阻塞。这意味着如果调用Wait或者在任务未完成时查询Result属性,极有...

4306
来自专栏程序员互动联盟

【专业技术】反射技术探究

反射 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的以及动态调用对象...

3286
来自专栏逸鹏说道

【推荐】C#线程篇---Task(任务)和线程池不得不说的秘密(5.1)

在上篇最后一个例子之后,我们发现了怎么去使用线程池,调用ThreadPool的QueueUserWorkItem方法来发起一次异步的、计算限制的操作,例子很简单...

4435

扫码关注云+社区

领取腾讯云代金券