Подразбиране на типа на променливите

Предполагам, че забелязахте в частта Филтриране с LINQ и C# 3.0, че използвахме ключовата дума var вместо познатите ни типове. В тази и последващата част, че обясним подробно защо.

Като разработчици на C# знаем, че променливи се декларират по този начин

<име на типа> <име на променлива> [ = инициализация];

Където:

·         <име на типа> - тип на променливата (string, int, SqlCommand или друго име на клас), който показва какви данни ще се съдържат в декларираната променлива

·         <име на променлива> - наименование на променливата, което ще използваме по-късно в кода. Има много литература, които описват как трябва да се кръщават променливите и всички се съгласяват, че името трябва да е показва значението на данните, които се съхраняват в променливата. (Пример за добри имена – customerDiscount, databaseConnection. Лоши имена – alabala, rf0j;)

·         [ = инициализация]това е частта, където се извикват конструкторите на класовете или данните на променливите.

Както, сигурно, се досещате ключовата дума var замества името на типа. Това означава, че променливите декларирани по този начин:

var text1 = "This is text. ";
var num = 12;

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

string text1 = "This is text. ";
int num = 12;

Тук по-напредналите читатели може би си задават вълна от въпроси: Не е ли същото ако вместо var използваме object? Или Variant за дните на VB6 и COM? А какво стана с идеята за силно типизираните променливи?

Отговорите на тези въпроси ще потърсим с вече познатият ни инструмент Reflector. Нека да декомпилираме асембли, в което сме декларирали променливи с var. За да направим нещата по-интересни ще добавим декларация на клас:

var text1 = "This is text. ";
var num = 12;
var connection = new SqlConnection("connection string here");

След компилацията (с компилатора от LINQ Preview (May 2006)) и разглеждане на асемблито с Reflector, виждаме следното:

string text1 = "This is text. ";
int num = 12;
SqlConnection connection = new SqlConnection("connection string here");

Интересно... Както бихме го написали и в C#2.0. Това означава, че компилатора разпознава данните и поставя правилният тип. Това действия не е по време на изпълнение (за разлика от Variant) или по време на създаване на приложението (design time), а по време на компилация.

По тази причина, когато се използва var променливите трябва да се инициализират на същият ред. В противен случай компилаторът няма да има представа за данните, които ще се съхраняват. Тази малка хитрина само привидно размива идеята за силно типизирани променливи.

Както изглежда тази нова езикова конструкция въвежда голямо улеснение за разработчиците. Ако си представим метод с двадесетина променливи, декларирани с var, ще е трудно да запомним типа на всяка една от тях, а и няма да може да го прочетем ведната, а трябва да погледнем и инициализиращата секция. Това би направило кода нечетим.

Декларирането на променливи с ключовата дума var трябва да става само за Анонимни типове и колекции връщани от методите на LINQ. Ненужното използване може да довете до намаляване качеството на програмният код и последваща промяна (maintenance).

На пръв поглед тази малка ключова думичка има много малък принос. Но това е само на пръв поглед, както ще видим в следващата част – Анонимни типове.

Анонимни типове

Характерно за програмните езици до момента е следният начин за работа с данни:

·         Дефиниране на типовете данни – деклариране на класове, структури

·         Инициализиране на променлива от тип дефиниран в предходната точка

·         Използване на данните – попълване, четене, промяна на свойствата на обектите.

По този начин се създават обектите в т.нар бизнес слой (business layer). Както винаги, обаче, нещата не спират до тук и следва не малко усложнение – данните се съхраняват в отделен слой (SQL Server), който използва различни начини за съхранение на данните. Това налага свързване между данните в слоя за съхранение (data tier) с бизнес слоя. (По-късно ще разгледаме подробно как се осъществява това свързване с помощта на C# 3.0 и Visual Studio Codename “Orcas”).

Не бива да се пренегрегва момента, в който се налага да се променят използваните данни. Тогава се налага промяна на дефинираните класове и кода, където се използват те. А ако са свързани със слой за съхранение, то тогава се налагат промени и в него.

Забележка: По-често в практиката промените в бизнес слоя се налагат заради промени слоя с данни, но за целта на абстракцията разглеждаме двата варианта – с и без слой за съхранение.

Както може да се предположи, изискването за някаква промяна в данните на приложението изисква множество промени в кода. Докато не може да се избяга (все още) от промяната на потребителския интерфейс, в зависимост от данните, то анонимните типове предоставят удобен и лесен начин за работа с данни, които често се променят.

За момента ще представим как се постига това, а практическата полза ще излезе наяве, когато разглеждаме LINQ в детайли.

Нека се върнем на предишният пример с класа Customer. С помощта на новите езикови конструкции може да се инициализира по този начин:

Customer cust = new Customer { Id=1, Name="John Atanasov" };

Това улеснява много инициализацията на обекта, но все още изисква декларация на класа Customer. Ако искаме да използваме данни за служител без да дефинираме клас за него това може да стане така:

var employee = new {Id=15, Name="Galin", Experience = 8, ManagerId=2};

и може да използваме така създадената и инициализирана променлива в същият метод:

Console.WriteLine(employee.Name);

или по начин, който намерим за добре. Това е изключително удобно в LINQ заявки, които сортират, групират и връщат нови структури от данни. Ако трябва да се дефинират предварително всички класове, това би отнело много време. (виж примера от Групиране с LINQ и C# 3.0)

Нека да навлезем в дълбините на тази функционалност и да видим как това става възможно. Отново използваме Reflector и при разглеждане на компилираното асембли откриваме следният клас:

[CompilerGenerated]

public sealed class <Projection>f__0

{

      // Methods

      public <Projection>f__0();

      public override bool Equals(object);

      public override int GetHashCode();

      public override string ToString();

  

      // Properties

      public int Experience { get; set; }

      public int Id { get; set; }

      public int ManagerId { get; set; }

      public string Name { get; set; }

  

      // Fields

      private int _Experience;

      private int _Id;

      private int _ManagerId;

      private string _Name;

}

Вече става по-ясно, нали? Както изглежда отново компилатора върши неприятната част от работата, като генерира клас, в зависимост от данните/свойствата от които имаме нужда. По този начин, когато се наложи да променяме класа, просто трябва да променим инициализацията (и използването ) на класа, но не трябва да се грижим да промяна на дефиницията.

Компилатора има и механизъм за оптимизация, който използва вече генерираният клас ако използваме две променливи с еднакви свойства. Ако променим свойствата, обаче, нов анонимен клас се генерира.

 

Следваща част: Автоматично изграждане на свойства