Przejdź do głównej zawartości

[C#] Jakich modyfikatorów dostępu możemy użyć w C#?


W C# istnieje sześć modyfikatorów dostępu:
  • public
  • proteted
  • private
  • internal
  • protected internal
  • private protected (uwaga: dostępne od C# w wersji 7.2)
Uwaga: do zrozumienia modyfikatorów dostępu konieczne jest zrozumienie, czym jest assembly. Można się tego dowiedzieć w tym poście [under construction].

Spróbujmy zrozumieć, jak działają odpowiednie modyfikatory. (Na samym dole posta można znaleźć tabelkę dla niecierpliwych)

public

public jest najluźniejszym modyfikatorem dostępu. Dostęp do pola lub metody oznaczonego słowem public mają nie tylko wszystkie klasy z tego samego assembly, ale również wszystkie z innych assembly.

Zobaczmy, jak to działa w praktyce:

W assembly TextTransform tworzymy klasę TextCutter, która posiada publiczną metodę CutTo5Letters:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
namespace TextTransform
{
    public class TextCutter
    {
        public string CutTo5Letters(string text)
        {
            return text.Substring(0, 5);
        }
    }
}

Dostęp do tej metody mają inne klasy w obrębie tego samego assembly, zarówno dziedziczące klasy TextTransform, jak i kompletnie niezależne:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
namespace TextTransform
{
    public class UpperCaseTextCutter : TextCutter
    {
        public string CutTo5LettersAndMakeUpperCase(string text)
        {
            return CutTo5Letters(text).ToUpper();
        }
    }

    public class TextCutterUser
    {
        public void UseTextCutter()
        {
            var textCutter = new TextCutter();
            var result = textCutter.CutTo5Letters("123456789");
        }
    }
}

Nie ma też problemu, by z metody tej skorzystała klasa z innego assembly, które posiada referencję do assembly TextTransform:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
using System;
using TextTransform;

namespace CSharpPractices
{  
    class Program
    {
        static void Main()
        {
            Console.WriteLine(new TextCutter().CutTo5Letters("aaaaaaaaaaa"));
            Console.ReadLine();
        }
    }
}

protected

Pole lub metoda oznaczone modyfikatorem protected będą dostępne tylko dla klas dziedziczących z klasy będącej właścicielem tego pola lub metody. Nie ma tu znaczenia, czy klasa dziedzicząca jest w tym samym, czy w innym assembly.

Zmieńmy dostęp do metody CutTo5Letter z public na protected:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
namespace TextTransform
{
    public class TextCutter
    {
        protected string CutTo5Letters(string text)
        {
            return text.Substring(0, 5);
        }
    }
}

Wciąż możemy dostać się do tej metody z klas dziedziczących, zarówno z tego samego assembly...

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
namespace TextTransform
{
    public class UpperCaseTextCutter : TextCutter
    {
        public string CutTo5LettersAndMakeUpperCase(string text)
        {
            return CutTo5Letters(text).ToUpper();
        }
    }
}

...jak i z innego:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
namespace CSharpPractices
{
    public class LowerCaseTextCutter : TextCutter
    {
        public string CutTo5LettersAndMakeUpperCase(string text)
        {
            return CutTo5Letters(text).ToLower();
        }
    }
}

Jednak próba użycia tej metody przez klasy inne niż dziedziczące, zakończy się błędem kompilacji. Zarówno w tym samym assembly...

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
namespace TextTransform
{
    public class TextCutterUser
    {
        public void UseTextCutter()
        {
            var textCutter = new TextCutter();
            var result = textCutter.CutTo5Letters("123456789");// błąd kompilacji
        }
    }
}

...jak i w innym:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
using System;
using TextTransform;

namespace CSharpPractices
{
    class Program
    {
        static void Main()
        {
            Console.WriteLine(new TextCutter().CutTo5Letters("aaaaaaaaaaa")); //błąd kompilacji
            Console.ReadLine();
        }
    }
}

private

private jest najbardziej rygorystycznym modyfikatorem dostępu. Pola lub metody są dostępne tylko w klasie, w której są zadeklarowane. Nie mają do nich dostępu ani klasy dziedziczące, ani żadne inne. Zmieńmy dostępność metody CutTo5Letters na private:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
namespace TextTransform
{
    public class TextCutter
    {
        private string CutTo5Letters(string text)
        {
            return text.Substring(0, 5);
        }
    }
}

Podobnie jak w przypadku protected, nie mogą jej użyć inne klasy, niezależnie od assembly:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
namespace TextTransform
{
    public class TextCutterUser
    {
        public void UseTextCutter()
        {
            var textCutter = new TextCutter();
            var result = textCutter.CutTo5Letters("123456789");// błąd kompilacji
        }
    }
}


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
using System;
using TextTransform;

namespace CSharpPractices
{
    class Program
    {
        static void Main()
        {
            Console.WriteLine(new TextCutter().CutTo5Letters("aaaaaaaaaaa")); //błąd kompilacji
            Console.ReadLine();
        }
    }
}

Tym razem dostępu nie mają też klasy dziedziczące z TextCutter. Zarówno w tym samym assembly...

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
namespace TextTransform
{
    public class UpperCaseTextCutter : TextCutter
    {
        public string CutTo5LettersAndMakeUpperCase(string text)
        {
            return CutTo5Letters(text).ToUpper(); //błąd kompilacji
        }
    }
}

...jak i w innym:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
using System;
using TextTransform;

namespace CSharpPractices
{
    public class LowerCaseTextCutter : TextCutter
    {
        public string CutTo5LettersAndMakeUpperCase(string text)
        {
            return CutTo5Letters(text).ToLower();
        }
    }
}

internal

Pole lub metoda z modyfikatorem internal jest dostępne dla każdej klasy - zarówno dziedziczącej, jak i niezależnej - ale tylko w obszarze tego samego assembly.

Innymi słowy: wewnątrz własnego assembly zachowują się jak public. Poza nim - jak private.

Zmieńmy modyfikator dostępności metody CutTo5Letters na internal:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
namespace TextTransform
{
    public class TextCutter
    {
        internal string CutTo5Letters(string text)
        {
            return text.Substring(0, 5);
        }
    }
}

Metoda ta jest dostępna dla wszystkich klas z tego samego assembly, zarówno dziedziczących z TextCutter...

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
namespace TextTransform
{
    public class UpperCaseTextCutter : TextCutter
    {
        public string CutTo5LettersAndMakeUpperCase(string text)
        {
            return CutTo5Letters(text).ToUpper();
        }
    }
}

...jak i niezależnych:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
namespace TextTransform
{
    public class TextCutterUser
    {
        public void UseTextCutter()
        {
            var textCutter = new TextCutter();
            var result = textCutter.CutTo5Letters("123456789");
        }
    }
}

Jednak poza assembly, w którym istnieje klasa TextCutter, nie mogą się do niej dostać ani klasy dziedziczące z TextCutter...

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
using System;
using TextTransform;

namespace CSharpPractices
{
    public class LowerCaseTextCutter : TextCutter
    {
        public string CutTo5LettersAndMakeUpperCase(string text)
        {
            return CutTo5Letters(text).ToLower(); //błąd kompilacji
        }
    }
}

...ani tym bardziej klasy niezależne od klasy TextCutter:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
using System;
using TextTransform;

namespace CSharpPractices
{
    class Program
    {
        static void Main()
        {
            Console.WriteLine(new TextCutter().CutTo5Letters("aaaaaaaaaaa")); //błąd kompilacji
            Console.ReadLine();
        }
    }
}

protected internal

Pola i metody oznaczone modyfikatorem protected internal są dostępne dla wszystkich klas wewnątrz tego samego assembly, i dla klas dziedziczących w innym assembly.

Innymi słowy: wewnątrz własnego assembly zachowują się jak pola public, zaś w innym assembly jak protected.

Zmieńmy dostęp do metody CutTo5Letters na protected internal:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
namespace TextTransform
{
    public class TextCutter
    {
        protected internal string CutTo5Letters(string text)
        {
            return text.Substring(0, 5);
        }
    }
}

Wewnątrz tego samego assembly metoda dostępna jest dla wszystkich:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
namespace TextTransform
{
    public class UpperCaseTextCutter : TextCutter
    {
        public string CutTo5LettersAndMakeUpperCase(string text)
        {
            return CutTo5Letters(text).ToUpper();
        }
    }
}


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
namespace TextTransform
{
    public class TextCutterUser
    {
        public void UseTextCutter()
        {
            var textCutter = new TextCutter();
            var result = textCutter.CutTo5Letters("123456789");
        }
    }
}

Jednak poza assembly TextTransform nie można się do niej dostać z klasy, która nie dziedziczy z TextCutter...

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
using System;
using TextTransform;

namespace CSharpPractices
{
    class Program
    {
        static void Main()
        {
            Console.WriteLine(new TextCutter().CutTo5Letters("aaaaaaaaaaa")); //błąd kompilacji
            Console.ReadLine();
        }
    }    
}

...za to dostępna jest w klasie dziedziczącej:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
using TextTransform;

namespace CSharpPractices
{
    public class LowerCaseTextCutter : TextCutter
    {
        public string CutTo5LettersAndMakeUpperCase(string text)
        {
            return CutTo5Letters(text).ToLower(); 
        }
    }    
}

private protected

Pola i metody oznaczone modyfikatorem private protected są dostępne jedynie w klasach dziedziczących z danej klasy, które znajdują się w tym samym assembly co klasa bazowa. Nie są dostępne dla klas dziedziczących znajdujących się w innym assembly.

Innymi słowy: wewnątrz tego samego assembly zachowują się jak pola protected. Poza nim zachowują się jak private.

Zmieńmy modyfikator naszej metody na private protected:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
namespace TextTransform
{
    public class TextCutter
    {
        private protected string CutTo5Letters(string text)
        {
            return text.Substring(0, 5);
        }
    }
}

Wewnątrz assembly TextTransform dostępna jest w klasach dziedziczących...

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
namespace TextTransform
{
    public class UpperCaseTextCutter : TextCutter
    {
        public string CutTo5LettersAndMakeUpperCase(string text)
        {
            return CutTo5Letters(text).ToUpper();
        }
    }
}

...jednak nie w innych:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
namespace TextTransform
{
    public class TextCutterUser
    {
        public void UseTextCutter()
        {
            var textCutter = new TextCutter();
            var result = textCutter.CutTo5Letters("123456789"); //błąd kompilacji
        }
    }
}

Poza własnym assembly nie jest dostępna dla nikogo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
using System;
using TextTransform;

namespace CSharpPractices
{
    class Program
    {
        static void Main()
        {
            Console.WriteLine(new TextCutter().CutTo5Letters("aaaaaaaaaaa")); //błąd kompilacji
            Console.ReadLine();
        }
    }    
}


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
using TextTransform;

namespace CSharpPractices
{
    public class LowerCaseTextCutter : TextCutter
    {
        public string CutTo5LettersAndMakeUpperCase(string text)
        {
            return CutTo5Letters(text).ToLower(); 
        }
    }    
}

Podsumujmy:

Domyślne modyfikatory dostępu

Na koniec odpowiedzmy jeszcze sobie na pytanie: jakie są domyślne modyfikatory dostępu? Jeśli nie napiszemy wprost modyfikatora, to modyfikatorem zawsze będzie najbardziej restrykcyjny modyfikator, jaki jest dozwolony w tym kontekście.

To znaczy, że:

  • dla elementu deklarowanego bezpośrednio w namespace (czyli dla klasy, struktury, interfejsu, delegatu, enuma itp) będzie to internal
  • dla elementu wewnątrz klasy lub struktury - a więc pól, properties, klas zagnieżdżonych, funkcji itp - będzie to private
  • dla konstruktora będzie to private. Uwaga: domyślny bezparametrowy konstruktor jest public
  • dla wartości enuma będzie to public
  • dla metod w interfejsie będzie to public


Komentarze