簡體   English   中英

C# 中“內部”關鍵字的實際用途

[英]Practical uses for the “internal” keyword in C#

你能解釋一下 C# 中internal關鍵字的實際用法嗎?

我知道internal修飾符限制了對當前程序集的訪問,但是我應該在什么時候以及在什么情況下使用它?

您希望從同一程序集中的許多其他類訪問的實用程序或幫助程序類/方法,但您希望確保其他程序集中的代碼無法訪問。

來自MSDN (通過 archive.org):

內部訪問的一個常見用途是在基於組件的開發中,因為它使一組組件能夠以私有方式協作,而不會暴露給應用程序的其余部分代碼。 例如,用於構建圖形用戶界面的框架可以提供使用具有內部訪問權限的成員進行協作的 Control 和 Form 類。 由於這些成員是內部成員,因此它們不會暴露給使用框架的代碼。

您還可以使用 internal 修飾符和InternalsVisibleTo程序集級別屬性來創建“朋友”程序集,這些程序集被授予對目標程序集內部類的特殊訪問權限。

這對於創建單元測試程序集很有用,然后允許調用要測試的程序集的內部成員。 當然,沒有其他程序集被授予此級別的訪問權限,因此當您發布系統時,會保持封裝。

如果 Bob 需要 BigImportantClass 那么 Bob 需要讓擁有項目 A 的人注冊以保證 BigImportantClass 將被編寫以滿足他的需要,測試以確保它滿足他的需要,被記錄為滿足他的需要,並且一個過程將落實到位,以確保它永遠不會改變,從而不再滿足他的需求。

如果一個班級是內部班級,那么它就不必經過那個過程,這為項目 A 節省了可以花在其他事情上的預算。

內部的重點並不是它讓鮑勃的生活變得困難。 它允許您控制項目 A 在特性、生命周期、兼容性等方面做出的昂貴承諾。

使用 internal 的另一個原因是如果您混淆了二進制文件。 混淆器知道打亂任何內部類的類名是安全的,而不能打亂公共類的名稱,因為這可能會破壞現有的引用。

如果您正在編寫一個將大量復雜功能封裝到一個簡單的公共 API 中的 DLL,那么“內部”用於不公開公開的類成員。

隱藏復雜性(又名封裝)是質量軟件工程的主要概念。

當您在非托管代碼上構建包裝器時,會大量使用 internal 關鍵字。

當您有一個基於 C/C++ 的庫想要 DllImport 時,您可以將這些函數作為類的靜態函數導入,並使它們成為內部函數,因此您的用戶只能訪問您的包裝器而不是原始 API,因此它不能亂七八糟的。 函數是靜態的,您可以在程序集中的任何地方使用它們,用於您需要的多個包裝類。

您可以查看 Mono.Cairo,它是使用這種方法的 cairo 庫的包裝器。

受“盡可能使用嚴​​格修飾符”規則的驅動,我在需要訪問的任何地方使用內部,例如,來自另一個類的方法,直到我明確需要從另一個程序集訪問它。

由於匯編接口通常比其類接口的總和更窄,因此我在很多地方使用它。

我發現內部被過度使用了。 你真的不應該只向某些類公開某些功能,而你不會向其他消費者公開。

在我看來,這打破了界面,打破了抽象。 這並不是說它不應該被使用,而是更好的解決方案是重構為不同的類,或者如果可能的話以不同的方式使用。 然而,這可能並不總是可能的。

它可能導致問題的原因是另一個開發人員可能負責在您所在的程序集中構建另一個類。 擁有內部結構會降低抽象的清晰度,並且如果被濫用可能會導致問題。 如果您將其公開,這將是相同的問題。 其他開發人員正在構建的另一個類仍然是消費者,就像任何外部類一樣。 類抽象和封裝不僅僅是為了保護/免受外部類,而是為了任何和所有類。

另一個問題是,許多開發人員會認為他們可能需要在程序集中的其他地方使用它並將其標記為內部,即使他們當時並不需要它。 另一個開發人員可能會認為它是可以接受的。 通常,您希望在有明確需求之前將其標記為私有。

但其中一些可能是主觀的,我並不是說永遠不應該使用它。 只在需要時使用。

