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

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

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


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



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

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

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

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

using System;

class MyClass {

  //...

}

// Наложить ограничение ссылочного типа,

class Test where T : class {

  T obj;

  public Test() {

    // Следующий оператор допустим только потому, что

    // аргумент Т гарантированно относится к ссылочному

    // типу, что позволяет присваивать пустое значение,

    obj = null;

  }

  // ...

}

class ClassConstraintDemo {

  static void Main() {

    // Следующий код вполне допустим,

    //  поскольку MyClass является классом.

    Test х = new Test();

    // Следующая строка кода содержит ошибку, поскольку

    // int относится к типу значения.

    // Test у = new Test();

  }

}

Обратите внимание на следующее объявление класса Test,

class Test where T : class {

Ограничение class требует, чтобы любой аргумент Т был ссылочного типа. В данном примере кода это необходимо для правильного выполнения операции присваивания в конструкторе класса Test.

public Test() {

  // Следующий оператор допустим только потому, что

  // аргумент Т гарантированно относится к ссылочному

  // типу, что позволяет присваивать пустое значение,

  obj = null;

}

В этом фрагменте кода переменной obj типа Т присваивается пустое значение. Такое присваивание допустимо только для ссылочных типов. Как правило, пустое значение нельзя присвоить переменной типа значения. (Исключением из этого правила является обнуляемый тип, который представляет собой специальный тип структуры, инкапсулирующий тип значения и допускающий пустое значение (null). Подробнее об этом – в главе 20.) Следовательно, в отсутствие ограничения такое присваивание было бы недопустимым, и код не подлежал бы компиляции. Это один из тех случаев, когда для обобщенного кода может оказаться очень важным различие между типами значений и ссылочными типами.

Ограничение типа значения является дополнением ограничения ссылочного типа. Оно просто гарантирует, что любой аргумент, обозначающий тип, должен быть типа значения, в том числе struct и enum. (В данном случае обнуляемый тип не относится к типу значения.) Ниже приведен пример наложения ограничения типа значения.

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

using System;

struct MyStruct {

  //...

}

class MyClass {

  // ...

}

class Test where T : struct {

  T obj;

  public Test(T x) {

    obj = x;

  }

  // ...

}

class ValueConstraintDemo {

  static void Main() {

    // Оба следующих объявления вполне допустимы.

    Test х = new Test(new MyStruct());

    Test у = new Test(10);

    //А следующее объявление недопустимо!

    // Test z = new Test(new MyClass());

  }

}

В этом примере кода класс Test объявляется следующим образом.

class Test where Т : struct {

На параметр типа Т в классе Test накладывается ограничение struct, и поэтому к нему могут быть привязаны только аргументы типа значения. Это означает, что объявления Test и Test вполне допустимы, тогда как объявление Test недопустимо. Для того чтобы убедиться в этом, удалите символы комментария в начале последней строки приведенного выше кода и перекомпилируйте его. В итоге вы получите сообщение об ошибке во время компиляции.


Установление связи между двумя параметрами типа с помощью ограничения

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

class Gen where V : T {

В этом объявлении оператор where уведомляет компилятор о том, что аргумент типа, привязанный к параметру типа V, должен быть таким же, как и аргумент типа, привязанный к параметру типа Т, или же наследовать от него. Если подобная связь отсутствует при объявлении объекта типа Gen, то во время компиляции возникнет ошибка. Такое ограничение на параметр типа называется неприкрытым ограничением типа. В приведенном ниже примере демонстрируется наложение этого ограничения.

// Установить связь между двумя параметрами типа.

using System;

class A {

  //...

}

class В : A {

  // ...

}

// Здесь параметр типа V должен наследовать от параметра типа Т.

class Gen where V : T {

  // ...

}

class NakedConstraintDemo {

