Source Code for Me (s-c.me)

Allows you to paste souce code to blogs! Adapted for Twitter! Here is Search Form in case you missed your code.
Tags: CSharp,Basel,Derived,Program,Rectangle,Square,CustomConverstons, Created At: 10/24/2016 8:21:53 PMViews:

HTML view:
Copy Source | Copy HTML
Преобразования между связанными типами классов
Как было показано в главе 6, типы классов могут быть связаны классическим
наследованием (отношение "является" ("is а")). В этом случае процесс преобразования С#
позволяет выполнять приведение вверх и вниз по иерархии классов. Например, класс-
наследник всегда может быть неявно приведен к базовому типу. Однако если
необходимо хранить тип базового класса в переменной типа класса-наследника, понадобится
явное приведение:
// Два связанных типа классов, 
class Basel}
class Derived : Basel}
class Program
I
static void Main(string [] args)
I
// Неявное приведение наследника к предку. 
Base myBaseType;
myBaseType = new Derived();
// Для хранения базовой ссылке в ссылке 
//на наследника нужно явное преобразование. 
Derived myDerivedType = (Derived)myBaseType;
}
}
Это явное приведение работает благодаря тому факту, что классы Base и Derived
связаны отношением классического наследования. Однако что если есть два типа
классов из разных иерархий без общего предка (кроме System.Object), которые требуют
преобразования друг в друга? Если они не связаны классическим наследованием, явное
приведение здесь не поможет.
Глава 12. Расширенные средства языка С# 435
Рассмотрим типы значений, такие как структуры. Предположим, что определены
две структуры .NET с именами Square и Rectangle. Учитывая, что они не могут
полагаться на классическое наследование (поскольку всегда запечатаны), нет естественного
способа выполнить приведение между этими, на первый взгляд, связанными типами.
Наряду с возможностью создания в структурах вспомогательных методов (вроде
Rectangle.ToSquare ()), язык С# позволяет строить специальные процедуры
преобразования, которые позволят типам реагировать на операцию приведения (). Таким
образом, если корректно сконфигурировать эти структуры, можно будет использовать
следующий синтаксис для явного преобразования между ними:
// Преобразовать Rectangle в Square! 
Rectangle rect;
rect.Width = 3;
rect.Height =10;
Square sq = (Square)rect;
Создание специальных процедур преобразования
Начнем с создания нового консольного приложения по имени CustomCconversions.
В С# предусмотрены два ключевых слова — explicit и implicit, которые можно
применять для управления реакцией на попытки выполнить преобразования. Предположим,
что имеются следующие определения классов:
public class Rectangle
{
public int Width {get; set;}
public int Height {get; set;}
public Rectangle (int w, int h)
{
Width = w; Height = h;
}
public Rectangle(){}
public void Draw()
{
for (int i =  0; l < Height; i++)
{
for (int j =  0; j < Width; j++)
{
Console.Write("*");
}
Console.WriteLine();
}
}
public override string ToStringO
{
return string.Format(" [Width = {0}; Height = {1}]",
Width, Height);
}
}
public class Square
{
public int Length {get; set;}
public Square(int 1)
{
Length = 1;
}
public Square () {}
436 Часть III. Дополнительные конструкции программирования на С#
public void Draw()
{
for (int i =  0; l < Length; i++)
{
for (int j =  0; j < Length; j++)
{
Console.Write("*");
}
Console.WriteLine();
}
}
public override string ToStringO
{ return string.Format("[Length = {0}]", Length); }
// Rectangle можно явно преобразовать в Square, 
public static explicit operator Square(Rectangle r)
{
Square s = new Square();
s.Length = r.Height;
return s;
}
}
Обратите внимание, что эта итерация типа Squire определяет явную операцию
преобразования. Подобно процессу перегрузки операций, процедуры преобразования
используют ключевое слово operator в сочетании с ключевым словом explicit или
implicit и должны быть статическими. Входным параметром является сущность, из
которой выполняется преобразование, в то время как тип операции — сущность, к
которой оно производится.
В этом случае предполагается, что квадрат (геометрическая фигура с четырьмя
равными сторонами) может быть получен из высоты прямоугольника. Таким образом,
преобразовать Rectangle в Square можно следующим образом:
static void Main(string [ ] args)
{
Console.WriteLine ("***** Fun with Conversions *****\n");
// Создать Rectangle. 
Rectangle r = new RectangleA5, 4);
Console.WriteLine(r.ToString());
r.Draw();
Console.WriteLine();
// Преобразовать г в Square на основе высоты Rectangle. 
Square s = (Square)r;
Console.WriteLine(s.ToString());
s.Draw ();
Console.ReadLine();
}
Вывод этой программы показан на рис. 12.3.
Хотя, может быть, не слишком полезно преобразовывать Rectangle в Square в
пределах одного контекста, предположим, что есть функция, спроектированная так, чтобы
принимать параметров Square:
// Этот метод требует параметр типа Square. 
static void DrawSquare(Square sq)
{
Console.WriteLine(sq.ToString());
sq.Draw();
Глава 12. Расширенные средства языка С# 437
Имея операцию явного преобразования в тип Square, можно передавать типы
Rectangle для обработки этому методу, используя явное приведение:
static void Main(string[] args)
{
// Преобразовать Rectangle в Square для вызова метода. 
Rectangle rect = new Rectangle A0, 5) ;
DrawSquare((Square)rect);
Console.ReadLine();
}
as C\Windo*s\system32\cmd.exe 1
i***** рип w-jth Conversions
[Width = 15; Height = 4]
***************
***************
I***************
***************
¦[Length = 4]
¦****
¦****
****
I****
¦Press any key to continue .
in
¦
Ь^ЁУи^яГ
*****
- - -
_^»
-
j
Рис. 12.3. Преобразование Rectangle в Square
Дополнительные явные преобразования типа Square
Теперь, когда можно явно преобразовывать объекты Rectangle в объекты Square,
давайте рассмотрим несколько дополнительных явных преобразований. Учитывая, что
квадрат симметричен по всем сторонам, может быть полезно предусмотреть процедуру
преобразования, которая позволит вызывающему коду привести целочисленный тип к
типу Square (который, разумеется, будет иметь длину стороны, равную переданному
целому). Аналогично, что если вы захотите модифицировать Square так, чтобы
вызывающий код мог выполнять приведение из Square в System. Int32? Логика вызова
выглядит следующим образом.
static void Main(string[] args)
{
// Преобразование int в Square. 
Square sq2 = (Square)90;
Console.WriteLine("sq2 = {0}", sq2);
// Преобразование Square в int. 
int side = (int)sq2;
Console.WriteLine("Side length of sq2 = {0}", side);
Console.ReadLine();
}
Ниже показаны необходимые изменения в классе Square:
public class Square
{
public static explicit operator Square(int sideLength)
{
438 Часть III. Дополнительные конструкции программирования на С#
Square newSq = new Square();
newSq.Length = sideLength;
return newSq;
}
public static explicit operator int (Square s)
{return s.Length;}
}
По правде говоря, преобразование Square в int может показаться не слишком
интуитивно понятной (или полезной) операцией. Однако это указывает на один очень
важный факт, касающийся процедур пользовательских преобразований: компилятор не
волнует, что и куда преобразуется, до тех пор, пока пишется синтаксически
корректный код. Таким образом, как и с перегруженными операциями, возможность создания
операций явного приведения еще не означает, что вы обязаны их создавать. Обычно
эта техника наиболее полезна при создании типов структур .NET, учитывая, что они не
могут участвовать в отношениях классического наследования (где приведение
достается бесплатно).
Определение процедур неявного преобразования
До сих пор вы создавали различные пользовательские операции явного
преобразования. Однако что, если понадобится неявное преобразование?
static void Main(string[] args)
{
// Попытка выполнить неявное приведение? 
Square s3 = new Square ();
s3.Length =83;
Rectangle rect2 = s3;
Console.ReadLine();
}
Этот код не скомпилируется, если для типа Rectangle не будет предусмотрена
процедура неявного преобразования. Ловушка здесь вот в чем: не допускается иметь
одновременно функции явного и неявного преобразования, если они не отличаются по типу
возвращаемого значения или списку параметров. Это может показаться ограничением,
однако вторая ловушка состоит в том, что когда тип определяет процедуру неявного
преобразования, никто не запретит вызывающему коду использовать синтаксис явного
приведения!
Запутались? Для того чтобы прояснить ситуацию, давайте добавим к классу
Rectangle процедуру неявного преобразования, используя для этого ключевое слово
implicit (обратите внимание, что в следующем коде предполагается, что ширина
результирующего Rectangle вычисляется умножением стороны Square на 2):
public class Rectangle
{
public static implicit operator Rectangle(Square s)
{
Rectangle r = new Rectangle ();
r.Height = s.Length;
// Предположим, что длина нового Rectangle 
// будет равна (Length x 2) 
г.Width = s.Length * 2;
return r;
}
}
Глава 12. Расширенные средства языка С# 439
После такой модификации можно будет выполнять преобразование между типами:
static void Main(string [ ] args)
{
// Неявное преобразование работает! 
Square s3 = new Square ();
s3.Length = 7;
Rectangle rect2 = s3;
Console.WriteLine("rect2 = {0}", rect2);
DrawSquare(s3);
// Синтаксис явного преобразования также работает! 
Square s4 = new Square ();
s4.Length =3; ¦
Rectangle rect3 = (Rectangle)s4;
Console.WriteLine("rect3 = {0}", rect3);
Console.ReadLine() ;
}
Внутреннее представление процедур
пользовательских преобразований
Подобно перегруженным операциям, методы, квалифицированные ключевыми
словами implicit или explicit, имеют специальные имена в терминах CIL: oplmplicit
и opExplicit, соответственно (рис. 12.4).
р НЛМу Ex>ki\C* Вох?* and the ,NF Г atform 5th ed'circt
Dr»«SChapter_12\Code\CustomC..
File i/iew Help
й Щ CustomConverstons
Ш ? CustomConverstons.Program
* Щ. CustomConverstons.Rectangle
й Щ- CustomConverstons.Square
> .class public auto ansi beforefieldinit
v <Length>k_BacttngFtoW : private W32
¦ ,ctor : votoX)
¦ .ctor : votd(nt32)
¦ Draw: votoX)
¦ ToString: strinoX)
Щ get Length : jnt32Q
аГ
^
(J op_Explclt: dass CustomConverstons.Square(int32)
Ё1 op_Expkit: mt32(class CustomConverstons.Square)
¦ set .Length : void(Jnt32)
A Length : instance int32()
мэдгштшитта
¦?1Й|1М1
•assembly CustomConverstons
Рис. 12.4. Представление CIL определяемых пользователем процедур преобразования
На заметку! В браузере объектов Visual Studio 2010 операции пользовательских преобразований
отображаются с использованием значков "явная операция" и " неявная операция".
На этом рассмотрение определений операций пользовательского преобразования
завершено. Как и с перегруженными операциями, здесь следует помнить, что данный
фрагмент синтаксиса представляет собой просто сокращенное обозначение
"нормальных" функций-членов, и в этом смысле является необязательным. Однако в случае
правильного применения пользовательские структуры могут использоваться более
естественно, поскольку трактуются как настоящие типы классов, связанные наследованием.
Исходный код. Проект CustomConversions доступен в подкаталоге Chapter 12.
440 Часть III. Дополнительные конструкции программирования на С#
Понятие расширяющих методов
В .NET 3.5 появилась концепция расширяющих методов (extension method), которая
позволила добавлять новую функциональность к предварительно скомпилированным
типам "на лету". Известно, что как только тип определен и скомпилирован в сборку
.NET, его определение становится более-менее окончательным. Единственный способ
добавления новых членов, обновления или удаления членов состоит в
перекодировании и перекомпиляции кодовой базы в обновленную сборку (или же можно прибегнуть
к более радикальным мерам, таким как использование пространства имен System.
Reflection.Emit для динамического изменения скомпилированного типа в памяти).
Теперь в С# можно определять расширяющие методы. Суть расширяющих методов
в том, что они позволяют существующим скомпилированным типам (а именно —
классам, структурам или реализациям интерфейсов), а также типам, которые в данный
момент компилируются (такие как типы в проекте, содержащем расширяющие методы),
получать новую функциональность без необходимости в непосредственном изменении
расширяемого типа.
Эта техника может оказаться полезной, когда нужно внедрить новую
функциональность в типы, исходный код которых не доступен. Также она может пригодиться, когда
необходимо заставить тип поддерживать набор членов (в интересах полиморфизма),
но вы не можете модифицировать его исходное объявление. Механизм расширяющих
методов позволяет добавлять функциональность к предварительно скомпилированным
типам, создавая иллюзию, что она была у него всегда.
На заметку! Имейте в виду, что расширяющие методы на самом деле не изменяют
скомпилированную кодовую базу! Эта техника лишь добавляет члены к типу в контексте текущего
приложения.
При определении расширяющих методов первое ограничение состоит в том, что
они должны быть определены внутри статического класса (см. главу 5), и потому
каждый расширяющий метод должен быть объявлен с ключевым словом static. Второй
момент состоит в том, что все расширяющие методы помечаются таковыми
посредством ключевого слова this в виде модификатора первого (и только первого) параметра
данного метода. Третий момент — каждый расширяющий метод может быть вызван
либо от текущего экземпляра в памяти, либо статически, через определенный
статический класс! Звучит странно? Давайте рассмотрим полный пример, чтобы прояснить
картину.
Определение расширяющих методов
Создадим новое консольное приложение по имени ExtensionMethods. Теперь
предположим, что строится новый служебный класс по имени MyExtensions, в котором
определены два расширяющих метода. Первый позволяет любому объекту из библиотек
базовых классов .NET получить новый метод по имени DisplayDef iningAssembly (),
который использует типы из пространства имен System.Reflection для отображения
сборки указанного типа.
На заметку! API-интерфейс рефлексии формально рассматривается в главе 15. Если эта тема
является новой, просто знайте, что рефлексия позволяет исследовать структуру сборок, типов и
членов типов во время выполнения.
Второй расширяющий метод по имени ReverseDigits () позволяет любому
экземпляру System. Int32 получить новую версию себя, но с обратным порядком следования
Глава 12. Расширенные средства языка С# 441
цифр. Например, если на целом значении 1234 вызвать ReverseDigits (),
возвращенное целое значение будет равно 4 321. Взгляните на следующую реализацию класса (не
забудьте импортировать пространство имен System.Reflection):
static class MyExtensions
{
// Этот метод позволяет любому объекту отобразить 
// сборку, в которой он определен. 
public static void DisplayDefiningAssembly(this object obj)
{
Console.WriteLine("{0} lives here: => {l}\n", ob].GetType().Name,
Assembly.GetAssembly(ob].GetType()).GetName().Name);
}
// Этот метод позволяет любому целому изменить порядок следования 
// десятичных цифр на обратный. Например, 56 превратится в 65. 
public static int ReverseDigits(this int i)
{
// Транслировать int в string и затем получить все его символы. 
char[] digits = i.ToString() .ToCharArray ();
// Изменить порядок элементов массива. 
Array.Reverse(digits);
// Вставить обратно в строку. 
string newDigits = new string(digits);
// Вернуть модифицированную строку как int. 
return int.Parse(newDigits) ;
}
}
Обратите внимание, что первый параметр каждого расширяющего метода
квалифицирован ключевым словом this, перед определением типа параметра. Первый
параметр расширяющего метода всегда представляет расширяемый тип. Учитывая, что
DisplayDef iningAssembly () прототипирован расширять System. Ob j ect, любой тип в
любой сборке теперь получает этот новый член. Однако ReverseDigits ()
прототипирован только для расширения целочисленных типов, и потому если что-то другое
попытается вызвать этот метод, возникнет ошибка времени компиляции.
Знайте, что каждый расширяющий метод может иметь множество параметров, но
только первый параметр может быть квалифицирован как this. Например, вот как
выглядит перегруженный расширяющий метод, определенный в другом служебном классе
по имени TestUtilClass:
static class TesterUtilClass
{
// Каждый Int32 теперь имеет метод Foo() . . . 
public static void Foo(this int i)
{ Console.WriteLine ("{0} called the Foo() method.", i); }
// ...который перегружен для приема параметра string! 
public static void Foo(this int i, string msg)
{ Console.WriteLine ("{0} called Foo() and told me: {1}", i, msg); }
}
Вызов расширяющих методов на уровне экземпляра
После определения этих расширяющих методов теперь все объекты (в том числе,
конечно же, все содержимое библиотек базовых классов .NET) имеют метод по имени
DisplayDef iningAssembly () , в то время как типы System. Int32 (и только целые) —
методы ReverseDigits () и Foo ():
442 Часть III. Дополнительные конструкции программирования на С#
static void Main(string[] args)
{
Console.WriteLine ("***** Fun with Extension Methods *****\n");
// В int появилась новая идентичность1 
int mylnt = 12345678;
mylnt.DisplayDefiningAssembly();
// To же и у DataSet! 
System.Data.DataSet d = new System.Data.DataSet ();
d.DisplayDefiningAssembly();
// И у SoundPlayerl 
System.Media.SoundPlayer sp = new System.Media.SoundPlayer();
sp.DisplayDefiningAssembly();
// Использовать новую функциональность int. 
Console.WriteLine("Value of mylnt: {0}", mylnt);
Console.WriteLine("Reversed digits of mylnt: {0}", mylnt.ReverseDigits()) ;
mylnt.Foo();
mylnt.Foo("Ints that Foo? Who would have thought it!");
bool b2 = true;
// Ошибка I Booleans не имеет метода Foo() I 
// b2.Foo() ; 
Console.ReadLine();
}
Ниже показан вывод этой программы:
••••• Fun with Extension Methods *****
Int32 lives here: => mscorlib
DataSet lives here: => System.Data
SoundPlayer lives here: => System
Value of mylnt: 12345678
Reversed digits of mylnt: 87654321
12345678 called the Foo() method.
12345678 called Foo () and told me: Ints that Foo? Who would have thought it!
Вызов расширяющих методов статически
Вспомните, что первый параметр расширяющего метода помечен ключевым словом
this, а за ним следует тип элемента, к которому метод применяется. Если вы
посмотрите, что происходит "за кулисами" (с помощью инструмента вроде ildasm.exe), то
обнаружите, что компилятор просто вызывает "нормальный" статический метод, передавая
переменную, на которой вызывается метод, в первом параметре (т.е. в качестве
значения this). Ниже показаны примерные подстановки кода.
private static void Main(string[] args)
{
Console.WriteLine("***** Fun with Extension Methods *****\n");
int mylnt = 12345678;
MyExtensions.DisplayDefiningAssembly(mylnt);
System.Data.DataSet d = new DataSet();
MyExtensions.DisplayDefiningAssembly(d);
System.Media.SoundPlayer sp = new SoundPlayer();
MyExtensions.DisplayDefiningAssembly(sp);
Console.WriteLine("Value of mylnt: {0}", mylnt);
Console.WriteLine("Reversed digits of mylnt: {0}",
MyExtensions.ReverseDigits(mylnt));
TesterUtilClass.Foo(mylnt);
TesterUtilClass.Foo(mylnt, "Ints that Foo? Who would have thought it!");
Console.ReadLine();
}
Глава 12. Расширенные средства языка С# 443
Учитывая, что вызов расширяющего метода из объекта (что похоже на вызов метода
уровня экземпляра) — это просто эффект "дымовой завесы", создаваемый
компилятором, расширяющие методы всегда можно вызвать как нормальные статические методы,
используя привычный синтаксис С# (как показано выше).
Контекст расширяющего метода
Как только что объяснялось, расширяющие методы — это, по сути, статические
методы, которые могут быть вызваны от экземпляра расширяемого типа. Поскольку
это — разновидность синтаксического "украшения", важно понимать, что в отличие от
"нормального" метода, расширяющий метод не имеет прямого доступа к членам типа,
который он расширяет. Иначе говоря, расширение — это не наследование. Взгляните на
следующий простой тип Саг:
public class Car
{
public int Speed;
public int SpeedUp ()
{
return ++Speed;
}
}
Построив расширяющий метод для типа Car по имени SlowDown (), вы не
получите прямого доступа к членам Саг внутри контекста расширяющего метода, поскольку
это не является классическим наследованием. Таким образом, следующий код вызовет
ошибку компиляции:
public static class CarExtensions
{
public static int SlowDown(this Car c)
{
// Ошибка! Этот метод не унаследован от Саг! 
return --Speed/
}
}
Проблема в том, что расширяющий метод SlowDown () пытается обратиться к
полю Speed типа Саг. Однако поскольку SlowDown () — статический член класса
CarExtension, в его контексте отсутствует Speed!
Тем не менее, допустимо использовать параметр, квалифицированный словом this,
для обращения к общедоступным (и только общедоступным) членам расширяемого типа.
Таким образом, следующий код успешно скомпилируется, как и следовало ожидать:
public static class CarExtensions
{
public static int SlowDown(this Car c)
{
// Скомпилируется успешно! 
return --с.Speed;
}
}
Теперь можно создать объект Car и вызывать методы SpeedUp () и SlowDown (), как
показано ниже:
static void UseCarO
{
Car с = new Car();
Console.WriteLine("Speed: {0}", с.SpeedUp());
Console.WriteLine("Speed: {0}", с.SlowDown ());
}
444 Часть III. Дополнительные конструкции программирования на С#
Импорт типов, которые определяют расширяющие методы
В случае выделения набора статических классов, содержащих расширяющие
методы, в уникальное пространство имен другие пространства имен в этой сборке
используют стандартное ключевое слово using для импорта не только самих статических
классов, но также и каждого из поддерживаемых расширяющих методов. Об этом следует
помнить, поскольку если не импортировать явно корректное пространство имен, то
расширяющие методы будут недоступны в таком файле кода С#. Хотя на первый взгляд
может показаться, что расширяющие методы глобальны по своей природе, на самом деле
они ограничены пространствами имен, в которых они определены, или пространствами
имен, которые их импортируют. Таким образом, если поместить определения
рассматриваемых статических классов (MyExtensions, TesterUtilClass и CarExtensions) в
пространство имен MyExtensionMethods, как показано ниже:
namespace MyExtensionMethods
{
static class MyExtensions
static class TesterUtilClass
static class CarExtensions
}
то другие пространства имен в проекте должны явно импортировать пространство
MyExtensionMethods для получения расширяющих методов, определенных этими
типами. Поэтому следующий код вызовет ошибку во время компиляции:
// Единственная директива using, 
using System;
namespace MyNewApp
{
class JustATest
{
void SomeMethod()
{
// Ошибка! Для расширения int методом Foo() необходимо 
// импортировать пространство имен MyExtensionMethods! 
int i= 0;
i.Foo ();
}
}
}

Based on Manoli.Net's CodeFormatter. Made by Topbot (c) 2008-2017