355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Герберт Шилдт » C# 4.0: полное руководство » Текст книги (страница 55)
C# 4.0: полное руководство
  • Текст добавлен: 6 апреля 2017, 04:00

Текст книги "C# 4.0: полное руководство"


Автор книги: Герберт Шилдт



сообщить о нарушении

Текущая страница: 55 (всего у книги 83 страниц)

Обнуляемые объекты, операторы отношения и логические операторы

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

byte? lower = 16;

byte? upper = null;

// Здесь переменная lower определена, а переменная upper не определена,

if(lower < upper) // ложно %

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

if(lower > upper) // .. также ложно!

Следовательно, если один или оба сравниваемых обнуляемых объекта оказываются пустыми, то результат их сравнения всегда будет ложным. Это фактически означает, что пустое значение (null) не участвует в отношении порядка.

Тем не менее с помощью операторов == и != можно проверить, содержит ли обнуляемый объект пустое значение. Например, следующая проверка вполне допустима и дает истинный результат.

if(upper == null) // ...

Если в логическом выражении участвуют два объекта типа bool?, то его результат может иметь одно из трех следующих значений: true (истинное), false (ложное) или null (неопределенное). Ниже приведены результаты применения логических операторов & и | к объектам типа bool?.

И наконец, если логический оператор ! применяется к значению типа bool?, которое является пустым (null), то результат этой операции будет неопределенным (null).

Частичные типы

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

Если модификатор partial используется для создания частичного типа, то он принимает следующую общую форму:

partial тип имя_типа {//...

где имя_типа обозначает имя класса, структуры или интерфейса, разделяемого на части. Каждая часть получающегося частичного типа должна указываться вместе с модификатором partial.

Рассмотрим пример разделения простого класса, содержащего координаты ХУ, на три отдельных файла. Ниже приведено содержимое первого файла.

partial class XY {

  public XY(int a, int b) {

    X = a;

    Y = b;

  }

}

Далее следует содержимое второго файла.

partial class XY {

  public int X { get; set; }

}

И наконец, содержимое третьего файла.

partial class XY {

  public int Y { get; set; }

}

В приведенном ниже файле исходного текста программы демонстрируется применение класса XY.

// Продемонстрировать определения частичного класса.

using System;

class Test {

  static void Main() {

    XY xy = new XY(1, 2);

    Console.WriteLine(xy.X + "," + xy.Y);

  }

}

Для того чтобы воспользоваться классом XY, необходимо включить в компиляцию все его файлы. Так, если файлы класса XY называются xy1.cs, ху2.cs и хуЗ.cs, а класс Test содержится в файле test.cs, то для его компиляции достаточно ввести в командной строке следующее.

csc test.cs xyl.cs xy2.cs xy3.cs

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


Частичные методы

Как пояснялось в предыдущем разделе, с помощью модификатора partial можно создать класс частичного типа. Начиная с версии 3.0, в C# появилась возможность использовать этот модификатор и для создания частичного метода в элементе данных частичного типа. Частичный метод объявляется в одной его части, а реализуется в другой. Следовательно, с помощью модификатора partial можно отделить объявление метода от его реализации в частичном классе или структуре.

Главная особенность частичного метода заключается в том, что его реализация не требуется! Если частичный метод не реализуется в другой части класса или структуры, то все его вызовы молча игнорируются. Это дает возможность определить, но не востребовать дополнительные, хотя и не обязательные функции класса. Если эти функции не реализованы, то они просто игнорируются.

Ниже приведена расширенная версия предыдущей программы, в которой создается частичный метод Show(). Этот метод вызывается другим методом, ShowXY(). Ради удобства все части класса XY представлены в одном файле, но они могут быть распределены по отдельным файлам, как было показано в предыдущем разделе.

// Продемонстрировать применение частичного метода.

using System;

partial class XY {

  public XY(int a, int b) {

    X = a;

    Y = b;

  }

  // Объявить частичный метод,

  partial void Show();

}

partial class XY {

  public int X { get; set; }

    // Реализовать частичный метод,

    partial void Show() {

      Console.WriteLine(«{0}, {1}», X, Y);

  }

}

partial class XY {

  public int Y { get; set; }

  // Вызвать частичный метод,

  public void ShowXY() {

    Show();

  }

}

class Test {

  static void Main() {

    XY xy = new XY(1, 2);

    xy.ShowXY();

  }

}

Обратите внимание на то, что метод Show() объявляется в одной части класса XY, а реализуется в другой его части. В реализации этого метода выводятся значения координат X и Y. Это означает, что когда метод Show() вызывается из метода ShowXY(), то данный вызов действительно имеет конкретные последствия: вывод значений координат X и Y. Но если закомментировать реализацию метода Show(), то его вызов из метода ShowXY() ни к чему не приведет.

Частичным методам присущ ряд следующих ограничений. Они должны возвращать значение типа void. У них не может быть модификаторов доступа и они не могут быть виртуальными. В них нельзя также использовать параметры out.


Создание объектов динамического типа

Как уже упоминалось не раз, начиная с главы 3, C# является строго типизированным языком программирования. Вообще говоря, это означает, что все операции проверяются во время компиляции на соответствие типов, и поэтому действия, не поддерживаемые конкретным типом, не подлежат компиляции. И хотя строгий контроль типов дает немало преимуществ программирующему, помогая создавать устойчивые и надежные программы, он может вызвать определенные осложнения в тех случаях, когда тип объекта остается неизвестным вплоть до времени выполнения. Нечто подобное может произойти при использовании рефлексии, доступе к COM-объекту или же в том случае, если требуется возможность взаимодействия с таким динамическим языком, как, например, IronPython. До появления версии C# 4.0 подобные ситуации были трудноразрешимы. Поэтому для выхода из столь затруднительного положения в версии C# 4.0 был внедрен новый тип данных под названием dynamic.

За одним важным исключением, тип dynamic очень похож на тип object, поскольку его можно использовать для ссылки на объект любого типа. А отличается он от типа object тем, что вся проверка объектов типа dynamic на соответствие типов откладывает до времени выполнения, тогда как объекты типа object подлежат этой проверке во время компиляции. Преимущество откладывания подобной проверки до времени выполнения состоит в том, что во время компиляции предполагается, что объект типа dynamic поддерживает любые операции, включая применение операторов, вызовы методов, доступ к полям и т.д. Это дает возможность скомпилировать код без ошибок. Конечно, если во время выполнения фактический тип, присваиваемый объекту, не поддерживает ту или иную операцию, то возникнет исключительная ситуация во время выполнения.

В приведенном ниже примере программы применение типа dynamic демонстрируется на практике.

// Продемонстрировать применение типа dynamic,

using System;

using System.Globalization;

class DynDemo {

  static void Main() {

    // Объявить две динамические переменные,

    dynamic str;

    dynamic val;

    // Поддерживается неявное преобразование в динамические типы.

    // Поэтому следующие присваивания вполне допустимы,

    str = «Это строка»;

    val = 10;

    Console.WriteLine("Переменная str содержит: " + str);

    Console.WriteLine("Переменная val содержит: " + val + 'n');

    str = str.ToUpper(CultureInfo.CurrentCulture);

    Console.WriteLine("Переменная str теперь содержит: " + str);

    val = val + 2;

    Console.WriteLine("Переменная val теперь содержит: " + val + 'n');

    string str2 = str.ToLower(CultureInfo.CurrentCulture);

    Console.WriteLine("Переменная str2 содержит: " + str2);

    // Поддерживаются неявные преобразования из динамических типов.

    int x = val * 2;

    Console.WriteLine("Переменная x содержит: " + x);

  }

}

Выполнение этой программы дает следующий результат.

Переменная str содержит: Это строка

Переменная val содержит: 10

Переменная str теперь содержит: ЭТО СТРОКА

Переменная val теперь содержит: 12

Переменная str2 содержит: это строка

Переменная х содержит: 24

Обратите внимание в этой программе на две переменные str и val, объявляемые с помощью типа dynamic. Это означает, что проверка на соответствие типов операций с участием обеих переменных не будет произведена во время компиляции. В итоге для них оказывается пригодной любая операция. В данном случае для переменной str вызываются методы ToUpper() и ToLower() класса String, а переменная участвует в операциях сложения и умножения. И хотя все перечисленные выше действия совместимы с типами объектов, присваиваемых обеим переменным в рассматриваемом здесь примере, компилятору об этом ничего не известно – он просто принимает. И это, конечно, упрощает программирование динамических процедур, хотя и допускает возможность Появления ошибок в подобных действиях во время выполнения.

В разбираемом здесь примере программа ведет себя "правильно" во время выполнения, поскольку объекты, присваиваемые упомянутым выше переменным, поддерживают действия, выполняемые в программе. В частности, переменной val присваивается целое значение, и поэтому она поддерживает такие целочисленные операции, как сложение. А переменной str присваивается символьная строка, и поэтому она поддерживает строковые операции. Следует, однако, иметь в виду, что ответственность за фактическую поддержку типом объекта, на который делается ссылка, всех операций над данными типа dynamic возлагается на самого программирующего. В противном случае выполнение программы завершится аварийным сбоем.

В приведенном выше примере обращает на себя внимание еще одно обстоятельство: переменной типа dynamic может быть присвоен любой тип ссылки на объект благодаря неявному преобразованию любого типа в тип dynamic. Кроме того, тип dynamic автоматически преобразуется в любой другой тип. Разумеется, если во время выполнения такое преобразование окажется неправильным, то произойдет ошибка при выполнении. Так, если добавить в конце рассматриваемой здесь программы следующую строку кода:

bool b = val;

то возникнет ошибка при выполнении из-за отсутствия неявного преобразования типа int (который оказывается типом переменной val во время выполнения) в тип bool. Поэтому данная строка кода приведет к ошибке при выполнении, хотя она и будет скомпилирована безошибочно.

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

Для того чтобы стало понятно, насколько тип dynamic способен упростить решение некоторых задач, рассмотрим простой пример его применения вместе с рефлексией. Как пояснялось в главе 17, чтобы вызвать метод для объекта класса, получаемого во время выполнения с помощью рефлексии, можно, в частности, обратиться к методу Invoke(). И хотя такой способ оказывается вполне работоспособным, нужный метод намного удобнее вызвать по имени в тех случаях, когда его имя известно. Например, вполне возможна такая ситуация, когда в некоторой сборке содержится конкретный класс, поддерживающий методы, имена и действия которых заранее известны. Но поскольку эта сборка подвержена изменениям, то приходится постоянно убеждаться в том, что используется последняя ее версия. Для проверки текущей версии сборки можно, например, воспользоваться рефлексией, сконструировать объект искомого класса, а затем вызвать методы, определенные в этом классе. Теперь эти методы можно вызвать по имени с помощью типа dynamic, а не метода Invoke(), поскольку их имена известны.

Разместите сначала приведенный ниже код в файле с именем MyClass.cs. Этот код будет динамически загружаться посредством рефлексии.

public class DivBy {

  public bool IsDivBy(int a, int b) {

    if ( (a % b) == 0) return true;

    return false;

  }

  public bool IsEven(int a) {

    if ( (a % 2) == 0) return true;

    return false;

  }

}

Затем скомпилируйте этот файл в библиотеку DLL под именем MyClass.dll. Если вы пользуетесь компилятором командной строки, введите в командной строке следующее.

csc /t:Library MyClass.cs

Далее составьте программу, в которой применяется библиотека MyClass.dll, как показано ниже.

// Использовать тип dynamic вместе с рефлексией.

using System;

using System.Reflection;

class DynRefDemo {

  static void Main() {

    Assembly asm = Assembly.LoadFrom(«MyClass.dll»);

    Type[] all = asm.GetTypes();

    // Найти класс DivBy.

    int i;

    for (i = 0; i < all.Length; i++)

      if (all[i].Name == «DivBy») break;

    if (i == all.Length) {

      Console.WriteLine(«Класс DivBy не найден в сборке.»);

      return;

    }

    Type t = all[i];

    //А теперь найти используемый по умолчанию конструктор.

    ConstructorInfo[] ci = t.GetConstructors();

    int j;

    for (j = 0; j < ci.Length; j++)

      if (ci[j].GetParameters().Length == 0) break;

    if (j == ci.Length) {

      Console.WriteLine(«Используемый по умолчанию конструктор не найден.»);

      return;

    }

    // Создать объект класса DivBy динамически,

    dynamic obj = ci[j].Invoke(null);

    // Далее вызвать по имени методы для переменной obj.

    // Это вполне допустимо,

    // поскольку переменная obj относится к типу dynamic, а вызовы методов

    // проверяются на соответствие типов во время выполнения, а не компиляции,

    if (obj.IsDivBy(15, 3))

      Console.WriteLine(«15 делится нацело на 3.»);

    else

      Console.WriteLine(«15 HE делится нацело на 3.»);

    if (obj.IsEven(9))

      Console.WriteLine(«9 четное число.»);

    else

      Console.WriteLine(«9 НЕ четное число.»);

  }

}

Как видите, в данной программе сначала динамически загружается библиотека MyClass.dll, а затем используется рефлексия для построения объекта класса DivBy. Построенный объект присваивается далее переменной obj типа dynamic. А раз так, то методы IsDivBy() и IsEven() могут быть вызваны для переменной obj по имени, а не с помощью метода Invoke(). В данном примере это вполне допустимо, поскольку переменная obj на самом деле ссылается на объект класса DivBy. В противном случае выполнение программы завершилось бы неудачно.

Приведенный выше пример сильно упрощен и несколько надуман. Тем не менее он наглядно показывает главное преимущество, которое дает тип dynamic в тех случаях, когда типы получаются во время выполнения. Когда характеристики искомого типа, в том числе методы, операторы, поля и свойства, заранее известны, эти характеристики могут быть получены по имени с помощью типа dynamic, как следует из приведенного выше примера. Благодаря этому код становится проще, короче и понятнее.

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

static void f(object v) { // ... }

static void f(dynamic v) {//...}// Ошибка!

И последнее замечание: тип dynamic поддерживается компонентом DLR (Dynamic Language Runtime – Средство создания динамических языков во время выполнения), внедренным в .NET 4.0.


Возможность взаимодействия с моделью СОМ

В версии C# 4.0 внедрены средства, упрощающие возможность взаимодействия с неуправляемым кодом, определяемым моделью компонентных объектов (СОМ) и применяемым, в частности, в COM-объекте Office Automation. Некоторые из этих средств, в том числе тип dynamic, именованные и необязательные свойства, пригодны для применения помимо возможности взаимодействия с моделью СОМ. Тема модели СОМ вообще и COM-объекта Office Automation в частности весьма обширна, а порой и довольно сложна, чтобы обсуждать ее в этой книге. Поэтому возможность взаимодействия с моделью СОМ выходит за рамки данной книги.

Тем не менее две особенности, имеющие отношение к возможности взаимодействия с моделью СОМ, заслуживают краткого рассмотрения в этом разделе. Первая из них состоит в применении индексированных свойств, а вторая – в возможности передавать аргументы значения тем COM-методам, которым требуется ссылка.

Как вам должно быть уже известно, в C# свойство обычно связывается только с одним значением с помощью одного из аксессоров get или set. Но совсем иначе дело обстоит со свойствами модели СОМ. Поэтому, начиная с версии C# 4.0, в качестве выхода из этого затруднительного положения во время работы с COM-объектом появилась возможность пользоваться индексированным свойством для доступа к COM-свойству, имеющему несколько параметров. С этой целью имя свойства индексируется, почти так же, как это делается с помощью индексатора. Допустим, что имеется объект myXLApp, который относится к типу Microsoft.Office.Interop.Execl.Application.

В прошлом для установки строкового значения "ОК" в ячейках С1-СЗ электронной таблицы Excel можно было бы воспользоваться оператором, аналогичным следующему.

myXLapp.get_Range(«C1», «СЗ»).set_Value(Type.Missing, «OK»);

В этой строке кода интервал ячеек электронной таблицы получается при вызове метода get_Range(), для чего достаточно указать начало и конец интервала. А значения задаются при вызове метода set_Value(), для чего достаточно указать тип (что не обязательно) и конкретное значение. В этих методах используются свойства Range и Value, поскольку у обоих свойств имеются два параметра. Поэтому в прошлом к ним нельзя было обращаться как к свойствам, но приходилось пользоваться упомянутыми выше методами. Кроме того, аргумент Type.Missing служил в качестве обычного заполнителя, который передавался для указания на тип, используемый по умолчанию. Но, начиная с версии C# 4.0, появилась возможно переписать приведенный выше оператор, приведя его к следующей более удобной форме.

myXLapp.Range["Cl", «СЗ»].Value = «OK»;

В этом случае значения интервала ячеек электронной таблицы передаются с использованием синтаксиса индексаторов, а заполнитель Type.Missing уже не нужен, поскольку данный параметр теперь задается по умолчанию.

Как правило, при определении в методе параметра ref приходится передавать ссылку на этот параметр. Но, работая с моделью СОМ, можно передавать параметру ref значение, не заключая его предварительно в оболочку объекта. Дело в том, что компилятор будет автоматически создавать временный аргумент, который уже заключен в оболочку объекта, и поэтому указывать параметр ref в списке аргументов уже не нужно.


    Ваша оценка произведения:

Популярные книги за неделю