  static void Main() {

    // Это объявление вполне допустимо, поскольку

    // класс В наследует от класса А.

    Gen х = new Gen();

    // А это объявление недопустимо, поскольку

    // класс А-.не наследует от класса В. .

    // Gen у = new Gen();

  }

}

Обратите внимание на то, что класс В наследует от класса А. Проанализируем далее оба объявления объектов класса Gen в методе Main(). Как следует из комментария к первому объявлению

Gen х = new Gen();

оно вполне допустимо, поскольку класс В наследует от класса А. Но второе объявление

// Gen у = new Gen();

недопустимо, поскольку класс А не наследует от класса В.


Применение нескольких ограничений

С параметром типа может быть связано несколько ограничений. В этом случае ограничения указываются списком через запятую. В этом списке первым должно быть указано ограничение class либо struct, если оно присутствует, или же ограничение на базовый класс, если оно накладывается. Указывать ограничения class или struct одновременно с ограничением на базовый класс не разрешается. Далее по списку должно следовать ограничение на интерфейс, а последним по порядку – ограничение new(). Например, следующее объявление считается вполне допустимым.

class Gen where Т : MyClass, IMylnterface, new() {

// ...

В данном случае параметр типа Т должен быть заменен аргументом типа, наследующим от класса MyClass, реализующим интерфейс IMylnterface и использующим конструктор без параметра.

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

// Использовать несколько операторов where,

using System;

// У класса Gen имеются два параметра типа, и на оба накладываются

// ограничения с помощью отдельных операторов where,

class Gen where T : class

      where V : struct {

  T ob1;

  V ob2;

  public Gen(T t, V v) {

    ob1 = t;

    ob2 = v;

  }

}

class MultipleConstraintDemo {

  static void Main() {

    // Эта строка кода вполне допустима, поскольку

    // string – это ссылочный тип, a int – тип значения.

    Gen obj = new Gen(«TecT», 11);

    //А следующая строка кода недопустима, поскольку

    // bool не относится к ссылочному типу.

    // Gencbool, int> obj = new Gencbool, int>(true, 11);

  }

}

В данном примере класс Gen принимает два аргумента с ограничениями, накладываемыми с помощью отдельных операторов where. Обратите особое внимание на объявление этого класса.

class GenCT, V> where T : class

      where V : struct {

Как видите, один оператор where отделяется от другого только пробелом. Другие знаки препинания между ними не нужны и даже недопустимы.


Получение значения, присваиваемого параметру типа по умолчанию

Как упоминалось выше, при написании обобщенного кода иногда важно провести различие между типами значений и ссылочными типами. Такая потребность возникает, в частности, в том случае, если переменной параметра типа должно быть присвоено значение по умолчанию. Для ссылочных типов значением по умолчанию является null, для неструктурных типов значений – 0 или логическое значение false, если это тип bool, а для структур типа struct – объект соответствующей структуры с полями, установленными по умолчанию. В этой связи возникает вопрос: какое значение следует присваивать по умолчанию переменной параметра типа: null, 0 или нечто другое? Например, если в следующем объявлении класса Test:

class Test {

Т obj;

// ...

переменной obj требуется присвоить значение по умолчанию, то какой из двух вариантов

obj = null; // подходит только для ссылочных типов или

obj =0; // подходит только для числовых типов и

// перечислений, но не для структур

следует выбрать? Для разрешения этой дилеммы можно воспользоваться еще одной формой оператора default, приведенной ниже.

default(тип)

Эта форма оператора default пригодна для всех аргументов типа, будь то типы значений или ссылочные типы.

Ниже приведен короткий пример, демонстрирующий данную форму оператора

default.

// Продемонстрировать форму оператора default.

using System;

class MyClass {

  //...

}

// Получить значение, присваиваемое параметру типа Т по умолчанию,

class Test {

  public T obj;

  public Test() {

    // Следующий оператор годится только для ссылочных типов.

    // obj = null; //не годится

    // Следующий оператор годится только для типов значений.

    // obj = 0; // не годится

    // А этот оператор годится как для ссылочных типов,

    // так и для типов значений,

    obj = default(T); // Годится!

  }

  // ...

}

class DefaultDemo {

  static void Main() {

    // Сконструировать объект класса Test, используя ссылочный тип.

    Test x = new Test();

    if (x.obj == null)

      Console.WriteLine(«Переменная x.obj имеет пустое значение .»);

    // Сконструировать объект класса Test, используя тип значения.

    Test у = new Test();

    if (у.obj == 0)

      Console.WriteLine(«Переменная у.obj имеет значение 0.»);

  }

}

Вот к какому результату приводит выполнение этого кода.

Переменная x.obj имеет пустое значение .

Переменная у.obj имеет значение 0.


Обобщенные структуры

В C# разрешается создавать обобщенные структуры. Синтаксис для них такой же, как и для обобщенных классов. В качестве примера ниже приведена программа, в которой создается обобщенная структура XY для хранения координат X, Y.

// Продемонстрировать применение обобщенной структуры,

using System;

// Эта структура является обобщенной,

struct XY {

  T х;

  T y;

  public XY(T а, T b) {

    х = а;

    y = b;

  }

  public T X {

    get { return х; }

    set { х = value; }

  }

  public T Y {

    get { return y; }

    set { y = value; }

  }

}

class StructTest {

  static void Main() {

    XY xy = new XY(10, 20);

    XY xy2 = new XY(88.0, 99.0);

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

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

  }

}

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

10, 20

88, 99

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

struct XY where Т : struct {

// ...


Создание обобщенного метода

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

Рассмотрим для начала простой пример. В приведенной ниже программе объявляется необобщенный класс ArrayUtils, а в нем – статический обобщенный метод CopyInsert(). Этот метод копирует содержимое одного массива в другой, вводя по ходу дела новый элемент в указанном месте. Метод CopyInsert() можно использовать вместе с массивами любого типа.

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

using System;

// Класс обработки массивов. Этот класс не является обобщенным,

class ArrayUtils {

  // Копировать массив, вводя по ходу дела новый элемент.

  // Этот метод является обобщенным.

  public static bool CopyInsert(T e, uint idx,

                   T[] src, T[] target) {

    // Проверить, насколько велик массив,

    if (target.Length < src.Length + 1) return false;

    // Скопировать содержимое массива src в целевой массив,

    // попутно введя значение е по индексу idx.

    for (int i = 0, j = 0; i < src.Length; i++, j++) {

      if (i == idx) {

        target[j] = e;

        j++;

      }

      target[j] = src[i];

    }

    return true;

  }

}

class GenMethDemo {

  static void Main() {

    int[] nums = { 1, 2, 3 };

    int[] nums2 = new int[4];

    // Вывести содержимое массива nums.

    Console.Write("Содержимое массива nums: ");

    foreach (int x in nums)

      Console.Write(x + " ");

    Console.WriteLine();

    // Обработать массив типа int.

    ArrayUtils.CopyInsert(99, 2, nums, nums2);

    // Вывести содержимое массива nums2.

    Console.Write("Содержимое массива nums2: ");

    foreach (int x in nums2)

      Console.Write(x + " ");

    Console.WriteLine();

    //А теперь обработать массив строк, используя метод copyInsert.

    string[] strs = { «Обобщения», «весьма», «эффективны.» };

    string[] strs2 = new string[4];

    // Вывести содержимое массива strs.

    Console.Write("Содержимое массива strs: ");

    foreach (string s in strs)

      Console.Write(s + " ");

    Console.WriteLine();

    // Ввести элемент в массив строк.

    ArrayUtils.CopyInsert(«в С#», 1, strs, strs2);

    // Вывести содержимое массива strs2.

    Console.Write("Содержимое массива strs2: ");

    foreach (string s in strs2)

      Console.Write(s + " ");

    Console.WriteLine();

    // Этот вызов недопустим, поскольку первый аргумент

    // относится к типу double, а третий и четвертый

    // аргументы обозначают элементы массивов типа int.

    // ArrayUtils.CopyInsert(0.01, 2, nums, nums2);

  }

}

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

Содержимое массива nums: 1 2 3

Содержимое массива nums2: 1 2 99.3

Содержимое массива strs: Обобщения весьма эффективны.

Содержимое массива strs2: Обобщения в C# весьма эффективны.

Внимательно проанализируем метод CopyInsert(). Прежде всего обратите внимание на объявление этого метода в следующей строке кода.

public static bool CopyInsert(Т e, uint idx,

         T[] src, T[] target) {

Параметр типа объявляется после имени метода, но перед списком его параметров. Обратите также внимание на то, что метод CopyInsert() является статическим, что позволяет вызывать его независимо от любого объекта. Следует, однако, иметь в виду, что обобщенные методы могут быть либо статическими, либо нестатическими. В этом отношении для них не существует никаких ограничений.

Далее обратите внимание на то, что метод CopyInsert() вызывается в методе Main() с помощью обычного синтаксиса и без указания аргументов типа. Дело в том, что типы аргументов различаются автоматически, а тип Т соответственно подстраивается. Этот процесс называется выводимостью типов. Например, в первом вызове данного метода

ArrayUtils.CopyInsert (99, 2, nums, nums2);

тип T становится типом int, поскольку числовое значение 99 и элементы массивов nums и nums2 относятся к типу int. А во втором вызове данного метода используются строковые типы, и поэтому тип Т заменяется типом string.

А теперь обратите внимание на приведенную ниже закомментированную строку кода.

// ArrayUtils.CopyInsert(0.01, 2, nums, nums2);

Если удалить символы комментария в начале этой строки кода и затем попытаться перекомпилировать программу, то будет получено сообщение об ошибке. Дело в том, что первый аргумент в данном вызове метода CopyInsert() относится к типу double, а третий и четвертый аргументы обозначают элементы массивов nums и nums2 типа int. Но все эти аргументы типа должны заменить один и тот же параметр типа Т, а это приведет к несоответствию типов и, как следствие, к ошибке во время компиляции. Подобная возможность соблюдать типовую безопасность относится к одним из самых главных преимуществ обобщенных методов.

Синтаксис объявления метода CopyInsert() может быть обобщен. Ниже приведена общая форма объявления обобщенного метода.

возвращаемый_ тип имя_метода<список_параметров_типа> (список_параметров) { // ...

В любом случае список_параметров_типа обозначает разделяемый запятой список параметров типа. Обратите внимание на то, что в объявлении обобщенного метода список параметров типа следует после имени метода.


Вызов обобщенного метода с явно указанными аргументами типа

В большинстве случаев неявной выводимости типов оказывается достаточно для вызова обобщенного метода, тем не менее аргументы типа могут быть указаны явным образом. Для этого достаточно указать аргументы типа после имени метода при его вызове. В качестве примера ниже приведена строка кода, в которой метод CopyInsert() вызывается с явно указываемым аргументом типа string.

ArrayUtils.CopyInsert(«В С#», 1, strs, strs2);

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


Применение ограничений в обобщенных методах

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

public static bool CopyInsert(Т e, uint idx,

           T[] src, T[] target) where T : class {

Если попробовать применить этот вариант в предыдущем примере программы обработки массивов, то приведенный ниже вызов метода CopyInsert () не будет скомпилирован, поскольку int является типом значения, а не ссылочным типом.

// Теперь неправильно, поскольку параметр Т должен быть ссылочного типа!

ArrayUtils.CopyInsert(99, 2, nums, nums2); // Теперь недопустимо!


Обобщенные делегаты

Как и методы, делегаты также могут быть обобщенными. Ниже приведена общая форма объявления обобщенного делегата.

delegate возвратцаемый_тип имя_делегата<список_параметров_типа> (список_аргументов) ;

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

В приведенном ниже примере программы демонстрируется применение делегата SomeOp с одним параметром типа Т. Этот делегат возвращает значение типа Т и принимает аргумент типа Т.

// Простой пример обобщенного делегата,

using System;

// Объявить обобщенный делегат,

delegate T SomeOp(T v);

class GenDelegateDemo {

  // Возвратить результат суммирования аргумента,

  static int Sum(int v) {

    int result = 0;

    for (int i = v; i > 0; i–) result += i;

    return result;

  }

  // Возвратить строку, содержащую обратное значение аргумента,

  static string Reflect(string str) {

    string result = "";

    foreach (char ch in str) result = ch + result;

    return result;

  }

  static void Main() {

    // Сконструировать делегат типа int.

    SomeOp intDel = Sum;

    Console.WriteLine(intDel(3));

    // Сконструировать делегат типа string.

    SomeOp strDel = Reflect;

    Console.WriteLine(strDel(«Привет»));

  }

}

Эта программа дает следующий результат.

6

тевирП

Рассмотрим эту программу более подробно. Прежде всего обратите внимание на следующее объявление делегата SomeOp.

delegate Т SomeOp(T v);

Как видите, тип Т может служить в качестве возвращаемого типа, несмотря на то, что параметр типа Т указывается после имени делегата SomeOp.

Далее в классе GenDelegateDemo объявляются методы Sum() и Reflect(), как показано ниже.

static int Sum(int v) {

static string Reflect(string str) {

Метод Sum() возвращает результат суммирования целого значения, передаваемого в качестве аргумента, а метод Reflect() – символьную строку, которая получается обращенной по отношению к строке, передаваемой в качестве аргумента.

В методе Main() создается экземпляр intDel делегата, которому присваивается ссылка на метод Sum().

SomeOp intDel = Sum;

Метод Sum() принимает аргумент типа int и возвращает значение типа int, поэтому он совместим с целочисленным экземпляром делегата SomeOp.

Аналогичным образом создается экземпляр strDel делегата, которому присваивается ссылка на метод Reflect().

SomeOp strDel = Reflect;

Метод Reflect() принимает аргумент типа string и возвращает результат типа string, поэтому он совместим со строковым экземпляром делегата SomeOp.

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

SomeOp strDel = Reflect; //Ошибка!

Ведь метод Reflect() принимает аргумент типа string и возвращает результат типа string, а следовательно, он несовместим с целочисленным экземпляром делегата SomeOp.


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

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