<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      .Net中的AOP系列之《攔截位置》

      返回《.Net中的AOP》系列學(xué)習(xí)總目錄


      本篇目錄


      本節(jié)的源碼本人已托管于Coding上:點(diǎn)擊查看

      本系列的實(shí)驗(yàn)環(huán)境:VS 2013 Update 5(建議最好使用集成了Nuget的VS版本,VS Express版也夠用),安裝了PostSharp。


      至今,我們的關(guān)注點(diǎn)都是集中在方法上,本節(jié),就看一下位置,這里的位置指的是字段或?qū)傩浴N恢脭r截是AOP框架不太通用的功能,因此,本節(jié)大多數(shù)的例子都是使用支持位置的PostSharp框架,此外,這節(jié)還會(huì)看到一個(gè)特殊的AOP工具(與常見(jiàn)的AOP框架截然不同),叫做PropertyChanged.Fody

      位置攔截

      也許很多人沒(méi)有聽(tīng)過(guò)C#中有位置這一說(shuō),其實(shí),一個(gè)字段或者一個(gè)屬性都是一個(gè)位置。字段和屬性是OOP中常見(jiàn)的東西,它們?yōu)?strong>類(lèi)提供數(shù)據(jù)和結(jié)構(gòu)。下面簡(jiǎn)單復(fù)習(xí)一下,覺(jué)得沒(méi)問(wèn)題的同學(xué)可以直接跳過(guò),記住:屬性只是getter/setter方法的語(yǔ)法糖。

      .Net中的字段和屬性

      字段是類(lèi)的成員。它們可以聲明為public,private,protected,internal等等,這樣就可以限制訪(fǎng)問(wèn)級(jí)別了(默認(rèn)是private)。

      通常,如果封裝很重要的話(huà),就不會(huì)使用public字段,因此,字段通常被設(shè)置成private,然后通過(guò)訪(fǎng)問(wèn)器方法在類(lèi)外面使用該字段。如果有一個(gè)private的_balance(余額)字段,那么只能通過(guò)其它對(duì)象調(diào)用Deposit(存款)或者Withdrawal(取款)方法來(lái)改變這個(gè)字段的值:

      public class BankAccount {
          decimal _balance;
          public void SetBalance(decimal amount) {
              _balance = amount;
          }
          public decimal GetBalance(decimal amount) {
              return _balance;
          }
      }
      
      

      在C#中,我們可以使用屬性語(yǔ)法(get 和set )來(lái)減少代碼量,下面的代碼中,Balance屬性封裝了一個(gè)私有字段_balance:

      public class BankAccount {
          decimal _balance;
          public decimal Balance {
              get {
                  return _balance;
              }
              set {
                  _balance = value;
              }
          }
      }
      
      

      get 和set都是可選的:如果不需要設(shè)置一個(gè)字段的值,那么就不需要寫(xiě)setter,getter同樣如此。但是,這后面,.Net編譯器幫我們創(chuàng)建了方法,如果使用反編譯工具如ILSpy看一下IL代碼,就會(huì)發(fā)現(xiàn)編譯器創(chuàng)建了一個(gè)decimal get_Balance()方法和一個(gè)void set_Balance(decimal)方法:

      .class public auto ansi beforefieldinit MyBankingProject.BankAccount
      	extends [mscorlib]System.Object
      {
      	.field private valuetype [mscorlib]System.Decimal _balance
      	.method public hidebysig specialname
      		instance valuetype [mscorlib]System.Decimal get_Balance ()
      			cil managed {
                    //此處省略若干IL代碼
      			}
      	.method public hidebysig specialname
      		instance void set_Balance (
      		valuetype [mscorlib]System.Decimal 'value'
      		) cil managed {
                 //此處省略若干IL代碼
      			}
      }
      
      

      自動(dòng)屬性是在C#2.0中引入的,這個(gè)工具讓語(yǔ)法糖變得更甜了,我們甚至不需要顯式創(chuàng)建字段就可以創(chuàng)建一個(gè)屬性,如下:

      public class MyClass {
          public string MyProperty {get; set;}
      }
      
      

      當(dāng)使用自動(dòng)屬性時(shí),必須同時(shí)使用get和set,但是可以使用不同的訪(fǎng)問(wèn)級(jí)別。比如,get可以設(shè)置成公共的,set可以設(shè)置成私有的。
      對(duì)于我們.Net開(kāi)發(fā)者來(lái)說(shuō),這并不是什么新鮮事兒,因?yàn)槲覀儙缀趺刻於紩?huì)使用這些,但是越是最常用的東西,通常你也認(rèn)為最理所當(dāng)然,因此,在深入涉及位置攔截的AOP代碼之前有必要重溫一下細(xì)節(jié)問(wèn)題。

      PostSharp位置攔截

      之前的教程我們知道了,AOP工具可以攔截方法,那么從上面我們又知道,屬性的底層就是方法,因此,我們可以猜想可以在屬性上使用方法攔截切面。事實(shí)上這是可行的,可以使用PostSharp或Castle DynamicProxy在屬性上創(chuàng)建方法攔截。下面就是一個(gè)使用PostSharp在屬性上創(chuàng)建方法攔截的控制臺(tái)例子:

      public class TestClass
      {
          public string TestProperty
          {
              get;
              [MyMethodAspect]
              set;//在一個(gè)屬性的setter上使用方法攔截切面
          }
      }
      
      [Serializable]
      public class MyMethodAspect:MethodInterceptionAspect
      {
          public override void OnInvoke(MethodInterceptionArgs args)
          {
              Console.WriteLine("這條語(yǔ)句來(lái)自自定義方法攔截切面");
              args.Proceed();
          }
      }
      
      class Program
      {
          static void Main(string[] args)
          {
              var test=new TestClass();
              test.TestProperty = "測(cè)試屬性";//這里會(huì)調(diào)用屬性的setter方法
      
              Console.Read();
          }
      }
      
      
      
      

      效果如下:

      但是這樣使用有幾個(gè)問(wèn)題:

      1. 笨拙。可能必須寫(xiě)兩個(gè)切面,一個(gè)給setter,一個(gè)給getter。
      2. 只能給屬性使用切面,字段的底層不是方法,所以行不通。

      沒(méi)關(guān)系,PostSharp給我們提供了一個(gè)更方便的方法,只需要寫(xiě)一個(gè)類(lèi)就可以處理getting和setting,還允許為字段和屬性編寫(xiě)切面。這就是PostSharp中的LocationInterceptionAspect,下面的例子和上面的一樣,只是這次使用了LocationInterceptionAspect:

      [Serializable]
      public class MyLocationAspect:LocationInterceptionAspect
      {
          public override void OnGetValue(LocationInterceptionArgs args)
          {
              Console.WriteLine("這條語(yǔ)句來(lái)自位置攔截的{0}方法",MethodBase.GetCurrentMethod());
              args.ProceedGetValue();
          }
      
          public override void OnSetValue(LocationInterceptionArgs args)
          {
              Console.WriteLine("這條語(yǔ)句來(lái)自位置攔截的{0}方法", MethodBase.GetCurrentMethod());
              args.ProceedSetValue();
          }
      }
      
      public class TestClass2
      {
          [MyLocationAspect]
          public string TestProperty
          {
              get;
              set;
          }
      }
      
      static void Main(string[] args)
      {
          //var test=new TestClass();
          //test.TestProperty = "測(cè)試屬性";
      
          var test2=new TestClass2();
          test2.TestProperty = "位置攔截測(cè)試";
          Console.WriteLine(test2.TestProperty);
          Console.Read();
      }
      
      
      

      Main方法中,先是給屬性賦值,所以會(huì)被MyLocationAspectOnSetValue方法攔截到,然后打印test2.TestProperty時(shí)會(huì)被OnGetValule方法攔截,因此運(yùn)行結(jié)果如下:

      這里新出現(xiàn)的args.ProceedSetValue(); args.ProceedGetValue();和之前的args.Proceed();是一樣的道理,是繼續(xù)執(zhí)行屬性方法(屬性的本質(zhì)就是方法)的意思。

      真實(shí)案例——懶加載

      懶加載的目的就是延遲一些耗時(shí)操作的執(zhí)行,相反,預(yù)加載的目的是一個(gè)或多個(gè)操作在得到結(jié)果前每次都要執(zhí)行,以防需要這些操作。NHibernate和EF都是用在持久層的數(shù)據(jù)庫(kù)工具,當(dāng)使用懶加載從DB中檢索實(shí)體時(shí),它們只會(huì)拉取你需要的實(shí)體而不會(huì)拉取相關(guān)實(shí)體,相反,使用預(yù)加載,它們就會(huì)把你需要的實(shí)體(比如A),和該實(shí)體相關(guān)的實(shí)體(B),以及和B相關(guān)的實(shí)體(C)等等都會(huì)加載出來(lái)。此時(shí),就需要在性能和方便之間進(jìn)行權(quán)衡了。

      .Net中的懶加載

      懶加載的一種方式是使用具有字段的屬性來(lái)實(shí)現(xiàn)。當(dāng)首次使用get時(shí),會(huì)創(chuàng)建一個(gè)新對(duì)象,后續(xù)再使用字段時(shí)都會(huì)像以往那樣返回字段。如下所示:

      SlowConstructor _myProperty;
      public SlowConstructor MyProperty {
          get {
              if (_myProperty == null)
      	 _myProperty = new SlowConstructor();
              return _myProperty;
          }
      }
      
      

      細(xì)心的你可能會(huì)發(fā)現(xiàn)這不是線(xiàn)程安全的代碼,如果這是一個(gè)關(guān)注點(diǎn)的話(huà),就需要放一個(gè)lock語(yǔ)句。這里使用雙重檢查的鎖機(jī)制再合適不過(guò)了,因?yàn)樵诘谝淮螜z查和lock之間可能會(huì)發(fā)生競(jìng)爭(zhēng)情況(race condition):

      readonly object _syncRoot = new object();
      SlowConstructor _myProperty;
      public SlowConstructor MyProperty {
          get {
              if (_myProperty == null) 
                lock(_syncRoot)
              	if (_myProperty == null)
                    _myProperty = new SlowConstructor();//在第一次if檢查和lock之間可能有另一個(gè)線(xiàn)程正在給字段賦值
              return _myProperty;
          }
      }
      
      

      這樣,就可以使用懶加載了。你可以像平時(shí)那樣訪(fǎng)問(wèn)屬性,如果不用它的話(huà),那么SlowConstructor永遠(yuǎn)都不會(huì)運(yùn)行。也可以使用工廠(chǎng)或者IoC工具代替new來(lái)實(shí)例化對(duì)象。但無(wú)論怎樣,lock,兩次if檢查和字段都始終是保持不變的。

      從.NET 4.0開(kāi)始,.Net Framework提供了System.Lazy<T>,它是一個(gè)方便類(lèi),可以使用更少的代碼完成和上面相同的事情。代碼如下:

      var lazy = new Lazy<SlowConstructor>(()=>new SlowConstructor());
      
      

      工廠(chǎng)代碼是以L(fǎng)ambda表達(dá)式(匿名函數(shù))傳入的,這就告訴Lazy首次訪(fǎng)問(wèn)時(shí)使用這個(gè)代碼來(lái)實(shí)例化對(duì)象,System.Lazy<T>默認(rèn)也是線(xiàn)程安全的,因此它封裝了所有的lock代碼。但是,跟前面那個(gè)例子不同的是,這樣字段就成了Lazy類(lèi)型,而不是SlowConstructor類(lèi)型,要使用SlowConstructor對(duì)象的話(huà),還要多個(gè)步驟:SlowConstructor c = MyProperty.Value;

      現(xiàn)在,想要使用懶加載時(shí)有兩種選擇,第一種有許多樣板代碼和字段,第二種使用Lazy,所有的樣板代碼是沒(méi)有了,但是必須通過(guò)Value屬性來(lái)獲得懶加載對(duì)象。下面使用AOP來(lái)結(jié)合一下這兩種方法的優(yōu)點(diǎn)。

      使用AOP實(shí)現(xiàn)懶加載

      結(jié)合上面兩種方法的優(yōu)點(diǎn),那就是可以直接訪(fǎng)問(wèn)屬性(不需要通過(guò)Value屬性),而且也沒(méi)有很多的樣板代碼,就像下面這個(gè)樣子:

      [LazyLoadGetter]//使用特性告訴PostSharp這個(gè)屬性是懶加載屬性
      static SlowConstructor MyProperty {
      	get { return(new SlowConstructor() ); }
      }
      
      

      get方法體內(nèi)包含了懶加載的工廠(chǎng),直到get執(zhí)行時(shí)才會(huì)調(diào)用,后續(xù)的get調(diào)用也會(huì)使用首次操作的結(jié)果。
      下面?zhèn)鲃?chuàng)建一個(gè)控制臺(tái)應(yīng)用,命名為LazyLoadingDemo,安裝PostSharp。定義一個(gè)模擬耗時(shí)的操作SlowConstructor(比如一個(gè)調(diào)用了一個(gè)很慢的web service等):

      public class SlowConstructor
      {
          public SlowConstructor()
          {
              Console.WriteLine("正在初始化SlowConstructor,請(qǐng)稍等...");
              Thread.Sleep(5000);//睡5秒,模擬耗時(shí)操作
          }
      
          public void DoSomething()
          {
              Console.WriteLine("{0}:正在處理一些業(yè)務(wù)...",DateTime.Now);
          }
      }
      
      

      在Main方法種定義該類(lèi)的一個(gè)屬性,并連續(xù)調(diào)用該類(lèi)的DoSomething方法:

      class Program
      {
          static SlowConstructor SlowService
          {
              get { return new SlowConstructor();}
          }
          static void Main(string[] args)
          {
              SlowService.DoSomething();
              SlowService.DoSomething();
      
              Console.Read();
          }
      }
      
      
      

      這樣寫(xiě)代碼的話(huà)就應(yīng)該優(yōu)化了,因?yàn)槊看握{(diào)用屬性的get方法時(shí)都會(huì)重新實(shí)例化SlowConstructor對(duì)象。
      執(zhí)行結(jié)果很明顯,如下:

      但我們這里計(jì)劃的是懶加載這個(gè)屬性,不需要添加所有的字段、雙重檢查鎖,或切換使用Lazy<T>,這里我們可以創(chuàng)建一個(gè)切面,該切面繼承PostSharp的LocationInterceptionAspect,然后把自定義的切面當(dāng)作特性用在需要懶加載的屬性上即可:

      [Serializable]
      public class MyLazyLoadingGetterAspect : LocationInterceptionAspect
      {
          private object _backingField;
          readonly object _syncRoot = new object();
          public override void OnGetValue(LocationInterceptionArgs args)
          {
              if (_backingField == null)
              {
                  lock (_syncRoot)
                  {
                      if (_backingField == null)
                      {
                          args.ProceedGetValue();//繼續(xù)像往常那樣執(zhí)行g(shù)et
                          _backingField = args.Value;//將get獲得的屬性值保存到支持字段中
                      }
                  }
                  args.Value = _backingField;//因?yàn)橹С肿侄沃幸呀?jīng)有值了,直接賦值即可
              }
      
          }
      }
      
      
      

      雖然切面中的代碼和之前的原始代碼很類(lèi)似,但這是在切面里面,切面可以用在很多不同的地方,在需要使用的地方只需要像特性那樣使用就可以了,很方便。
      首次調(diào)用get時(shí),OnGetValue會(huì)被調(diào)用,一開(kāi)始支持字段_backingField=null,因此需要像之前那樣加鎖并雙重檢查,然后args.ProceedGetValue()告訴PostSharp繼續(xù)運(yùn)行g(shù)et中的代碼,這時(shí),就會(huì)創(chuàng)建一個(gè)SlowConstructor的實(shí)例,然后,就會(huì)使用get執(zhí)行的結(jié)果填充args.Value。然后我們把該值存入支持字段中,方便下次使用。
      之后,每個(gè)后續(xù)調(diào)用,PostSharp都會(huì)將支持字段的值設(shè)置給args.Value,因此args.ProceedGetValue()只會(huì)在首次調(diào)用,這樣,就不需要每次都實(shí)例化類(lèi)了。有了這個(gè)切面,我們就有了和Lazy<T>類(lèi)似的語(yǔ)法了,而且可以直接訪(fǎng)問(wèn)屬性。

      直接在需要懶加載的屬性上以特性的方式使用:

      [MyLazyLoadingGetterAspect]
      static SlowConstructor SlowService
      {
          get { return new SlowConstructor();}
      }
      
      
      

      執(zhí)行結(jié)果如下:

      從上面的運(yùn)行結(jié)果可以看出,只創(chuàng)建了1個(gè)實(shí)例,因而,大大提高了性能。
      我們都知道,字段沒(méi)有get,因此對(duì)字段進(jìn)行懶加載稍微有點(diǎn)不同。

      如何懶加載字段?

      使用反射的Activator

      字段是類(lèi)級(jí)別的變量,這就意味著我們不能找到一種方法顯式指定應(yīng)該如何懶加載一個(gè)字段。假設(shè)我們以隱式的方式懶加載指定的字段,首先,編寫(xiě)代碼如下,這次用的不是屬性,而是字段:

      #region 2.0 懶加載字段
      
      private static SlowConstructor SlowService;
      #endregion
      static void Main(string[] args)
      {
          SlowService.DoSomething();
          SlowService.DoSomething();
      
          Console.Read();
      }
      
      
      

      最簡(jiǎn)單的做法就是使用反射的Activator創(chuàng)建字段類(lèi)型的實(shí)例,下面我們創(chuàng)建一個(gè)繼承了LocationInterceptionAspect的切面,然后用于該字段:

      #region 2.0 懶加載字段
      [MyLazyLoadingFieldAspect]
      private static SlowConstructor SlowService;
      #endregion
      static void Main(string[] args)
      {
          SlowService.DoSomething();
          SlowService.DoSomething();
      
          Console.Read();
      }
      
      [Serializable]
      public sealed class MyLazyLoadingFieldAspect : LocationInterceptionAspect
      {
          private object _backingField;
          readonly object _syncRoot=new object();
          public override void OnGetValue(LocationInterceptionArgs args)
          {
              if (_backingField==null)
              {
                  lock (_syncRoot)
                  {
                      if (_backingField==null)
                      {
                          _backingField = Activator.CreateInstance(args.Location.LocationType);//Activator會(huì)使用位置的類(lèi)型創(chuàng)建一個(gè)新對(duì)象
                      }
                  }
              }
              args.Value = _backingField;
          }
      
      }
      
      

      反射之Activator

      反射是位于System.Reflection命名空間下的一系列工具,它允許我們編寫(xiě)一些在程序運(yùn)行時(shí)進(jìn)行讀取或者生成代碼的代碼。Activator可以創(chuàng)建運(yùn)行時(shí)中對(duì)象的新實(shí)例,這在直到運(yùn)行時(shí)才知道該實(shí)例化哪種類(lèi)型的對(duì)象時(shí)很有用。上面的切面可以在任何類(lèi)型的字段上重復(fù)使用,但是這種靈活性也帶來(lái)了性能損耗,因此,確保必要的時(shí)候才使用反射。

      上面的代碼和之前懶加載屬性切面的代碼很相似,但是我們這里應(yīng)該注意的是不同點(diǎn),比如,這里沒(méi)有使用args.ProceedGetValue(),而是使用了Activator.CreateInstance()。PostSharp的args.Location.LocationType可以告訴我們被攔截位置的類(lèi)型Type(字段和屬性都可以),有了這個(gè)信息,我們就可以使用System.Activator創(chuàng)建那個(gè)類(lèi)型的實(shí)例了。和之前一樣,將結(jié)果存到支持字段_backingField中。

      這種方法適用面很窄,更加現(xiàn)實(shí)的方式是使用工廠(chǎng),服務(wù)定位器或者IoC容器取代Activator。比如,如果使用的是StructureMap(一個(gè)流行的.Net IoC工具),那么可以使用ObjectFactory.GetInstance代替Activator,這種方法可以讓我們對(duì)更復(fù)雜的依賴(lài)(即,沒(méi)有無(wú)參構(gòu)造函數(shù)的類(lèi))使用懶加載。

      使用IoC工具

      假設(shè)SlowConstructor只有一個(gè)構(gòu)造函數(shù),并且該構(gòu)造函數(shù)有一個(gè)IMyService參數(shù),修改之后的代碼如下:

      public class SlowConstructor
      {
          //public SlowConstructor()
          //{
          //    Console.WriteLine("正在初始化SlowConstructor,請(qǐng)稍等...");
          //    Thread.Sleep(5000);
          //}
          private IMyService _myService;
          public SlowConstructor(IMyService myService)//只有一個(gè)構(gòu)造函數(shù),并且需要一個(gè)參數(shù)
          {
              _myService = myService;
              Console.WriteLine("正在初始化SlowConstructor,請(qǐng)稍等...");
              Thread.Sleep(5000);
          }
          //public void DoSomething()
          //{
          //    Console.WriteLine("{0}:正在處理一些業(yè)務(wù)...",DateTime.Now);
          //}
      
          public void DoSomething()
          {
              _myService.DoSomething();
          }
      }
      
      public interface IMyService
      {
          void DoSomething();
      }
      
      public class MyService:IMyService
      {
          public void DoSomething()
          {
              Console.WriteLine("{0}:正在處理一些業(yè)務(wù)...", DateTime.Now);
          }
      }
      
      
      

      在切面中,仍然可以使用Activator創(chuàng)建對(duì)象,但是同時(shí)必須創(chuàng)建該對(duì)象依賴(lài)的對(duì)象,在上面的代碼中就是MySevice,在一個(gè)真實(shí)應(yīng)用中,依賴(lài)鏈可能會(huì)更長(zhǎng)或更復(fù)雜,因此,一般都會(huì)把這個(gè)任務(wù)交給一個(gè)工具,比如StructureMap。下面的代碼是如何在控制臺(tái)的Main方法中初始化StructureMap,其它的IoC工具都是類(lèi)似的【下一個(gè)系列教程就是關(guān)于DI/IoC的】:

      #region 2.0 懶加載字段
      //[MyLazyLoadingFieldAspect]
      [LazyLoadStructureMapAspect]
      private static SlowConstructor SlowService;
      #endregion
      static void Main(string[] args)
      {
          //ObjectFactory.Initialize告訴StructureMap使用哪個(gè)實(shí)現(xiàn)
          ObjectFactory.Initialize(cfg =>
          {
              cfg.For<IMyService>().Use<MyService>();//當(dāng)調(diào)用IMyService的構(gòu)造函數(shù)時(shí),使用MyService作為實(shí)現(xiàn)
              cfg.For<SlowConstructor>().Use<SlowConstructor>();//這行代碼可選,StructureMap會(huì)自動(dòng)綁定
          });
          SlowService.DoSomething();
          SlowService.DoSomething();
      
          Console.Read();
      }
      
      
      

      現(xiàn)在依賴(lài)配置好了,并且給字段添加了新的特性切面。這里簡(jiǎn)單介紹一下StructureMap的依賴(lài)配置,下一個(gè)系列教程會(huì)詳細(xì)講解哦!首先使用ObjectFactory.Initialize【已經(jīng)過(guò)時(shí)了,在新版本已經(jīng)不建議使用這種方式】指定依賴(lài),如果StructureMap請(qǐng)求IMyService的實(shí)現(xiàn),那么就會(huì)返回MyService對(duì)象,如果請(qǐng)求的是SlowConstructor,那么就會(huì)使用SlowConstructor。更重要的是,當(dāng)創(chuàng)建SlowConstructor的實(shí)例時(shí),它會(huì)識(shí)別出SlowConstructor的構(gòu)造函數(shù)有一個(gè)IMyService類(lèi)型的參數(shù),因此會(huì)自動(dòng)使用配置的依賴(lài)并傳給該構(gòu)造函數(shù)MyService的新實(shí)例。

      下面我們需要?jiǎng)?chuàng)建一個(gè)新切面,和之前使用Activator的例子看起來(lái)很像,但是這次使用了ObjectFactory.GetInstance而不是Activator,這樣StructureMap會(huì)自動(dòng)提供需要的對(duì)象:

      [Serializable]
      public class LazyLoadStructureMapAspect:LocationInterceptionAspect
      {
          private object _backingField;
          readonly object _syncRoot=new object();
          public override void OnGetValue(LocationInterceptionArgs args)
          {
              if (_backingField==null)
              {
                  lock (_syncRoot)
                  {
                      if (_backingField==null)
                      {
                          var locationType = args.Location.PropertyInfo.PropertyType;
                          _backingField= ObjectFactory.GetInstance(locationType);
                      }
      
                  }
              }
              args.Value = _backingField;
          }
      }
      
      
      

      執(zhí)行結(jié)果和之前的一樣,只不過(guò)這次的例子更加具有現(xiàn)實(shí)意義,因?yàn)轭?lèi)有關(guān)于接口的依賴(lài),配置這些依賴(lài)使用了IoC工具。

      適當(dāng)?shù)氖褂脩屑虞d可以改善耗時(shí)操作的性能,AOP通過(guò)攔截訪(fǎng)問(wèn)的字段和屬性以及將樣板代碼移到單獨(dú)的切面類(lèi)中使得對(duì)位置進(jìn)行懶加載不再那么痛苦。字段或?qū)傩钥赡軙?huì)遇到樣板代碼問(wèn)題的其它地方在可響應(yīng)的GUI。

      真實(shí)案例——INotifyPropertyChanged

      在桌面應(yīng)用中使用INotifyPropertyChanged

      首先創(chuàng)建一個(gè)WPF應(yīng)用,搭建的界面如下圖所示:

      需求是,當(dāng)輸入進(jìn)行輸入時(shí),需要將姓和名兩個(gè)文本框中的內(nèi)容連接起來(lái)填充到姓名那行所在的Label控件上。在WPF中一種普遍的做法是創(chuàng)建一個(gè)封裝數(shù)據(jù)(姓和名)和導(dǎo)出數(shù)據(jù)(姓名)的視圖模型。創(chuàng)建視圖模型NameViewModel如下:

      public class NameViewModel
      {
          public string FirstName { get; set; }
          public string LastName { get; set; }
      
          public string FullName
          {
              get { return string.Format("{0}{1}", FirstName, LastName); }
          }
      }
      
      
      

      還需要做以下幾步才能實(shí)現(xiàn)需求:

      1. 將該視圖模型的一個(gè)實(shí)例綁定到WPF窗體的數(shù)據(jù)上下文DataContext上,這可以在xaml的代碼后置類(lèi)中對(duì)窗體的DataContext屬性賦值;
      2. 將該視圖模型的每個(gè)屬性分別綁定到各自的文本框和Label控件上,這可以通過(guò)分別將視圖模型的屬性綁定TextBox和Label控件的TextContent屬性上完成;
      3. 告訴兩個(gè)文本框,無(wú)論何時(shí)文本框內(nèi)容變化時(shí),都應(yīng)該觸發(fā)一個(gè)更新,因此,修改姓和名所對(duì)應(yīng)的文本框,指定Binding中的UpdateSourceTrigerPropertyChanged,目的是當(dāng)用戶(hù)輸入時(shí),告訴它們更新源數(shù)據(jù)。
      4. 讓視圖模型類(lèi)NameViewModel實(shí)現(xiàn)INotifyPropertyChanged,實(shí)現(xiàn)這個(gè)接口需要做的唯一一件事就是PropertyChangedEventHandler類(lèi)型的事件。由于已經(jīng)將綁定添加到了文本框上,WPF會(huì)自動(dòng)尋找要觸發(fā)的事件,這意味著我們必須在每個(gè)屬性的setter方法中編碼來(lái)觸發(fā)該屬性名所對(duì)應(yīng)的事件。

      第一步:

      public partial class MainWindow : Window
      {
          public MainWindow()
          {
              InitializeComponent();
              DataContext=new NameViewModel();
          }
      }
      
      
      

      第二步,第三步:

      <StackPanel Orientation="Horizontal">
                  <Label Content="姓:" Width="100"/>
                  <TextBox Height="23" Width="200" Text="{Binding Path=FirstName,UpdateSourceTrigger=PropertyChanged}" />
              </StackPanel>
      <StackPanel Orientation="Horizontal">
                  <Label Content="名:" Width="100"/>
                  <TextBox Height="23" Width="200" Text="{Binding Path=LastName,UpdateSourceTrigger=PropertyChanged}" />
              </StackPanel>
      
      
      

      第四步:

      public class NameViewModel:INotifyPropertyChanged
      {
          public event PropertyChangedEventHandler PropertyChanged;
      
          void OnPropertyChanged(string propertyName)
              {
                  if (PropertyChanged!=null)
                  {
                      PropertyChanged(this,new PropertyChangedEventArgs(propertyName));
                  }
              }
      
          private string _firstName;
      
          public string FirstName
              {
                  get { return _firstName; }
                  set
                  {
                      if (value!=_firstName)
                      {
                          _firstName = value;
                          OnPropertyChanged("FirstName");
                          OnPropertyChanged("FullName");
                      }
                  }
              }
      
          private string _lastName;
      
          public string LastName
              {
                  get { return _lastName; }
                  set
                  {
                      if (value!=_lastName)
                      {
                          _lastName = value;
                          OnPropertyChanged("LastName");
                          OnPropertyChanged("FullName");
                      }
                  }
              }
      
          public string FullName
          {
              get { return string.Format("{0}{1}", FirstName, LastName); }
          }
      
         
      }
      
      
      

      如果你對(duì)WPF熟悉的話(huà),那么上面的代碼沒(méi)什么可說(shuō)的:無(wú)論何時(shí)在這些屬性上使用了set,PropertyChanged事件都會(huì)被觸發(fā)。比如,在姓的文本框上輸入了A,那么就會(huì)導(dǎo)致FirstName的屬性值被設(shè)置set。在set期間,觸發(fā)了兩次PropertyChanged:一次是宣布FirstName屬性修改了,然后是宣布FullName屬性修改了。

      編譯、運(yùn)行程序,結(jié)果如下:

      雖然這個(gè)例子不是很復(fù)雜,但是在真實(shí)的WPF應(yīng)用中,可能會(huì)有更多的字段以及這些字段之間關(guān)系更復(fù)雜,如果熟悉MVVM(Model-View-ViewModel)模式的話(huà),那么這種類(lèi)型的綁定對(duì)于實(shí)現(xiàn)那種模式很重要。此外,雖然這只是一個(gè)簡(jiǎn)單的示例,但是NameViewModel從一個(gè)只有自動(dòng)屬性的小類(lèi)變得越來(lái)越大,代碼越來(lái)越多,有了支持字段,而且每個(gè)set方法中還要邏輯。雖然可以在View和ViewModel之間進(jìn)行干凈的分離,但是使用INotifyPropertyChanged會(huì)面臨很多陷阱和問(wèn)題。

      使用INotifyPropertyChanged的問(wèn)題和約束

      雖然使用INotifyPropertyChanged有很多好處,但是也有很多弊端,比如,潛在產(chǎn)生了樣板代碼,脆弱的代碼以及可能維護(hù)起來(lái)困難的代碼。
      產(chǎn)生的樣板代碼很明顯,因?yàn)閺淖钤嫉闹挥腥齻€(gè)自動(dòng)屬性的NameViewModel類(lèi)現(xiàn)在體積已經(jīng)膨脹了好幾倍,有了顯式的支持字段,setter里面也有了邏輯,因此,這里有很多重復(fù),可以使用AOP減少重復(fù)。
      其次,要觸發(fā)PropertyChanged事件,就需要有一個(gè)PropertyChangedEventArgs對(duì)象,它需要一個(gè)字符串來(lái)識(shí)別已經(jīng)改變的屬性。因此,當(dāng)每次調(diào)用OnPropertyChanged時(shí),需要傳一個(gè)和屬性名稱(chēng)對(duì)應(yīng)的字符串,如果不小心手誤輸錯(cuò)了,就會(huì)導(dǎo)致觸發(fā)事件失敗。
      最后,使用INotifyPropertyChanged很難維護(hù)。因?yàn)樗褂昧俗址绻牧藢傩悦捅仨氂浀靡惨薷淖址ò惭b了ReSharp等重構(gòu)工具時(shí),如果重命名屬性,ReSharp可以幫我們完成這件事)。還要注意,因?yàn)槲覀冇幸粋€(gè)導(dǎo)出屬性(FullName),所以要記得當(dāng)發(fā)送關(guān)于其它屬性更改的消息時(shí)要包括該屬性。

      使用ReSharp重構(gòu)
      雖然屬性名FirstName和字符串“FirstName”對(duì)于我們?nèi)祟?lèi)來(lái)說(shuō)看起來(lái)是相同的,但是對(duì)于編譯器它們是不同的符號(hào),如果更改了一個(gè)符號(hào),編譯器不會(huì)聰明到也能意識(shí)到其它相關(guān)的符號(hào),當(dāng)運(yùn)行代碼時(shí)最終會(huì)報(bào)錯(cuò)。
      一些重構(gòu)工具比如ReSharp,Telerik JustCode等都會(huì)嘗試使用智能分析和演繹找出相關(guān)的符號(hào)。比如,當(dāng)使用ReSharp重命名FirstName屬性時(shí),它可能會(huì)詢(xún)問(wèn)你是否想要更改“FirstName”字符串的值。

      不使用AOP也可以緩解這些問(wèn)題,比如可以寫(xiě)單元測(cè)試或者防御性編程,它們可以驗(yàn)證所有將會(huì)發(fā)送的正確通知。雖然可以使用發(fā)射使得這件事簡(jiǎn)單些,但是會(huì)潛在產(chǎn)生很多代碼。(比如可以使用反射可以循環(huán)遍歷類(lèi)的所有屬性,獲得屬性名稱(chēng),進(jìn)而確保當(dāng)事件觸發(fā)時(shí)屬性名稱(chēng)時(shí)匹配的)。

      如果使用了.NET 4.5,那么可以使用一個(gè)叫做CallMemberName的新工具來(lái)處理INotifyPropertyChanged,CallMemberName是一個(gè)特性,可以使用它將一個(gè)參數(shù)設(shè)置成調(diào)用的屬性的名稱(chēng)。這里我們可以使用它來(lái)減少NameViewModel類(lèi)中字符串的依賴(lài),代碼如下:

      public class NameViewModel2:INotifyPropertyChanged
      {
          public event PropertyChangedEventHandler PropertyChanged;
      
        
      
          void OnPropertyChanged([CallerMemberName]string propertyName="")
              {
                  if (PropertyChanged!=null)
                  {
                      PropertyChanged(this,new PropertyChangedEventArgs(propertyName));
                  }
              }
      
          string _firstName;
          public string FirstName
              {
                  get { return _firstName; }
                  set {
                      if (value!=_firstName)
                      {
                          _firstName = value;
                          OnPropertyChanged();//這里就可以移除“FirstName”了
                          OnPropertyChanged("FullName");
                      }
                  }
              }
          string _lastName;
          public string LastName
              {
                  get { return _lastName; }
                  set
                  {
                      if (value != _firstName)
                      {
                          _lastName = value;
                          OnPropertyChanged();//這里就可以移除“LastName”了
                          OnPropertyChanged("FullName");
                      }
                  }
              }
          public string FullName
              {
                  get
                  {
                      return string.Format("{0} {1}", _firstName, _lastName);
                  }
              }
      }
      
      
      

      這是一種改善,重命名屬性FirstName和LastName不再是問(wèn)題了,因?yàn)?Net Framework會(huì)幫我們填充空白。拼寫(xiě)失誤也不是問(wèn)題了,因?yàn)镃allMemberName只會(huì)使用屬性的名稱(chēng),但是還必須通知FullName更改了,因?yàn)樗且粋€(gè)導(dǎo)出屬性。而且仍然有很多樣板代碼,包括顯式支持字段和許多setter中的代碼。
      我們可以使用AOP來(lái)處理這些問(wèn)題,下面我們就使用一個(gè)新的AOP工具來(lái)協(xié)助處理INotifyPropertyChanged。

      使用AOP減少樣板代碼

      之前已經(jīng)使用PostSharp和Castle Dynamic這兩個(gè)AOP框架演示了很多例子,現(xiàn)在再引入一個(gè)新的框架,這個(gè)框架之前叫INotifyPropertyWeaver,是專(zhuān)為INotifyPropertyChanged而生的,然而,現(xiàn)在這個(gè)框架已經(jīng)棄用了,在網(wǎng)上基本找不到關(guān)于它的消息了,然而,他的作者將它集成到了Fody項(xiàng)目中,而且它現(xiàn)在的名字叫PropertyChanged.Fody,安裝時(shí),直接在Nuget控制臺(tái)輸入Install-Package PropertyChanged.Fody即可。
      安裝好之后,我們只需要定義一個(gè)原始的NameViewModel類(lèi):

      public class NameViewModel
      {
          public string FirstName { get; set; }
          public string LastName { get; set; }
      
          public string FullName
          {
              get
              {
                  return String.Format("{0}{1}", FirstName, LastName);
              }
          }
      }
      
      
      

      然后,神奇的地方來(lái)了,只需要在這個(gè)類(lèi)的上方添加一個(gè)特性ImplementPropertyChanged即可,當(dāng)然前面的數(shù)據(jù)綁定的步驟還是要做的:

      [ImplementPropertyChanged]
      public class NameViewModel
      {
      
      

      運(yùn)行程序,如下:

      簡(jiǎn)直太方便了,只需要加一個(gè)特性就把之前要寫(xiě)那么多代碼的事情完成了。事實(shí)上,這個(gè)特性做的就是這件事,代碼編譯之后的效果和我們寫(xiě)那么多代碼是一樣的。

      當(dāng)然,這個(gè)工具里面還要很多其它特性,感興趣的同學(xué)可以去Github上去學(xué)習(xí),鏈接在此:https://github.com/Fody/PropertyChanged/wiki。

      PropertyChanged.Fody幫我們填充了所有的間隙,它很聰明,可以探測(cè)出導(dǎo)出屬性,從而填充通知間隙。PostSharp也以PostSharp Ultimate的形式提供了類(lèi)似的功能。

      PostSharp Ultimate
      PostSharp Ultimate收集了很多現(xiàn)成的開(kāi)源切面,包括的現(xiàn)成切面如下:

      • 多線(xiàn)程
      • 診斷(logging)
      • 有限的INotifyPropertyChanged切面
        這些工具都是免費(fèi)的,但是它們要求PostSharp是全商業(yè)版本。這些工具的優(yōu)勢(shì)是可以獲得AOP的所有優(yōu)勢(shì)來(lái)解決特定的問(wèn)題,不需要從零開(kāi)始編寫(xiě)切面。當(dāng)然,我們也可以使用開(kāi)源免費(fèi)的Fody代替。

      接下來(lái)我們使用PostSharp來(lái)實(shí)現(xiàn)和上面使用ProperyChanged.Fody相同的功能。先安裝Postsharp,再創(chuàng)建一個(gè)叫做NotifyPropertyChangedAspect切面類(lèi),它繼承于LocationIntercetionAspect:

      [Serializable]
      public class NotifyPropertyChangedAspect:LocationInterceptionAspect
      {
          private string[] _derivedProperties;
          public NotifyPropertyChangedAspect(params string[] derived)//構(gòu)造函數(shù)參數(shù)為可變長(zhǎng)參數(shù),用于接收導(dǎo)出屬性
          {
              _derivedProperties = derived;
          }
          public override void OnSetValue(LocationInterceptionArgs args)
          {
              //base.OnSetValue(args);
          }
      }
      
      
      

      回憶一下,之前我們一開(kāi)始實(shí)現(xiàn)INotifyPropertyChanged時(shí),困難的工作都放到setter中了,因此,這里需要重寫(xiě)OnSetValue方法,這個(gè)方法會(huì)在使用屬性的setter時(shí)運(yùn)行,而且它會(huì)代替setter運(yùn)行。

      在OnSetValue里面,需要做2件事:

      1. 比較新值和舊值,如果不等,那么應(yīng)該允許set操作通過(guò)(使用PostSharp的args.ProceedSetValue方法);
      2. 需要通知已經(jīng)改變的屬性(包括構(gòu)造函數(shù)中指定的任何導(dǎo)出屬性)。創(chuàng)建可以重復(fù)使用的RaisePropertyChanged方法,對(duì)指定的所有導(dǎo)出屬性進(jìn)行循環(huán)遍歷,并調(diào)用RaisePropertyChanged方法。代碼如下:
      [Serializable]
      public class NotifyPropertyChangedAspect:LocationInterceptionAspect
      {
          private string[] _derivedProperties;
          public NotifyPropertyChangedAspect(params string[] derived)//構(gòu)造函數(shù)參數(shù)為可變長(zhǎng)參數(shù),用于接收導(dǎo)出屬性
          {
              _derivedProperties = derived;
          }
          public override void OnSetValue(LocationInterceptionArgs args)
          {
              var oldValue = args.GetCurrentValue();
              var newValue = args.Value;
              if (oldValue!=newValue)
              {
                  args.ProceedSetValue();
                  RaisePropertyChanged(args.Instance, args.LocationName);//只要屬性執(zhí)行了setter,就觸發(fā)RaisePropertyChanged事件
                  if (_derivedProperties!=null)
                  {
                      //對(duì)每個(gè)導(dǎo)出屬性觸發(fā)事件
                      foreach (string derivedProperty in _derivedProperties)
                      {
                          RaisePropertyChanged(args.Instance,derivedProperty);
                      }
                  }
              }
          }
      
          private void RaisePropertyChanged(object p1, string p2)
          {
              throw new NotImplementedException();
          }
      }
      
      
      

      RaisePropertyChanged待會(huì)再實(shí)現(xiàn)。先來(lái)學(xué)習(xí)一下之前沒(méi)有碰到過(guò)的PostSharp的API。args.GetCurrentValue獲取當(dāng)前的位置值,但是它還沒(méi)有把值value放到args.Value。因此,這里把它存儲(chǔ)在oldValue變量中再合適不過(guò)了。args.Value返回即將到來(lái)的位置值。args.ProceedSetValue指示PostSharp允許繼續(xù)set操作。
      如果屬性值發(fā)生了變化,那么我們就觸發(fā)屬性改變的事件。看一下傳入的實(shí)參,args.Instance返回的是屬性所在的對(duì)象(比如,NameViewModel類(lèi)的實(shí)例),它應(yīng)該是一個(gè)實(shí)現(xiàn)了INotifyPropertyChanged的類(lèi)。args.LocationName返回被攔截的屬性名,比如可能是FirstName或LastName。

      當(dāng)屬性更改的通知發(fā)出之后,遍歷所有指定的導(dǎo)出屬性(如FullName),并為這些屬性調(diào)用RaisePropertyChanged方法。下面我們完成最后這個(gè)切面并寫(xiě)完RaisePropertyChanged方法。在該方法中,你期望找到傳入的實(shí)例對(duì)象上的PropertyChanged事件,并使用傳入的位置名觸發(fā)那個(gè)事件。然而,只有純粹的一個(gè)對(duì)象object傳入,所以必須借助反射來(lái)處理:

      
      private void RaisePropertyChanged(object instance, string propertyName)
      {
          var type = instance.GetType();
          var propertyChanged = type.GetField("PropertyChanged", BindingFlags.Instance|BindingFlags.NonPublic);
          var handler = propertyChanged.GetValue(instance) as PropertyChangedEventHandler;
          handler(instance,new PropertyChangedEventArgs(propertyName));
      }
      
      
      

      這個(gè)方法中沒(méi)使用任何PostSharp API,只有反射的API。反射會(huì)檢索實(shí)例instance的類(lèi)型,從該類(lèi)型中可以找到PropertyChanged事件字段。使用那個(gè)字段可以調(diào)用事件。

      這里使用發(fā)射,是因?yàn)閺念?lèi)外面觸發(fā)事件的唯一方式就是反射了。這樣做并不好,因?yàn)榉瓷涫且粋€(gè)緩慢的過(guò)程,這樣編寫(xiě)切面的話(huà)意味著屬性每次改變時(shí)都會(huì)執(zhí)行反射。此外,如果這個(gè)切面用在一個(gè)沒(méi)有PropertyChanged事件的類(lèi)上,那么就會(huì)報(bào)錯(cuò)。(解決辦法請(qǐng)關(guān)注后面的教程,特別是PostSharp的CompileTimeValidate功能)

      小結(jié)

      這節(jié)我們覆蓋了一個(gè)新的攔截類(lèi)型:攔截屬性和字段(位置)。和攔截方法一樣,位置攔截切面扮演著getter/setter和處理getting/setting代碼之間的中間人。

      C#中的屬性提供了簡(jiǎn)明的方式編寫(xiě)getter/setter方法,可以攔截方法的工具也可以攔截屬性(比如Castle DynamicProxy)。但PostSharp為位置提供了一個(gè)特殊的類(lèi),該API可以同時(shí)為屬性和字段服務(wù)。和方法攔截一樣,我們可以繼續(xù)執(zhí)行g(shù)et/setca操作,也可以獲得關(guān)于位置的信息(比如字段名和屬性名),實(shí)例對(duì)象等等。

      這節(jié)也引入了一個(gè)新的AOP工具——PropertyChanged.Fody,這個(gè)工具很專(zhuān)一,只做一件事,不像PostSharp和Castle DynamicProxy是通用框架。

      現(xiàn)在,我們已經(jīng)可以編寫(xiě)攔截方法、邊界方法、攔截位置的切面了。但是學(xué)習(xí)AOP不僅僅是數(shù)量(可以少寫(xiě)代碼),而且還有質(zhì)量。下一篇我們看下如何將單元測(cè)試和切面結(jié)合起來(lái)。

      posted @ 2016-08-30 10:02  tkbSimplest  閱讀(11484)  評(píng)論(28)    收藏  舉報(bào)
      主站蜘蛛池模板: 国产精品一区二区国产主播| 狠狠色婷婷久久综合频道日韩| 日韩精品二区三区四区| 亚洲成人午夜排名成人午夜 | 激情国产一区二区三区四区| 国产精品一级久久黄色片| 午夜激情福利一区二区| 97人洗澡人人澡人人爽人人模| 青青草国产精品日韩欧美| 久久国内精品一区二区三区| 精品亚洲国产成人av| 欧洲中文字幕一区二区| 亚洲日本中文字幕乱码中文 | 日韩免费无码一区二区三区| 国偷自产视频一区二区久| 色婷婷五月综合久久| 国产99久久精品一区二区| 香蕉eeww99国产在线观看| 国产精品午夜精品福利| 国产成人a∨激情视频厨房| 精品人妻日韩中文字幕| 精品无码人妻| 日韩精品久久久肉伦网站| 国产毛片子一区二区三区| 99九九成人免费视频精品| 国产精品中文字幕久久| 青草99在线免费观看| 日韩av综合免费在线| 美女内射毛片在线看免费人动物| 99久久无码一区人妻a黑| 18禁在线一区二区三区| 国产日韩精品视频无码| 蜜臀av黑人亚洲精品| 久草热大美女黄色片免费看 | 久久这里都是精品二| 日韩精品中文女同在线播放| 日本成熟少妇喷浆视频| 宅男噜噜噜66在线观看| 亚洲成亚洲成网| 日韩中文免费一区二区| 不卡国产一区二区三区|