1. 程式人生 > >使用InternalsVisibleToAttribute給assembly添加“友元assembly”特性遭遇"強簽名"

使用InternalsVisibleToAttribute給assembly添加“友元assembly”特性遭遇"強簽名"

hive code over height 末尾 文件 用例 == syn

一、如何讓Intenal成員暴露給另一個程序集

我們知道Modifier為Internal的類型成員僅限於當前程序集能夠訪問,但是在某些情況下,我們希望將它們暴露給另一個程序集。比較典型的應用場景包括如下兩種:

  • 將一個組件或者模塊定義成兩個或者兩個以上程序集,一個程序集需要訪問另一個程序集的Internal成員。比如將一個Logging組件定義成三個程序集:Logging.dll、Logging.Client.dll和Logging.Server.dll。其中後兩個分別用於客戶端和服務端的日誌記錄,而它們共同依賴的功能定義在Logging.dll中。定義在Logging.dll的API以共有成員的形式公布出來,而一些僅僅需要被Logging.Client.dll和Logging.Server.dll使用的API在定義成Interna成員。
  • 對一個組件或者模塊進行單元測試時候,單元測試用例需要調用定義在被測試組件或者模塊的Internal成員

舉個例子,如下圖所示:

技術分享圖片

我將某個組件定義在Lib項目中,而Test是與之對應的單元測試項目。定義在Lib中組建成員的可見性依賴於具體的設計,但是在很多情況下,單元測試用例為了盡可能覆蓋較多的分支,需要調用一些Internal成員。比如,設置一些Internal屬性,或者調用一些Internal方法。

我在Lib中定義了如下一個表示二維向量的Vector類,其中X和Y屬性的Set方法為Internal

   1: public class Vector
   2:
{
   3:     private static void EnsureNotNull(object value, string parameterName)
   4:     {
   5:         if (null == value)
   6:         {
   7:             throw new ArgumentNullException(parameterName);
   8:         }
   9:     }
  10:     public double X { get; internal
set; }
  11:     public double Y { get; internal set; }
  12:  
  13:     public Vector(double x, double y)
  14:     {
  15:         this.X = x;
  16:         this.Y = y;
  17:     }
  18:  
  19:     public override bool Equals(object obj)
  20:     {
  21:         Vector vector = obj as Vector;
  22:         if (null == vector)
  23:         {
  24:             return false;
  25:         }
  26:  
  27:         return this.X == vector.X && this.Y == vector.Y;
  28:     }
  29:  
  30:     public static Vector operator + (Vector v1, Vector v2)
  31:     {
  32:         EnsureNotNull(v1, "v1");
  33:         EnsureNotNull(v2, "v2");
  34:         return new Vector(v1.X + v2.X, v1.Y+ v2.Y);
  35:     }
  36:  
  37:     public override int GetHashCode()
  38:     {
  39:         return this.X.GetHashCode() ^ this.Y.GetHashCode();
  40:     }
  41: }

在單元測試項目Test中,定義如下一個VectorFixture類型,用於測試向量相加的邏輯。為了測試方便,我在這裏希望直接設置Vector的X和Y屬性,而這兩個屬性的Set方式是Internal的

   1: [TestClass()]
   2: public class VectorFixture
   3: {
   4:     [TestMethod]
   5:     public void Add()
   6:     {
   7:         var v1 = new Vector(1, 2);
   8:         var v2 = new Vector(3, 4);
   9:         Assert.AreEqual<Vector>(new Vector(4,6),v1+v2);
  10:  
  11:         v1.X = -1;
  12:         v1.Y = -2;
  13:         v2.X = -3;
  14:         v2.Y = -4;
  15:         Assert.AreEqual<Vector>(new Vector(-4, -6), v1 + v2);
  16:     }
  17: }

為了解決這個問題,我想很多人都知道一個特殊的自定義特性(Custom Attribute):System.Runtime.CompilerServices.InternalsVisibleToAttribute。沒錯,我們只需要在Lib項目的AssemblyInfo.cs添加這個InternalsVisibleToAttribute特性,指定目標程序集(能夠訪問本程序集的Internal成員的程序集)名稱即可。

   1: [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Test)]

二、如果對Lib添加強簽名呢?

在很多情況下,我們需要將最終的程序集以強命名的形式發布。為此,我們修改Lib項目設置,開啟"Sign the assembly”開關,並創建一個密鑰文件。

技術分享圖片

當完成上面的步驟後,Lib項目將不能通過編譯,編譯錯誤如下圖所示。具體的錯誤信息為:“Friend assembly reference ‘Test‘ is invalid. Strong-name signed assemblies must specify a public key in their InternalsVisibleTo declarations.

技術分享圖片

三、如果在InternalsVisibleToAttribute指定程序集的強名稱(Strong Name)呢?

從上面的出錯消息中我們不難看出,編譯錯誤的原因是:當自身具有強簽名的情況下,通過InternalsVisibleToAttribute指定的程序集也需要具有強簽名。那麽,如果我們將單元測試項目Test也加上強簽名,並將InternalsVisibleToAttribute特性指定成程序集的強名稱,是否可以解決這個問題呢?