前幾天,也許是一周,在一個我不記得的博客上看到了一個有趣的。 基本上我不能相信這一點,但我認為它可能有一些有用的應用程序。

假設您希望另一個程序集看到一個抽象類,但您不希望有人能夠繼承它。 Sealed 不起作用,因為它是抽象的,因為它是抽象的,該程序集中的其他類確實從它繼承了它。 Private 不起作用,因為您可能想在另一個程序集中的某處聲明一個 Parent 類。

namespace Base.Assembly
{
  public abstract class Parent
  {
    internal abstract void SomeMethod();
  }

  //This works just fine since it's in the same assembly.
  public class ChildWithin : Parent
  {
    internal override void SomeMethod()
    {
    }
  }
}

namespace Another.Assembly
{
  //Kaboom, because you can't override an internal method
  public class ChildOutside : Parent
  {
  }

  public class Test 
  { 

    //Just fine
    private Parent _parent;

    public Test()
    {
      //Still fine
      _parent = new ChildWithin();
    }
  }
}

如您所見,它有效地允許某人使用 Parent 類而不能繼承。

當您有方法、類等需要在當前程序集范圍內訪問時,不能在當前程序集范圍外訪問。

例如,一個 DAL 可能有一個 ORM,但對象不應該暴露給業務層,所有交互都應該通過靜態方法並傳入所需的參數來完成。

此示例包含兩個文件:Assembly1.cs 和 Assembly2.cs。 第一個文件包含一個內部基類 BaseClass。 在第二個文件中,嘗試實例化 BaseClass 將產生錯誤。

// Assembly1.cs
// compile with: /target:library
internal class BaseClass 
{
   public static int intM = 0;
}

// Assembly1_a.cs
// compile with: /reference:Assembly1.dll
class TestAccess 
{
   static void Main()
   {  
      BaseClass myBase = new BaseClass();   // CS0122
   }
}

在此示例中,使用與示例 1 中相同的文件,並將 BaseClass 的可訪問性級別更改為public 還將成員 IntM 的可訪問性級別更改為internal 在這種情況下,您可以實例化類,但不能訪問內部成員。

// Assembly2.cs
// compile with: /target:library
public class BaseClass 
{
   internal static int intM = 0;
}

// Assembly2_a.cs
// compile with: /reference:Assembly1.dll
public class TestAccess 
{
   static void Main() 
   {      
      BaseClass myBase = new BaseClass();   // Ok.
      BaseClass.intM = 444;    // CS0117
   }
}

來源http : //msdn.microsoft.com/en-us/library/7c5ka91b(VS.80).aspx

作為經驗法則,有兩種成員:

  • 公共表面:從外部程序集可見(公共、受保護和內部受保護):調用者不受信任,因此需要參數驗證、方法文檔等。
  • 私有表面:從外部程序集(私有和內部,或內部類)不可見:調用者通常是可信的,因此可以省略參數驗證、方法文檔等。

內部的一個非常有趣的用途——內部成員當然僅限於聲明它的程序集——在某種程度上獲得了“朋友”功能。 朋友成員是僅對聲明它的程序集之外的某些其他程序集可見的東西。 C# 沒有對朋友的內置支持,但是 CLR 有。

您可以使用InternalsVisibleToAttribute來聲明友元程序集,來自友元程序集內的所有引用都會將聲明程序集的內部成員視為友元程序集范圍內的公共成員。 這樣做的一個問題是所有內部成員都是可見的; 你不能挑剔。

InternalsVisibleTo 的一個很好的用途是將各種內部成員暴露給單元測試程序集,從而消除對復雜反射工作的需要來測試這些成員。 所有內部成員都可見並不是什么大問題,但是采用這種方法確實會嚴重破壞您的類接口,並且可能會破壞聲明程序集中的封裝。

降噪,你暴露的類型越少,你的庫就越簡單。 防篡改/安全性是另一個(盡管反射可以戰勝它)。

內部類使您能夠限制程序集的 API。 這有很多好處,比如讓你的 API 更容易理解。

