Подразбиране на типа на променливите
Предполагам, че забелязахте в частта
Филтриране с 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;
}
|
Вече става по-ясно, нали? Както изглежда отново компилатора върши неприятната
част от работата, като генерира клас, в зависимост от данните/свойствата
от които имаме нужда. По този начин, когато се наложи да променяме класа, просто
трябва да променим инициализацията (и използването ) на класа, но не трябва да се
грижим да промяна на дефиницията.
Компилатора има и механизъм за оптимизация, който използва вече генерираният
клас ако използваме две променливи с еднакви свойства. Ако променим свойствата,
обаче, нов анонимен клас се генерира.
Следваща част: Автоматично изграждане на свойства