在對Test項目按照上面的步驟進行強簽名後,並重新修改了應用在Lib程序集上的InternalsVisibleToAttribute特性設置,即設置成包含4個部分(名稱、版本、語言文化和公鑰令牌)的程序集強名稱。

   1: [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=8dba6a4f4e33b7dc")]

不幸的是,這會導致另一個編譯錯誤(如下圖所示)。具體出錯信息為:“Friend assembly reference ‘Test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=8dba6a4f4e33b7dc‘ is invalid. InternalsVisibleTo declarations cannot have a version, culture, public key token, or processor architecture specified.”。出錯信息表明:通過InternalsVisibleToAttribute特性指定的程序集名稱的時候,只能指定程序集名稱(文件名),不能指定版本、語言文化和公鑰令牌


技術分享圖片

四、需要指定的是完整的公鑰

實際上對於上面的情況,需要指定的不是程序名的強命名,而是指定對程序集進行簽名時采用的公鑰。那麽如何得到這個公鑰呢?我們可以通過強名稱(SN.exe)命令行工具直接將公鑰從密鑰文件中提取出來。

具體來說我們需要兩個步驟:通過SN.exe結合-p開關從將包含公鑰/私鑰的密鑰文件中提取公鑰,並導入到指定的密鑰文件中;然後執行SN.exe並結合使用-tp開關,將公鑰文件中的公鑰顯示出來。

兩個步驟地命令行輸入和輸出入下所示。其中Test.snk表示對單元測試項目進行簽名的密鑰文件,而Test.PK.snk則表示導出的只包含公鑰的密鑰文件。最終控制臺顯示出我們需要的完整的公鑰:“0024000004800000940000000602000000240000525341310004000001000100c9d70c8b6c1eb494b113701099f43ef62efe8c9cf4310bda2061eff1cc91ffda4368848d3283d4d83e63087038e32ea25e0098891608ae48993bf16ea93362d10207de3a4dca263c145a6febf1784401948c2474c3f55713e6b97e9c1c3eef5b8966b879407b955b23404c62cd75fcf3598b6950d104a4ea97209ad051763ca4

1: C:\Users\jinnan\Documents\Visual Studio 2010\Projects\InternalsVisibility\Test>SN -p Test.snk Test.PK.snk
2: Microsoft (R) .NET Framework Strong Name Utility  Version 4.0.30319.1   Copyright (c) Microsoft Corporation.  All rights reserved.   
3: Public key written to Test.PK.snk   
4: C:\Users\jinnan\Documents\Visual Studio 2010\Projects\InternalsVisibility\Test>SN -tp Test.PK.snk 
5: Microsoft (R) .NET Framework Strong Name Utility  Version 4.0.30319.1 Copyright (c) Microsoft Corporation.  All rights reserved.  
6: Public key is: 0024000004800000940000000602000000240000525341310004000001000100c9d70c8b6c1eb494b113701099f43ef62efe8c9cf4310bda2061eff1cc
7: 91ffda4368848d3283d4d83e63087038e32ea25e0098891608ae48993bf16ea93362d10207de3a4dca263c145a6febf1784401948c2474c3  
8: f55713e6b97e9c1c3eef5b8966b879407b955b23404c62cd75fcf3598b6950d104a4ea97209ad051763ca4  
9: Public key token is 8dba6a4f4e33b7dc

我們只需要將該公鑰指定到InternalsVisibleToAttribute特性中即可:

   1: [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Test,PublicKey=0024000004800000940000000602000000240000525341310004000
   2: 001000100c9d70c8b6c1eb494b113701099f43ef62efe8c9cf4310bda2061eff1cc91ffda4368848d3283d4d83e63087038e32ea25e0098891608ae48993bf16ea933
   3: 62d10207de3a4dca263c145a6febf1784401948c2474c3f55713e6b97e9c1c3eef5b8966b879407b955b23404c62cd75fcf3598b6950d104a4ea97209ad051763ca4")]

出處:https://www.cnblogs.com/artech/archive/2010/10/06/1844721.html

====================================================================

C#的internal關鍵字可以使標記的方法,字段或者屬性等等只能在當前assembly內部使用,那麽如果其他的assembly需要使用這個internal的方法的時候怎麽辦呢?.NET提供了一種類似於C++中的友元類的方式來完成這個功能,那就是使用InternalsVisibleTo

這種情況常見於做測試的時候,需要另外一個項目來測試項目中的internal方法所標記的功能,所以有了InternalsVisibleTo,我們就不用為了做單元測試而把一個本不該公開的方法改為public了.

使用InternalsVisibleTo還有一些需要註意的地方,特別是PublicKey不太容易弄的明白,下面先來說說這個InternalsVisibleTo該怎麽使用:

先來說明一下前提:Project1是功能項目,Project1.Test (assembly name: Project1.Test.dll)是為做Project1的測試工程。

1. 打開Project1的Assembly.cs文件,在文件的末尾加上這樣一句話:

[assembly: InternalsVisibleTo("Project1.Test, PublicKey=******")]

其中PublicKey=******應該替換成Project1.Test.dll的public key,至於如何獲取PublicKey,請看文章末尾的Notes部分.

2. 確認namespace: System.Runtime.CompilerServices 添加到了Assembly.cs的namespace引用中,因為InternalsVisibleTo位於命名空間System.Runtime.CompilerService中。

Notes:

1. 如何獲取PublicKey?

A: 在命令行下,使用sn -Tp Project1.Test.dll就可以看到Public Key is ......和Public Key Token is ......

2. 如果Project1是個strong-named的項目,那麽InternalsVisibleTo必須指定PublicKey,所以Project1.Test也必須使用強簽名才能正確使用InternalsVisibleTo, 不然編譯會出錯,如果Project1沒有使用強簽名,那麽Project1.Test也不必使用強簽名,而且在使用InternalsVisibleTo的時候只需要程序集的名字就可以了,不需要設置PuklicKey。

出處:https://www.cnblogs.com/lmule/archive/2010/08/15/1800227.html

使用InternalsVisibleToAttribute給assembly添加“友元assembly”特性遭遇"強簽名"