Ако два класа не са видими един за друг, то елементите им (полета и методи) не са видими също, независимо с какви нива на достъп са декларирани самите те.
В следващите подсекции, към обясненията, ще разглеждаме примери, в които имаме два класа (Dog и Kid), които са видими един за друг, т.е. всеки един от класовете може да създава обекти от тип – другия клас и да достъпва елементите му в зависимост от нивото на достъп, с което са декларирани. Ето как изглежда първия клас Dog:
public class Dog
{
private string name = "Sharo";
public string Name
{
get { return this.name; }
}
public void Bark()
{
Console.WriteLine("wow-wow");
}
public void DoSth()
{
this.Bark();
}
}
|
В освен полета и методи се използва и свойство Name, което просто връща полето name. Ще разгледаме свойствата след малко, така че за момента се фокусирайте върху останалото.
Кодът на класа Kid има следния вид:
public class Kid
{
public void CallTheDog(Dog dog)
{
Console.WriteLine("Come, " + dog.Name);
}
public void WagTheDog(Dog dog)
{
dog.Bark();
}
}
|
В момента, всички елементи (полета и методи) на двата класа са декларирани с модификатор за достъп public, но при обяснението на различните нива на достъп, ще го променяме съответно. Това, което ще ни интересува, е как промяната в нивото на достъп на елементите (полета и методи) на класа Dog и ще рефлектира върху достъпа до тези елементи, когато този достъп се извършва от:
- Самото тяло на класа Dog.
- Тялото на класа Kid, съответно вземайки предвид дали Kid е в пространството от имена (или асембли), в което се намира класа Dog или не.
Ниво на достъп public
Когато метод или променлива на класа са декларирани с модификатор за достъп public, те могат да бъдат достъпвани от други класове, независимо дали другите класове са декларирани в същото пространство от имена, в същото асембли или извън него.
Нека разгледаме двата типа достъп до член на класа, които се срещат в нашите класове Dog и Kid:
|
Достъп до член на класа осъществен в самата декларация на класа.
|
|
Достъп до член на класа осъществен, чрез референция към обект, създаден в тялото на друг клас
|
Когато членовете на двата класа са public, се получава следното:
Dog.cs
|
|
class Dog
{
public string name = "Sharo";
public string Name
{
get { return this.name; }
}
public void Bark()
{
Console.WriteLine("wow-wow");
}
public void DoSth()
{
this.Bark();
}
}
|
Kid.cs
|
|
class Kid
{
public void CallTheDog(Dog dog)
{
Console.WriteLine("Come, " + dog.name);
}
public void WagTheDog(Dog dog)
{
dog.Bark();
}
}
|
Както виждаме, без проблем осъществяваме, достъп до полето name и до метода Bark() в класа Dog от тялото на самия клас. Независимо дали класът Kid е в пространството от имена на класа Dog, можем от тялото му, да достъпим полето name и съответно да извикаме метода Bark() чрез оператора точка, приложен към референцията dog към обект от тип Dog.
Ниво на достъп internal
Когато член на някой клас бъде деклариран с ниво на достъп internal, тогава този елемент на класа може да бъде достъпван от всеки клас в същото асембли (т.е. в същия проект във Visual Studio), но не и от класовете извън него (т.е. от друг проект във Visual Studio):
Dog.cs
|
|
class Dog
{
internal string name = "Sharo";
public string Name
{
get { return this.name; }
}
internal void Bark()
{
Console.WriteLine("wow-wow");
}
public void DoSth()
{
this.Bark();
}
}
|
Съответно, за класа Kid, разглеждаме двата случая:
- Когато е в същото асембли, достъпът до елементите на класа Dog, ще бъде позволен, независимо дали двата класа са в едно и също пространство от имена или в различни:
Kid.cs
|
|
class Kid
{
public void CallTheDog(Dog dog)
{
Console.WriteLine("Come, " + dog.name);
}
public void WagTheDog(Dog dog)
{
dog.Bark();
}
}
|
- Когато класът Kid е външен за асемблито, в което е деклариран класът Dog, тогава достъпът до полето name и метода Bark() ще е невъзможен:
Kid.cs
|
|
class Kid
{
public void CallTheDog(Dog dog)
{
Console.WriteLine("Come, " + dog.name);
}
public void WagTheDog(Dog dog)
{
dog.Bark();
}
}
|
Всъщност достъпът до internal членовете на класа Dog е невъзможен по две причини: недостатъчна видимост на класа и недостатъчна видимост на членовете му. За да се позволи достъп от друго асембли до класа Dog, той, е необходимо той да е деклариран като public и едновременно с това въпросните му членове да са декларирани като public. Ако или класът или членовете му имат по-ниска видимост, достъпът до тях е невъзможен от други асемблита (други Visual Studio проекти).
Ако се опитаме да компилираме класа Kid, когато е външен за асемблито, в което се намира класа Dog, ще получим грешки при компилация.
Ниво на достъп private
Нивото на достъп, което налага най-много ограничения е private. Елементите на класа, които са декларирани с модификатор за достъп private (или са декларирани без модификатор за достъп, защото тогава private се подразбира), не могат да бъдат достъпвани от никой друг клас, освен от класа, в който са декларирани.
Следователно, ако декларираме полето name и метода Bark() на класа Dog, с модификатори private, няма проблем да ги достъпваме вътрешно от самия клас Dog, но достъп от други класове не е позволен, дори ако са от същото асембли:
Dog.cs
|
|
class Dog
{
private string name = "Sharo";
public string Name
{
get { return this.name; }
}
private void Bark()
{
Console.WriteLine("wow-wow");
}
public void DoSth()
{
this.Bark();
}
}
|
Kid.cs
|
|
class Kid
{
public void CallTheDog(Dog dog)
{
Console.WriteLine("Come, " + dog.name);
}
public void WagTheDog(Dog dog)
{
dog.Bark();
}
}
|
Трябва да знаем, че когато задаваме модификатор за достъп за дадено поле, той най-често трябва да бъде private, тъй като така даваме възможно най-висока защита на достъпа до стойността на полето. Съответно, достъпът и модификацията на тази стойност от други класове (ако са необходими) ще се осъществяват единствено чрез свойства или методи. Повече за тази техника ще научим в секцията "Капсулация" на главата "Принципи на обектно-ориентираното програмиране".
Как се определя нивото на достъп на елементите на класа?
Преди да приключим със секцията за видимостта на елементите на един клас, нека направим един експеримент. Нека в класа Dog полето name и метода Bark() са декларирани с модификатор за достъп private. Нека също така, декларираме метод Main(), със следното съдържание:
public class Dog
{
private string name = "Sharo";
// ...
private void Bark()
{
Console.WriteLine("wow-wow");
}
// ...
public static void Main()
{
Dog myDog = new Dog();
Console.WriteLine("My dog's name is " + myDog.name);
myDog.Bark();
}
}
|
Въпросът, който стои пред нас е, ще се компилира ли класът Dog, при положение, че сме декларирали елементите на класа с модификатор за достъп private, а в същото време ги извикваме с точкова нотация, приложена към променливата myDog, в метода Main()?
Стартираме компилацията и тя минава успешно. Съответно, резултатът от изпълнението на метода Main(), който декларирахме в класа Dog ще бъде следният:
My dog’s name is Sharo
Wow-wow
|
Всичко се компилира и работи, тъй като модификаторите за достъп до елементите на класа се прилагат на ниво клас, а не на ниво обекти. Тъй като променливата myDog е дефинирана в тялото на класа Dog (където е разположен и Main() метода на програмата), можем да достъпваме елементите му (полета и методи) чрез точкова нотация, независимо че са декларирани с ниво на достъп private. Ако обаче се опитаме да направим същото от тялото на класа Kid, това няма да е възможно, тъй като достъпът до private полетата от външен клас не е разрешено.
Конструктори
В обектно-ориентираното програмиране, когато създаваме обект от даден клас, е необходимо да извикаме елемент от класа, наречен конструктор.
Какво е конструктор?
Конструктор на даден клас, наричаме псевдометод, който няма тип на връщана стойност, носи името на класа и който се извиква чрез ключовата дума new. Задачата на конструктора е да инициализира заделената за обекта памет, в която ще се съхраняват неговите полетата (тези, които не са static).
Извикване на конструктор
Единственият начин да извикаме един конструктор в C# е чрез ключовата дума new. Тя заделя памет за новия обект (в стека или в хийпа според това дали обектът е стойностен или референтен тип), занулява полетата му, извиква конструктора му (или веригата конструктори, образувана при наследяване) и накрая връща референция към новозаделения обект.
Нека разгледаме един пример, от който ще стане ясно как работи конструкторът. От главата "Създаване и използване на обекти", знаем как се създава обект:
В случая, чрез ключовата дума new, извикваме конструктора на класа Dog, при което се заделя паметта необходима за новосъздадения обект от тип Dog. Когато става дума за класове, те се заделят в динамичната памет (хийпа). Нека проследим как протича този процес стъпка по стъпка. Първо се заделя памет за обекта:
След това се инициализират полетата му (ако има такива) с подразбиращите се стойности за съответните им типове:
Ако създаването на новия обект е завършило успешно, конструкторът връща референция към него, която се присвоява на променливата myDog, от тип класа Dog:
Деклариране на конструктор
Ако имаме класа Dog, ето как би изглеждал неговия най-опростен конструктор:
Формално, декларацията на конструктора изглежда по следния начин:
Сподели с приятели: |