Технологии разработки современных информационных систем на платформе Microsoft.NET

С# 3.0

Показывать лекцию целиком

Обзор C# 3.0

C# 3.0 ("C# Orcas") представляет собой расширение C# 2.0, разработанное для создания высокоуровневых библиотек классов с использованием подходов функционального программирования. C# 3.0 позволяет создавать API, поддерживающие запросы к реляционным базам данных и XML. C# 3.0 включает следующие расширения:

  • Неявно типизированные локальные переменные.То есть тип локальной переменной может быть получен из выражения, инициализирующего эту переменную.
  • Неявно типизированные массивы.
  • Расширяющие методы.Они позволяют добавлять методы к уже существующим типам данных.
  • Лямбда-выражения.В C# 3.0 представляют собой развитие анонимных методов.
  • Инициализаторы объектов.Упрощают создание и инициализацию объектов.
  • Анонимные типы.Типы-кортежи, которые определяются на основе инициализаторов объектов.
  • Выражения, содержащие запросы.В C# добавлены выражения, аналогичные выражениям на языках запросов SQL и XQuery.
  • Деревья выражений.Позволяют представить лямбда-выражения в виде данных.
  • Спецификации C# 1.2 и 2.0 гораздо объемнее (более 100 стр.) по сравнению с C# 3.0, так как содержат описания основ языка, и доступны по ссылке http://msdn.microsoft.com/vcsharp/language.

    Неявно типизированные локальные переменные

    При объявлении неявно типизированной локальной переменной ее тип определяется из инициализирующего выражения. Например:

    var i = 5
    var numbers = new int[] {1, 2, 3}

    Тип var не был определен в коде, при этом объявления эквивалентны следующему:

    int i = 5
    int[] numbers = new int[] {1, 2, 3}

    Для неявно типизированных переменных существуют следующие ограничения:

  • объявление переменной должно включать инициализатор;
  • инициализатор должен быть выражением и не должен быть объектом или инициализатором коллекции. (Объект и коллекцию можно включить в выражение с new.)
  • тип инициализирующего выражения на момент компиляции не может быть null-типом.
  • Если объявление локальной переменной содержит несколько вложенных объявлений (например, при создании массива), все инициализаторы должны иметь один тип на момент компиляции.

    Примеры нарушения ограничений:

    var x;             // Error, no initializer to infer type from
    var y = {1, 2, 3}; // Error, collection initializer not permitted 
    var z = null;      // Error, null type not permitted

    Если объявляется локальная переменная типа var, который был определен в коде, — используется определенный в коде тип и генерируется предупреждение (warning).

    Неявно типизированные локальные переменные можно использовать в конструкциях for и foreach.

    int[] numbers = { 1, 3, 5, 7, 9 }
    foreach (var n in numbers) Console.WriteLine(n)

    Неявно типизированные массивы

    При объявлении неявно типизированного массива его тип определяется из инициализирующего выражения. В результате тип элемента массива должен быть таким, чтобы в него неявно могли быть преобразованы типы всех выражений, использованных для инициализации. Если такой тип не является null -типом, массив успешно создается, иначе происходит ошибка компиляции. Например:

    var a = new[] { 1, 10, 100, 1000 };    // int[] 
    var d = new[] { 1, "one", 2, "two" };  // Error

    В последнем случае происходит ошибка компиляции, так как невозможно неявно преобразовать int в string и наоборот.

    Зачем нужна неявная типизация?

    Неявно типизированные переменные и массивы могут быть полезны начинающим программистам, так как еще сильнее снижают требования к начальному уровню подготовки. Можно просто писать код и создавать там переменные, не зная о типах практически ничего.

    Кроме того, возможности неявной типизации могут быть полезны программистам, работающим на языках типа php, где возможности неявного определения типа были заложены изначально. Они могут получить преимущества управляемого программирования, перейдя на C# и сохраняя при этом привычный стиль программирования.

    Однако возможности неявной типизации, без сомнения, могут служить источником дополнительных ошибок и делают код менее понятным.

    Расширяющие методы

    Расширяющие методы — это статические методы статического класса A, которые расширяют уже существующий класс B и могут быть вызваны экземпляром класса B.

    Расширяющие методы содержат первым параметром переменную того типа, который данный метод расширяет. Перед типом стоит ключевое слово this. Например:

    Namespace Acme.Utilities 
    {
     public static class Extensions 
     {
      public static int ToInt32(this string s) 
      {
       return Int32.Parse(s) 
      }
      
      public static T[] Slice<T>(this T[] source, int index, int count) 
      {
       if (index < 0 || count < 0 || source.Length - index < count)
          throw new ArgumentException() 
          T[] result = new T[count] 
          Array.Copy(source, index, result, 0, count) 
          return result 
      }
     }
    }

    Расширяющие методы работают как статические и при этом могут быть вызваны из экземпляров расширяемого класса. Необходимо только указать в директиве using пространство имен, содержащее расширяющий метод. Например:

    using Acme.Utilities
    string s = "1234"
    int i = s.ToInt32();   // То же, что Extensions.ToInt32(s)
    int[] digits = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    int[] a = digits.Slice(4, 3); //То же,что Extensions.Slice(digits, 4, 3)

    Расширяющие методы, очевидно, полезны для добавления функциональности в сторонние классы. Однако они могут служить источником путаницы и дополнительных ошибок. Кроме того, расширяющие методы труднее обнаружить (в описании расширяемого класса их нет). Расширения свойств, событий или операторов пока не поддерживаются. Возможно, они появятся в C# позднее.

    Лямбда-выражения

    C# 2.0 поддерживал анонимные методы, позволяющие писать блоки кода вместо параметров-делегатов. Анонимные методы предоставляют значительную часть возможностей для функционального программирования, но при этом обладают достаточно сложным синтаксисом. Лямбда-выражения предоставляют более краткий функциональный синтаксис написания анонимных методов.

    Инициализаторы объектов и коллекций

    При создании объекта можно использовать инициализатор объекта или коллекции. Тогда при создании объекта сначала вызывается конструктор с пустым списком параметров, а затем инициализируются члены, указанные в списке инициализатора объекта или коллекции.

    Инициализаторы объектов

    Инициализатор объекта содержит последовательность инициализаторов свойств или полей объекта, заключенную в фигурные скобки. При этом нельзя инициализировать свойство или поле более одного раза. Свойство типа-значения не может быть инициализировано в инициализаторе объекта. Пусть есть класс, описывающий точку:

    public class Point
    {
    int x, y;
    public int X { get { return x; } set { x = value; } }
    public int Y { get { return y; } set { y = value; } }
    }

    Пример инициализации экземпляра класса точки с помощью инициализатора объекта:

    var a = new Point { X = 0, Y = 1 }

    Это эквивалентно:

    var a = new Point(); a.X = 0 a.Y = 1

    Инициализаторы коллекций

    Инициализатор коллекции аналогичен инициализатору объекта. Для устранения путаницы с инициализатором объекта инициализаторы элементов коллекции не могут содержать знака присваивания. Пример инициализации:

    List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }

    Объект-коллекция, созданный инициализатором коллекции, должен иметь тип, реализующий интерфейс System.Collections.Generic.ICollection<T> только для одного типа T. Более того, для каждого инициализатора элемента коллекции должно существовать неявное преобразование его типа к T, иначе происходит ошибка компиляции.

    Итак, инициализаторы объектов или коллекций сокращают количество кода, необходимого для создания объектов. Кроме того, при передаче объектов в качестве параметров метода не требуется писать код, создающий переменную, и затем передавать ее методу. Создание параметра и вызов метода можно объединить.

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

    C# 3.0 позволяет использовать оператор new с анонимным инициализатором объекта. При этом создается объект анонимного типа. Анонимный тип — это класс без имени (имя генерируется компилятором автоматически и не может быть использовано в коде), унаследованный от object. Члены анонимного класса — последовательность свойств с доступом read/write, полученные из списка инициализатора объекта. Если выражение, инициализирующее объект анонимного типа, имеет тип null, происходит ошибка компиляции. Если два инициализатора анонимных объектов содержат абсолютно одинаковые выражения инициализации (имена и порядок свойств), создаются объекты одного типа. Например:

    var p1 = new { Name = "Lawnmower", Price = 495.00 } 
    var p2 = new { Name = "Shovel", Price = 26.95 } 
    p1 = p2

    Выражения, содержащие запросы

    С# 3.0 поддерживает синтаксис для создания запросов, аналогичных запросам на языках иерархических и реляционных баз данных (например, языки SQL и XQuery).

    Запрос начинается с from- и заканчивается select- или group- выражением. За исходным from - выражением может идти 0 и более from- или where - выражений. Итоговое select - или group - выражение задает форму результата в виде переменных для последующей итерации. Перед select или group может быть конструкция orderby, задающая порядок группирования результата. Into - выражение используется для создания подзапросов. Пример использования:

    using System
    using System.Query
    using System.Collections.Generic
    class app {
     static void Main() 
     {
      string[] names = { "Burke", "Connor", "Frank"
                         "Everett", "Albert",  "George" 
                         "Harris", "David"}
      IEnumerable<string> expr = from s in names
      where s.Length == 5
      orderby s
      select s.ToUpper()
      foreach (string item in expr) Console.WriteLine(item) 
     }
    }

    Деревья выражений

    Деревья выражений позволяют представить лямбда-выражения в виде структур данных, а не исполнимого кода. Лямбда-выражение, которое преобразуется в тип-делегат D, также может быть преобразовано в дерево выражений типа System.Query.Expression<D>. Преобразование лямбда-выражений в тип-делегат порождает исполнимый код, на который ссылается делегат. Преобразование лямбда-выражений в дерево выражений порождает код, создающий экземпляр дерева выражений. Деревья выражений представляют собой эффективное представление лямбда-выражений в памяти и делают структуру выражения более прозрачной. Пример выражения в виде кода и дерева выражений:

    Func<int,int> f = x => x + 1;                    // Code
    Expression<Func<int,int>> e = x => x + 1;  // Data

    Делегат f ссылается на метод, возвращающий x+1, а дерево выражений e ссылается на структуру данных, описывающую выражение x+1.

    Итоги

    C# 3.0 представляет собой расширения C# 2.0. В него входят:

  • неявно типизированные локальные переменные;
  • неявно типизированные массивы;
  • расширяющие методы;
  • лямбда-выражения;
  • анонимные типы;
  • выражения, содержащие запросы;
  • деревья выражений.
  • Эти расширения позволяют быстрее осваивать C# тем, кто с ним не работал ранее. Кроме того, C# 3.0 предоставляет специалистам, знакомым с массой тонкостей, еще более гибкие и мощные средства. Разумеется, став более гибким, C# требует от разработчиков большего внимания к использованию своих средств, чтобы не допустить ошибок.

    Вернуться к учебному плану