本篇用圖表來介紹 C# 類別成員的存取關係,有 privateprotectedpublic,跟跨組件的成員存取,有 internalprotected internalprivate protected。以及比較一般繼承與多型繼承中,會取得什麼版本的方法。

類別成員的存取

存取來源 private protected public
自己類別中 :heavy_check_mark: :heavy_check_mark: :heavy_check_mark:
衍生類別中 :x: :heavy_check_mark: :heavy_check_mark:
外部 :x: :x: :heavy_check_mark:

自己類別中

在自己類別中存取的情況是:

  1. 在類別定義中,存取類別的成員,如 Test1。
  2. 在類別定義中,透過該類別的物件存取成員,如 Test2。

所有 privateprotectedpublic 的成員都可以存取。

public class Bar
{
  private int x;
  private void Func()
  {
    Console.WriteLine("Func called");
  }

  public void Test1()
  {
    x = 42;
    Func();
  }

  public void Test2(Bar bar)
  {
    bar.x = 42;
    bar.Func();
  }
}

衍生類別中

在衍生類別中存取的情況是:

  1. 在衍生類別的定義中,存取基礎類別的成員,如 Test1。
  2. 在衍生類別的定義中,透過該類別物件存取基礎類別的成員,如 Test2。

protectedpublic 的成員都可以存取,但 private 的成員不行。

public class Bar
{
  protected int x;
  protected void Func()
  {
    Console.WriteLine("Func called");
  }
}

public class Foo : Bar
{
  public void Test1()
  {
    x = 42;
    Func();
  }

  public void Test2(Foo foo)
  {
    foo.x = 42;
    foo.Func();
  }
}

外部

在外部存取是指:在非衍生類別中,透過該類別的物件存取。

只有 public 成員才可以存取。

public class Bar
{
  public int x;
  public void Func()
  {
    Console.WriteLine("Func called");
  }
}

public class SomeClass
{
  public void Test()
  {
    var bar = new Bar();
    bar.x = 42;
    bar.Func();
  }
}

跨組件的類別成員存取

在 C# 中,程式碼可以編譯到不同的組件(Assembly)中,並像模組或函式庫一樣,可以放到不同的程式中供取用。上述的 publicprotectedprivated 成員不受組件不同的影響,如 public 成員在也可以被不同組件的類別取用,但 C# 另外提供可以控制不同組件的類別成員存取的修飾詞。

相同組件中

存取來源 internal protected internal private protected
自己類別中 :heavy_check_mark: :heavy_check_mark: :heavy_check_mark:
衍生類別中 :heavy_check_mark: :heavy_check_mark: :heavy_check_mark:
外部 :heavy_check_mark: :heavy_check_mark: :x:

不同組件中

存取來源 internal protected internal private protected
衍生類別中 :x: :heavy_check_mark: :x:
外部 :x: :x: :x:
  • internalpublic 相似,但只在相同組件中才是 public
  • protected internal 比起 internal 的權限稍為大一點,讓不同元件的衍生類別可以取用。
  • private protectedprotected 相似,但只在相同組件中才是 protected

外部存取方法的版本

在基礎類別跟衍生類別中的 public 方法有相同簽章1時,會因為關鍵字(如:newvirtual)的不同而讓類別物件存取到不同版本的方法。

一般繼承

如果沒有同簽章的方法

public class Bar
{
  public void Func()
  {
    Console.WriteLine("Bar.Func");
  }
}

public class Foo : Bar
{
}
  Bar b Foo f
new Bar() “Bar.Func” :x:
new Foo() “Bar.Func” “Bar.Func”

上方表格以 Bar bnew Bar() 為例,代表的是:

Bar b = new Bar();
b.Func();

欄位內則是會輸出的訊息。

如果有同簽章的方法

如果衍生類別中有跟基礎類別同簽章的方法,則會「隱藏」基礎類別的方法,編譯器會發出警告,加上 new 關鍵字則可以告訴編譯器就是要這樣作。

public class Bar
{
  public void Func()
  {
    Console.WriteLine("Bar.Func");
  }
}

public class Foo : Bar
{
  public new void Func()
  {
    Console.WriteLine("Foo.Func");
  }
}
  Bar b Foo f
new Bar() “Bar.Func” :x:
new Foo() “Bar.Func” (1.) “Foo.Func” (2.)
  1. 在一般繼承中,如果將衍生類別的物件賦值給基礎類別的變數,該物件會被隱含轉換(implict type conversion)成基礎類別的物件,所以會使用到基礎類別的版本。
  2. 因為衍生類別中「隱藏」了基礎類別的方法,所以衍生類別的物件會使用衍生類別的版本。

多型繼承

如果基礎類別有 virtual 方法,而且在衍生類別中用 override「覆寫」該方法的話,即使把衍生類別的物件賦值給基礎類別的變數,也可以取用到衍生類別的版本,稱為多型繼承。在多型繼承下,就可以讓基礎類別的變數因給與不同的衍生類別的物件,呼叫同個方法而有衍生類別上的行為,不需使用多個衍生類別的物件。

如果沒有同簽章的方法

public class Bar
{
  public virtual void Func()
  {
    Console.WriteLine("Bar.Func");
  }
}

public class Foo : Bar
{
}
  Bar b Foo f
new Bar() “Bar.Func” :x:
new Foo() “Bar.Func” “Bar.Func”

如果基礎類別的方法沒有被「覆寫」的話,就跟一般繼承一樣,都是基礎類別的版本。

如果有同簽章的方法

public class Bar
{
  public virtual void Func()
  {
    Console.WriteLine("Bar.Func");
  }
}

public class Foo : Bar
{
  public override void Func()
  {
    Console.WriteLine("Foo.Func");
  }
}
  Bar b Foo f
new Bar() “Bar.Func” :x:
new Foo() “Foo.Func” (1.) “Foo.Func”
  1. 把衍生類別的物件賦值給基礎類別的變數,雖然會被隱含轉換,但是在多型繼承下,基礎類別的方法會被「覆寫」,所以會取用到衍生類別的版本。

參考資料

  1. 方法簽章(method signature)由存取層級(access level,如:public)、選擇性修飾詞(optional modifier,如:abstract)、回傳值、方法名稱、方法參數(parameters)構成。 

標籤: ,

分類:

更新時間: