Кратко съдържание


Защита от неинициализирани променливи



страница41/73
Дата21.07.2018
Размер9.03 Mb.
#76887
1   ...   37   38   39   40   41   42   43   44   ...   73

Защита от неинициализирани променливи


Когато декларираме променлива в кода, C# компилаторът ни задължава да й зададем стойност преди първото й използване. Ако се опитаме да използване неинициализирана променлива (независимо дали е от стой­нос­тен или референтен тип), C# компилаторът дава грешка и отказва да компилира кода. Ето един пример:

int someVariable;

Console.WriteLine(someVariable);



При опит за компилация ще възникне грешката "Use of unassigned local variable someVariable".

Автоматична инициализация на променливите


При създаване на обект от даден тип с оператора new CLR автоматично инициализира декларираната променлива с неутрална (нулева) стойност. Ето един пример:

int i = new int();

Console.WriteLine(i);



Горният код се компилира успешно и отпечатва като резултат 0. Това се дължи на автоматичната инициализация, която операторът new извършва.

Когато заделяме структура или клас, се изпълнява и съответният кон­структор и всички член-променливи на новия обект се инициализират с нулеви стойности. Това предпазва разработчиците от проблеми свързани с неини­циали­зи­рани член-данни, които могат да бъдат много досадни, защото се проявя­ват само от време на време.

Ако само дефинираме променлива, без да създадем инстанция за нея с оператора new, ще получим грешка по време на компилация, защото про­менливата ще остане неинициализирана. Ето пример:

int i;

Console.WriteLine(i); // Use of unassigned local variable 'i'


Типът System.Object


Типът System.Object е базов за всички типове в .NET Framework. Както референтните, така и стойностните типове произлизат от System.Object или от негов наследник. Това улеснява програмиста и в много ситуации му спестява писане на излишен код.

В .NET Framework можем да напишем следния код:



string s = 5.ToString();

Този код извиква виртуалния метод ToString() от класа System.Object. Това е възможно, защото числото 5 е инстанция на типа System.Int32, който е наследник на System.Object.

