Введення

Коли говорять про. NET мовах, перш за все мають на увазі C # – мова
з'явився разом з першою версією. NET Framework і є основним
мовою. NET. Чому це там можна вести тривалі суперечки і приводити
безліч аргументів, але основним з них буде те, що мову спеціально
створювався під нову платформу, тому з моменту свого народження він
підтримував всі концепції платформи. NET.

У цій статті я розповім про нововведення в мові C #, які були
привнесені в мову з виходом першої Beta версії. NET Framework 2.
Основне завдання статті – розповісти розробникам про нових чудових
можливостей, доступних при розробці програм на C #.

Зовнішні псевдоніми

Зовнішні псевдоніми (external aliases) дозволяють використовувати
різні збірки з однаковими просторами імен як різні
простору імен. Звучить заплутано, але насправді ідея проста,
кожної окремої збірці призначається своє глобальний простір імен.
Наприклад у нас є дві збірки Functions.dll і Globals.dll, кожна з
яких містить простір імен PublicFunctions.

Збірка Functions.dll
namespace PublicFunctions
{
 public class Functionality
 {
/ / Функції складання Functions.dll
 }
}

Збірка Globals.dll
namespace PublicFunctions
{
 public class Functionality
 {
/ / Функції складання Globals.dll
 }
}

Як бачите, при використанні двох цих збірок може виникнути
конфлікт, тому ми призначаємо кожної з зборок "зовнішній простір
імен "за яким і будемо звертатися до функцій тієї чи іншої збірки.

extern alias Functions;
extern alias Globals;

public class Main
{
  public Main()
  {
    Functions.PublicFunctions.Functionality.SomeMethod();
    Globals.PublicFunctions.Functionality.AnotherMethod();
  }
}

Специфікатор до псевдонімом

Як деяке доповнення та розширення попереднього параграфа статті
потрібно сказати про нову можливості по роботі з псевдонімами для
просторів імен. Наприклад, щоб звернутися до глобального простору
імен можна використовувати синтаксис global:: namespace.

global::Microsoft.Win32;

А для того, щоб звернутися до псевдоніму простору імен, а не
однойменним класу треба використовувати синтаксис
MyAlias::MySubNamespace.SomeClass.

using Win32 = Microsoft.Win32;
// ...
Win32::SystemEvents.CreateTimer(100);

Загальні типи (Generics)

Загальні (або параметризрвані) типи (generics) Дозволяють
при описі класів, структур, методів та інтерфейсів використовувати
параметризрвані параметри (не вказувати тип параметра в момент
написання коду). Тип параметра визначається в момент оголошення
змінної відповідного типу. Таким чином можна створити певний
загальний елемент, тип який можна використовувати в подальшому для даних
різних типів. Програмісти на C + + можуть вгледіти з загальних типах
схожість з шаблонами (templates), В чомусь ця аналогія буде
вірна, але тут існують деякі обмеження, які ми розглянемо
трохи пізніше.

Пояснення вийшло кілька сумбурним, тому буде краще
розглянути прості і зрозумілі приклади. Перш за все створимо клас
використовуючи загальні типи:

class Generics<TYPE1, TYPE2>
{
  private TYPE1 mVar1;
  private TYPE2 mVar2;

  public Generics(TYPE1 Var1, TYPE2 Var2)
  {
    this.mVar1 = Var1;
    this.mVar2 = Var2;
  }
  public string ToStringFunction(string Delemiter)
  {
return this.mVar1.ToString () + Delemiter + this.mVar2.ToString ();
  }

  public TYPE1 Variable1
  {
    get
    {
      return this.mVar1;
    }
    set
    {
      this.mVar1 = value;
    }
  }

  public TYPE2 Variable2
  {
    get
    {
      return this.mVar2;
    }
    set
    {
      this.mVar2 = value;
    }
  }
}

Як видно з прикладу, для того щоб використовувати загальні типи потрібно
після оголошення класу вказати параметризрвані типи: Generics <TYPE1,
TYPE2> оголошує клас з двома параметризованих типами. Тепер
використовуємо написаний клас:

/ / Оголошення
Generics <string, string> strGeneric = new Generics <string, string> ("Hello", "world");
Generics <int, int> intGeneric = new Generics <int,int> (1, 2);
Generics <string, int> strintGeneric = new Generics <string,int> ("Three", 3);
int intSum;
string strSum;

/ / Використання
intSum = intGeneric.Variable1 + intGeneric.Variable2;
strSum = strintGeneric.Variable1 + "" + strintGeneric.Variable2.ToString ();

MessageBox.Show("
strGeneric:"+ StrGeneric.Variable1 +" "+ strGeneric.Variable2 + 
"

intGeneric sum:
" + intSum.ToString() +
"

strintGeneric sum:
" + strSum.ToString());

Таким чином очевидно, що створено клас який можна використовувати з
параметрами різних типів, які встановлюються в момент оголошення
класу. Аналогічним чином можна оголосити структуру або інтерфейс:

public struct GenericStruct<TYPE>
{
  public TYPE someField;
}
public interface IGeneric<TYPE>
{
  TYPE SomeMethod();
  TYPE AnotherMethod();
}

Більш того, параметризрвані типи можуть бути використані при
оголошенні делегатів функцій. Продемонструю цю можливість використовуючи
оголошений вищий клас Generics.

/ / Оголошуємо делегат
public delegate DELEGATETYPE GenericDelegate <DELEGATETYPE, PARAMTYPE> (PARAMTYPE Param);
/ / Використовуємо делегат
Generics <string, string> strGeneric = new Generics <string, string> ("Hello", "world");
GenericDelegate<string, string> genDelegate = 
new GenericDelegate <string, string> (strGeneric.ToStringFunction);
/ / Виклик делагата
MessageBox.Show(genDelegate(" my "));

Переваги використання загальних типів

"Вау!" – Вигукнуть програмісти на C + + використовують у своїй роботі
також і C #. "І що нам з того?" – Скажуть програмісти на C # ніколи не
працювали з шаблонами С + +. Які ж переваги дає використання
загальних типів?

  1. Найбільш очевидне – повторне використання коду. Ні
    необхідності створювати два ідентичних класу, які відрізняються лише
    типами параметрів, досить створити один з параметризованих
    типами. При цьому використання параметризованих типів дозволяє
    створювати єдиний програмний код для роботи з різними типами
    даних. Наприклад, одного разу написаний алгоритм може працювати і з
    цілими числами і з числами з плаваючою десятковою крапкою, при цьому
    не виробляючи на кожному кроці перевірку / приведення типу. Так Generics
    витісняють класи оголошені з використанням типу object.
  2. Підвищення продуктивності коду в порівнянні з використання
    параметрів типу object – немає необхідності виконувати приведення, як
    вже сказано вище, на кожному кроці, за рахунок чого виходить виграш у
    продуктивності.
  3. Перевірка типів у момент компіляції програми. Оскільки не
    використовуються параметри типу object, то компілятор може виконати
    перевірку типу кожного параметра в момент компіляції, оскільки типи
    для Generic класів жорстко задаються в момент оголошення змінних
    класів цього типу.

На жаль, досвідчених програмістів на C + + я повинен дещо
засмутити. Загальні типи все-таки не відповідають шаблонах у C + +,
оскільки параметризрвані типи в C # не можуть мати типів за умовчанням.
Параметризрвані типи не можуть бути використані в якості базових
класів для загальних типів. Також в C # не допускається використання Generic
класів в якості параметрів типів в інших Generic класах.

Але, незважаючи на це, загальні типи все-таки досить корисним
нововведенням мови C #, особливо цінним і зручним для розробників
використовують математичні алгоритми, оскільки переваги
використання Generic класів очевидні.

Статичні класи

Статичні класи (static) – класи містять тільки статичні
функції. Наприклад, певний клас дозволяє отримати доступ до
налаштувань програми зберігаються в реєстрі або в базі даних може бути
оголошений як:

public static class AppSettings
{

  public static string BaseDir
  {

  }
  public static string GetRelativeDir
  {

  }
/ / І т.д. і т.п.
}

Примірник такого класу не може бути створений з використанням
оператора new, оскільки всі члени цього класу статичні і доступні з
використанням імені цього класу, наприклад AppSettings.BaseDir і т.п.

Поділ класів

Поділ класів (partial types) дозволяє розбивати код класу на
кілька різних частин. Наприклад, ми маємо клас User:

public class User
{
  private int mInt;
  private long mLong;
  private string mString;
  private bool mBool;

  public int MyInt{
    get{return this.mInt;}
    set{this.mInt = value;}
  }
}

Ми можемо розбити його на декілька частин використовуючи ключове слово
partial:

public partial class User
{
/ / Тут міститься генерований код
  private int mInt;
  private long mLong;
  private string mString;
  private bool mBool;
}

public partial class User
{
/ / Тут міститься код, написаний вручну
  public int MyInt{
    get{return this.mInt;}
    set{this.mInt = value;}
  }
}

Для компілятора ці два способи опису класу рівнозначні. Тоді
навіщо потрібна така можливість розділяти клас? Перш за все для
відділення коду створюваного генератором коду і розробником, як
наприклад це зроблено для підтримки дизайнера форма в Visual Studio 2005.
Якщо ви звернете увагу, то помітите, що код має в своєму розпорядженні елементи
на формі за замовчуванням прихований і відділений від код написаного вами якраз з
використанням ключового слова partial.

Також подібна можливість може бути використана для розділення
функцій класу по декількох файлах, що дозволить одночасно працювати
з різними частинами класу декільком розробникам.

Ітератори

Загальновідомо, що для того щоб перебрати всі елементи в деякій
колекції використовується метод foreach. Для тих, хто ніколи не створював
власні колекції дозволяють перебирати елементи цей механізм був
невідомий, тому варто описати процес створення колекції,
підтримуючої послідовний перебір елементів за допомогою синтаксису
foreach.

Для того, щоб колекція підтримувала foreach необхідно реалізувати
метод GetEnumerator, який повертає спеціальний клас за допомогою якого
проводиться визначення порядку виведення елементів. Для прикладу створимо
колекцію, при проході за допомогою foreach якої, елементи повертаються
в порядку їх розташування в масиві.

public class MyUsualCollection
{
 public int[] myItems;

 public MyUsualCollection()
 {
   myItems = new int[10] { 1,2,3,4,5,6,7,8,9,10 };
 }

 public MyUsualEnumerator GetEnumerator()
 {
   return new MyUsualEnumerator(this);
 }
}

/ / Клас Enumerator для нашої колекції
public class MyUsualEnumerator
{
 int indexEnum;
 MyUsualCollection myCol;

 public MyUsualEnumerator(MyUsualCollection col)
 {
   this.myCol = col;
   indexEnum = -1;
 }

 public bool MoveNext()
 {
indexEnum + +; / / переміщаємося далі
   return (indexEnum < this.myCol.myItems.GetLength(0));
 }

 public int Current
 {
   get
   {
     return (this.myCol.myItems[indexEnum]);
   }
 }
}

Очевидно, що для такої простої операції занадто багато коду.
Тому-то в C # з виходом Visual Studio 2005 і Framework 2 з'явився
більш простий шлях підтримки перебору елементів. Того ж результату ми
доб'ємося написавши наступний код:

public class MyIteratorCollection
{
  public int[] myItems;

  public MyIteratorCollection()
  {
    myItems = new int[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  }

  public IEnumerator GetEnumerator()
  {
    for (int i = 0; i < 10; i++)
      yield return myItems[i];
  }
}

Погодьтеся, коли компілятор бере вашу роботу на себе це приємно!

Безіменні методи

Безіменні методи (anonymous methods) дозволяють значно скоротити
обсяг коду, який повинен написати розробник. Найбільш просте і
зрозуміле використання безіменних методів при призначенні щодо
простих обробників подій. Розглянемо приклад, нехай у нас є форма,
на якій розміщені текстове поле txtLogin і кнопка btnLoginі нам
потрібно, щоб при зміні тексту в текстовому полі, змінювався текст
кнопки. Зрозуміло, що для цього необхідно в обробнику події
TextChanged змінювати текст кнопки.

Який код створює Visual Studio при додаванні нового обробника
події? Перш за все функцію обробник і запис про відповідність функції
обробника події, що здійснюється наданням відповідного
делегата відповідному події контрола TextBoxв функції
InitializeComponent:

this.txtLogin.TextChanged + = new System.EventHandler (this.txtLogin_TextChanged);

Сам обробник виглядає так:

private void txtLogin_TextChanged(object sender, EventArgs e)
{
btnLogin.Text = "Login [" + txtLogin.Text + "]";
} 

Ті ж операції можна виконати вручну створивши, наприклад, такий код:

public frmMain()
{
  InitializeComponent();
this.txtLogin.TextChanged + = new System.EventHandler (this.txtLogin_TextChanged);
}
private void txtLogin_TextChanged(object sender, EventArgs e)
{
btnLogin.Text = "Login [" + txtLogin.Text + "]";
} 

Тепер же перепишемо цей код з використанням безіменних методів:

public frmMain()
{
  InitializeComponent();
this.txtLogin.TextChanged + = delegate {btnLogin.Text = "Login [" + txtLogin.Text + "]";};
}

Таким чином ми помістили код обробника безпосередньо в місце
присвоювання делегата події TextChanged. Формально цей процес нічого
не міняє – у момент компіляції компілятор сам створить функцію і
присвоїть їй деякий ім'я, а потім створить делегат для цієї функції і
привласнить його події TextChanged. Але відзначте, на скільки менше рядків
коду потрібно для реалізації, при цьому жодних втрат продуктивності
після компіляції не буде, оскільки в результаті буде отриманий такий же
код, що і в прикладах описаних вище.

Звичайно, наведений приклад практичної цінності не має (а якщо і
має, але досить і досить невелику), але демонструє загальний шлях
використання безіменних методів. При цьому ніщо не заважає отримати
доступ до переданих функції параметрах, для цього потрібно лише явно
вказати їх під час написання безіменного методу:

this.btnLogin.Click += delegate(object sender, EventArgs e) 
{MessageBox.Show(((Button)sender).Text.ToString());};

На додаток наведу загальний приклад використання безіменних
методів:

public delegate int MyDelegate(int mInt1, int mInt2);

private void btnAnonymous_Click(object sender, EventArgs e)
{
  MyDelegate anSum = delegate(int a, int b) { return a + b; };
  MessageBox.Show(anSum(1, 2).ToString());
}

У прикладі новий делегат створюється безпосередньо в обробнику
події клацання по кнопці.

Нововведення в поведінці властивостей

У C # з виходом. NET Framework 2 і Visual Studio 2005возможно
обмежувати видимість get і set блоків властивостей класів, наприклад:

public partial class User
{
  public int MyInt{
    get{return this.mInt;}
    private set{this.mInt = value;}
  }
}

Таким чином тільки всередині класу можна буде встановити властивість
MyInt, "ззовні" така можливість буде недоступна. Такий підхід спрощує
код доступу до властивостей, більше немає необхідності звертатися до
внутрішньої змінної, досить зробити блок set недоступним для
"Зовнішнього впливу" і звертатися до цієї змінної використовуючи
властивість.

Висновок

У цій статті я розповів про деякі нововведення в мові C #. Звичайно,
ця стаття не претендує на повноту викладу і опису перерахованих
новинок. Мета статті – дати інформацію про нові можливості доступних
розробнику. За детальним описом я раджу звернутися до
відповідним монографіям присвяченим C # і MSDN.

Я з радістю прийму пропозиції та коментарі читачів! Пишіть
gaidar at vbstreets.ru і
я постараюся відповісти кожному.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*