CommonOperations.cs
public class CommonOperations
{
// No warning
public void Swap(ref K a, ref K b)
{
K oldA = a;
a = b;
b = oldA;
}
}
|
По този начин, винаги ще сме сигурни, че няма да има препокриване на заместителите на неизвестните типове на метода и класа.
Използването на ключовата дума default в типизиран код
След като се запознахме с основите на типизирането, нека се опитаме да преработим нашия пръв пример в тази секция – класът описващ приют за бездомни животни. Както разбрахме, единственото, което е нужно да направим е да заменим конкретния тип Dog с някакъв параметър, например T:
AnimalsShelter.cs
|
public class AnimalShelter
{
private const int DefaultPlacesCount = 20;
private T[] animalList;
private int usedPlaces;
public AnimalShelter()
: this(DefaultPlacesCount)
{
}
public AnimalShelter(int placesCount)
{
this.animalList = new T[placesCount];
this.usedPlaces = 0;
}
public void Shelter(T newAnimal)
{
if (this.usedPlaces >= this.animalList.Length)
{
throw new InvalidOperationException("Shelter is full.");
}
this.animalList[this.usedPlaces] = newAnimal;
this.usedPlaces++;
}
public T Release(int index)
{
if (index < 0 || index >= this.usedPlaces)
{
throw new ArgumentOutOfRangeException(
"Invalid cell index: " + index);
}
T releasedAnimal = this.animalList[index];
for (int i = index; i < this.usedPlaces - 1; i++)
{
this.animalList[i] = this.animalList[i + 1];
}
this.animalList[this.usedPlaces - 1] = null;
this.usedPlaces--;
return releasedAnimal;
}
}
|
Всичко изглежда наред, докато не се опитаме да компилираме класа. Тогава получаваме следната грешка:
Cannot convert null to type parameter 'T' because it could be a non-nullable value type. Consider using 'default(T)' instead.
|
Грешката е в метода Release() и е свързана със записването на резултат null в освободената последна (най-дясна) клетка на приюта. Проблемът е, че се опитваме да използваме подразбиращата се стойност за референтен тип, но не сме сигурни, дали конкретния тип е референтен или примитивен. Тъкмо затова, компилаторът извежда гореописаните грешки. Ако типът AnimalShelter се инстанцира по структура, а не по клас, то стойността null е невалидна.
За да се справим с този проблем, трябва в нашия код, вместо null, да използваме конструкцията default(T), която връща подразбиращата се стойност за конкретния тип, който ще бъде използван на мястото на T. Както знаем подразбиращата стойност за референтен тип е null, а за числови типове – нула. Можем да направим следната промяна:
// this.animalList[this.usedPlaces - 1] = null;
this.animalList[this.usedPlaces - 1] = default(T);
|
Едва сега компилацията минава без проблем и класът AnimalShelter<Т> работи коректно. Можем да го тестваме например по следния начин:
static void Main()
{
AnimalShelter shelter = new AnimalShelter();
shelter.Shelter(new Dog());
shelter.Shelter(new Dog());
shelter.Shelter(new Dog());
Dog d = shelter.Release(1); // Release the second dog
Console.WriteLine(d);
d = shelter.Release(0); // Release the first dog
Console.WriteLine(d);
d = shelter.Release(0); // Release the third dog
Console.WriteLine(d);
d = shelter.Release(0); // Exception: invalid cell index
}
|
Предимства и недостатъци на типизирането
Типизирането на класове и методи води до по-голяма преизползваемост на кода, по-голяма сигурност и по-голяма ефективност, в сравнение с алтернативните нетипизирани решения.
Като генерално правило, програмистът трябва да се стреми към типизиране на класовете, които създава винаги, когато е възможно. Колкото повече се използва типизиране, толкова повече нивото на абстракция в програмата се покачва, както и самият код става по-гъвкав и преизползваем. Все пак трябва да имаме предвид, че прекалената употреба на типизиране може да доведе до прекалено генерализиране и кодът може да стане нечетим и труден за разбиране от други програмисти.
Ръководни принципи при именуването на заместителите при типизиране на класове и методи
Преди да приключим с темата за типизирането, нека дадем някои указания при работата със заместителите (параметрите) на непознатите типове в един типизиран клас:
1. Когато при типизирането имаме само един непознат тип, тогава е общоприето да се използва буквата T, като заместител за този непознат тип. Като пример можем да вземем декларацията на нашия клас AnimalShelter, който използвахме до сега.
2. На заместителите трябва да се дават възможно най-описателните имена, освен ако една буква не е достатъчно описателна и добре подбрано име, не би подобрило по никакъв начин четимостта на кода. Например, можем да модифицираме нашия пример, заменяйки буквата T, с по-описателния заместител Animal:
AnimalShelter.cs
|
public class AnimalShelter
{
// ... rest of the code ...
public void Shelter(Animal newAnimal)
{
// Method body here
}
public Animal Release(int i)
{
// Method body here
}
}
|
Когато използваме описателни имена на заместителите, вместо буква, е добре да добавяме T, в началото на името, за да го разграничаваме по-лесно от имената на класовете в нашата програма. С други думи, вместо в предходния пример да използваме заместител Animal, е добре да използваме TAnimal.
Сподели с приятели: |