Понеже всички типове са съвместими със System.Object (object в C#), защото са негови наследници, можем на инстанция на System.Object да присвояваме както референтни, така и стойностни типове:



object obj = 5;

object obj2 = new SomeClass();



Забележка: Ако не е указано друго, в C# целите числа по подразбиране са инстанции на типа System.Int32.

Защо стойностните типове наследяват референтния тип System.Object?


Ако си спомним, че System.Object е референтен тип, изглежда малко странно че стойностните типове също го наследяват. Сякаш има някакво противоречие: Как така стойностните типове, които не са указатели, произлизат от тип, който е указател?

Всъщност противоречие няма, защото архитектите на .NET Framework по изкуствен начин са направили съвместими всички стойностни типове със System.Object. За удобство в CLR всички стойностни типове могат да се преобразуват към референтни чрез операцията "опаковане". Опакова­нето и обратната му операция "разопаковане" преобразуват стойностни типове в опаковани стойностни типове и обратното. При опаковане стой­ностните типове се копират в динамичната памет и се получава указател (референция) към тях. При разопаковане стойността от динамичната памет, сочена от съответната референция, се копира в стека.

По късно в настоящата тема ще дискутираме в детайли опаковането и разопаковането на стойностни типове.

Потребителските типове скрито наследяват System.Object


При дефиниране на какъвто и да е тип, скрито от нас се наследява System.Object. Например структурата:

struct Point

{

int x, y;



}

е наследник на System.Object, макар това да не се вижда непосредствено от декларацията й.

Методите на System.Object


Като базов тип за всички .NET типове System.Object дефинира обща за всички тях функционалност. Тази функционалност се реализира в няколко метода, някои от които са виртуални и могат да бъдат припокрити:

  • bool Equals(object) – виртуален метод, който сравнява текущия обект с друг обект. Методът има и статична версия Equals(object, object), която сравнява два обекта, подадени като параметри. Обектите се сравняват не по адрес, а по съдържание. Методът често пъти се припокрива, за да се даде възможност за сравнение на потребителски обекти.

  • string ToString() – виртуален метод, който представя обекта във вид на символен низ. Имплементацията по подразбиране на ToString() отпечатва името самия тип.

  • int GetHashCode() – виртуален метод за изчисляване на хеш-код. Използва се при реализацията на някои структури от данни, например хеш-таблици. По-нататък, в темата за масиви и колекции, ще разгледаме този метод по-детайлно.

  • Finalize() – виртуален метод за имплементиране на почистващи операции при унищожаване на обект. В C# не може да се дефинира директно, а се имплементира чрез деструктора на типа. Ще разгле­даме подробности за т. нар. "финализация на обекти" в темата за управление на паметта и ресурсите.

  • Type GetType() – връща метаданни за типа на обекта във вид на инстанция на System.Type. Имплементиран е вътрешно от CLR.

  • object MemberwiseClone() – копира двоичното представяне на обекта в нов обект, т. е. извършва плитко копиране. При референтни типове създава нова референция към същия обект. При стойностни типове копира стойността на подадения обект.

  • bool ReferenceEquals() – сравнява два обекта по референция. При референтни типове се сравнява дали обектите сочат на едно и също място в динамичната памет. При стойностни типове връща false.

Предефиниране на сравнението на типове


Когато дефинираме собствен тип, често пъти се налага да се импле­ментира функционалност за сравнение на негови инстанции. В .NET Framework се препоръчва такава функционалност да се реализира чрез имплементиране на предвидените за целта методи в System.Object.

Препоръчва се методите Equals(object), operator ==, operator != и GetHashCode() да се имплементират заедно в комплект. Тази практика спестява някои доста досадни проблеми. Например ако Equals(object) е имплементиран, а операторът == не е имплементиран, потребителите на типа могат да се подведат и да извършват некоректно сравнение с ==, което по подразбиране връща резултата от метода ReferenceEquals().


Предефиниране на сравнението – пример


В настоящия пример се дефинира клас Student, който съдържа 2 инфор­ма­ционни полета (име и възраст), след което се дефинират методите за сравнение на студенти. Счита се, че два студента са един и същ, ако имат еднакви имена и възраст. Предефинират се виртуалните методи Equals(object), operator ==, operator !=, GetHashCode() и ToString() от System.Object. С цел илюстриране как се използва предефинираното сравнение в края на примера се създават няколко инстанции на Student и се сравняват една с друга.

using System;
public class Student

{

public string mName;



public int mAge;
public override bool Equals(object aObject)

{

// If the cast is invalid, the result will be null



Student student = aObject as Student;
// Check if we have valid not null Student object

if (student == null)

{

return false;



}
// Compare the reference type member fields

if (! Object.Equals(this.mName, student.mName))

{

return false;



}
// Compare the value type member fields

if (this.mAge != student.mAge)

{

return false;



}
return true;

}
public static bool operator == (Student aStudent1,

Student aStudent2)

{

return Student.Equals(aStudent1, aStudent2);



}
public static bool operator != (Student aStudent1,

Student aStudent2)

{

return ! (Student.Equals(aStudent1, aStudent2));



}
public override int GetHashCode()

{

// Return the hash code of the mName field



return mName.GetHashCode();

}
public override string ToString()

{

return String.Format(



"Student(Name: {0}, Age: {1})", mName, mAge);

}
static void Main()

{

Student st1 = new Student();



st1.mName = "Бай Иван";

st1.mAge = 68;

Console.WriteLine(st1); // Student.ToString() is called
Student st2 = new Student();

if (st1 != st2) // it is true

{

Console.WriteLine("{0} != {1}", st1, st2);



}
st2.mName = "Бай Иван";

st2.mAge = 68;

if (st1 == st2) // it is true

{

Console.WriteLine("{0} == {1}", st1, st2);



}
st2.mAge = 70;

if (st1 != st2) // it is true

{

Console.WriteLine("{0} != {1}", st1, st2);



}
if (st1 != null) // it is true

{

Console.WriteLine("{0} is not null", st1);



}

}

}



След като се изпълни примерът, се получава следния резултат:


Как работи примерът?


Методът Equals(object) е реализиран на няколко стъпки. Първо се проверява дали е подаден обект от тип Student, който не е null. Това е необходимо условие, за да е възможно равенството на подадения студент с текущия студент. След това се сравняват имената на студентите и ако съвпаднат се сравняват и годините им. Истина се връща, само ако и двете сравнения установят равенство.

Операторите == и != се имплементират чрез извикване на Equals( object).

Методът GetHashCode() връща хеш-кода на името на студента, което ще върши работа в повечето случаи. По-подробно на този метод ще се спрем в темата "Масиви и колекции".

Методът ToString() връща символен низ, съдържащ името и възрастта на студента в лесно четим формат.

В главната програма (Main() метода) се извършват серия сравнения, които демонстрират правилната работа на имплементираните методи.




Сподели с приятели:
1   ...   37   38   39   40   41   42   43   44   ...   73




©obuch.info 2024
отнасят до администрацията

    Начална страница