此外,如果您的程序集中存在錯誤,則修復引入破壞性更改的可能性較小。 如果沒有內部類,您將不得不假設更改任何類的公共成員將是一個重大更改。 對於內部類,您可以假設修改它們的公共成員只會破壞程序集(以及 InternalsVisibleTo 屬性中引用的任何程序集)的內部 API。

我喜歡在類級別和程序集級別進行封裝。 有些人不同意這一點,但很高興知道該功能可用。

internal 關鍵字的一種用途是限制程序集用戶對具體實現的訪問。

如果您有一個工廠或其他一些用於構造對象的中心位置,則程序集的用戶只需要處理公共接口或抽象基類。

此外,內部構造函數允許您控制實例化公共類的位置和時間。

我有一個使用 LINQ-to-SQL 作為數據后端的項目。 我有兩個主要的命名空間:Biz 和 Data。 LINQ 數據模型存在於 Data 中,並被標記為“內部”; Biz 命名空間具有環繞 LINQ 數據類的公共類。

所以有Data.ClientBiz.Client 后者公開數據對象的所有相關屬性,例如:

private Data.Client _client;
public int Id { get { return _client.Id; } set { _client.Id = value; } }

Biz 對象有一個私有構造函數(強制使用工廠方法)和一個內部構造函數,如下所示:

internal Client(Data.Client client) {
    this._client = client;
}

庫中的任何業務類都可以使用它,但前端 (UI) 無法直接訪問數據模型,從而確保業務層始終充當中介。

這是我第一次真正使用internal ,並且證明它非常有用。

在某些情況下,將類的成員設置為internal是有意義的。 一個例子可能是,如果您想控制類的實例化方式; 假設您提供了某種工廠來創建類的實例。 您可以將構造函數設置為internal ,以便工廠(駐留在同一程序集中)可以創建類的實例,但該程序集之外的代碼不能。

但是,我認為在沒有特定原因的情況下將類或成員設置為internal沒有任何意義,就像在沒有特定原因的情況下將它們設置為publicprivate一樣有意義。

我唯一使用過的 internal 關鍵字是我產品中的許可證檢查代碼;-)

這個怎么樣:通常建議您不要向程序集的外部用戶公開 List 對象,而是公開 IEnumerable。 但是在程序集中使用 List 對象要容易得多,因為您可以獲得數組語法和所有其他 List 方法。 所以,我通常有一個內部屬性,公開一個要在程序集中使用的 List。

歡迎對此方法發表評論。

請記住,當有人查看您的項目命名空間時,任何定義為public類都會自動顯示在智能感知中。 從 API 的角度來看,重要的是只向項目用戶展示他們可以使用的類。 使用internal關鍵字隱藏他們不應該看到的東西。

如果您的項目 A 的Big_Important_Class打算在您的項目之外使用,那么您不應將其標記為internal

但是,在許多項目中,您通常會擁有真正僅用於項目內部的類。 例如,您可能有一個類來保存參數化線程調用的參數。 在這些情況下,如果沒有其他原因,您應該將它們標記為internal ,而不是為了保護自己免受無意中的 API 更改。

這個想法是,當您設計一個庫時,只有打算從外部(由您的庫的客戶)使用的類應該是公共的。 這樣你就可以隱藏那些

  1. 在未來的版本中可能會發生變化(如果它們是公開的,你會破壞客戶端代碼)
  2. 對客戶沒用,可能會造成混亂
  3. 不安全(因此不當使用可能會嚴重破壞您的庫)

等等。

如果您正在開發內部解決方案,那么使用內部元素並不是那么重要,因為通常客戶會與您保持聯系和/或訪問代碼。 不過,它們對庫開發人員來說相當重要。

添加 Web 服務引用時 您可以為生成的客戶端類選擇內部/公共訪問。 如果在示例中您不想讓這些生成的類對項目外部可見,但您想以某種方式包裝它們,這會有所幫助。 在此處輸入圖片說明

當您的類或方法不完全符合面向對象范式時,它們會做危險的事情,需要從您控制的其他類和方法中調用,並且您不想讓其他人使用它們.

public class DangerousClass {
    public void SafeMethod() { }
    internal void UpdateGlobalStateInSomeBizarreWay() { }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM