Исходники.Ру - Программирование
Исходники
Статьи
Книги и учебники
Скрипты
Новости RSS
Магазин программиста

Главная » Статьи по программированию » .NET - Все статьи »

Обсудить на форуме Обсудить на форуме

Новинки языка C#
В этой статье я расскажу о новшествах в языке C#, которые были привнесены в язык с выходом первой Beta версии .NET Framework 2. Основная задача статьи - рассказать разработчикам о новых замечательных возможностей, доступных при разработке программ на C#.

Введение

Когда говорят о .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("\nstrGeneric:\n" + strGeneric.Variable1 + " " + strGeneric.Variable2 + 
"\n\nintGeneric sum:\n" + intSum.ToString() +
"\n\nstrintGeneric sum:\n" + 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 и я постараюсь ответить каждому.


Может пригодится:


Автор: gaidar
Прочитано: 6861
Рейтинг:
Оценить: 1 2 3 4 5

Комментарии: (0)

Добавить комментарий
Ваше имя*:
Ваш email:
URL Вашего сайта:
Ваш комментарий*:
Код безопастности*:

Рассылка новостей
Рейтинги
© 2007, Программирование Исходники.Ру