Главная страница Случайная страница КАТЕГОРИИ: АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника |
Интерфейс IFormattable
Обеспечивает функциональные возможности форматирования значения объекта в строковое представление.
В следующем примере определяется класс Temperature, реализующий интерфейс IFormattable. Класс поддерживает четыре спецификатора формата: " G" и " C", которые указывают, что температура будет отображаться по Цельсию; " F", указывает, что температура будет отображаться по Фаренгейту; и " K", указывает, что температура будет отображения по Кельвину. Кроме того, реализация IFormattable.ToString также может обрабатывать строку формата, которая является null или пустой. Другие два метода ToString, определенные классом Temperature, просто используют программу-оболочку для вызова реализации IFormattable.ToString. using System.Globalization; public class Temperature: IFormattable{ private decimal temp; public Temperature(decimal temperature) { if (temperature < -273.15m) throw new ArgumentOutOfRangeException(String.Format(" {0} is less than absolute zero.", temperature)); this.temp = temperature; } public decimal Celsius { get { return temp; } } public decimal Fahrenheit { get { return temp * 9 / 5 + 32; } } public decimal Kelvin { get { return temp + 273.15m; } } public override string ToString() { return this.ToString(" G", CultureInfo.CurrentCulture); } public string ToString(string format) { return this.ToString(format, CultureInfo.CurrentCulture); } public string ToString(string format, IFormatProvider provider) { if (String.IsNullOrEmpty(format)) format = " G"; if (provider == null) provider = CultureInfo.CurrentCulture; switch (format.ToUpperInvariant()) { case " G": case " C": return temp.ToString(" F2", provider) + " °C"; case " F": return Fahrenheit.ToString(" F2", provider) + " °F"; case " K": return Kelvin.ToString(" F2", provider) + " K"; default: throw new FormatException(String.Format(" The {0} format string is not supported.", format)); } }}
Необходимость в универсализации возникает с первых шагов программирования. Одна из первых процедур, появляющихся при обучении программированию, - это процедура свопинга: обмен значениями двух переменных одного типа. Выглядит она примерно так: public void Swap(ref T x1, ref T x2) { T temp; temp = x1; x1 = x2; x2 = temp; }Если тип T - это вполне определенный тип, например, int, string или Person, то никаких проблем не существует, все совершенно прозрачно. Но как быть, если возникает необходимость обмена данными и типа int, и типа string, и типа Person? Неужели нужно писать копии этой процедуры для каждого типа? Проблема легко решается в языках, где нет контроля типов, - там достаточно иметь единственный экземпляр такой процедуры, прекрасно работающий, но лишь до тех пор, пока передаются аргументы одного типа. Когда же процедуре будут переданы фактические аргументы разного типа, то немедленно возникнет ошибка периода выполнения, и это слишком дорогая плата за универсальность. В типизированных языках, не обладающих механизмом универсализации, выхода практически нет - приходится писать многочисленные копии Swap. Для достижения универсальности процедуры Swap следует рассматривать тип T как ее параметр, такой же, как и сами аргументы x1 и x2. Суть универсальности в том, чтобы в момент вызова процедуры передавать ей не только фактические аргументы, но и их фактический тип. Вот как можно в C# объявить процедуру Swap с параметром, задающим тип аргументов: public void Swap< T> (ref T item1, ref T item2) { T temp = item1; item1 = item2; item2 = temp; }Вот как клиент может вызывать этот метод для данных разных типов: public void TestSwapT() { int n1 = 7, n2 = 11; double x1 = 7.7, x2 = 11.1; Console.WriteLine(" n1 = {0}, n2 = {1}", n1, n2); Console.WriteLine(" x1 = {0}, x2 = {1}", x1, x2); Console.WriteLine(" После обмена данными одного типа"); gen.Swap< int> (ref n1, ref n2); gen.Swap< double> (ref x1, ref x2); Console.WriteLine(" n1 = {0}, n2 = {1}", n1, n2); Console.WriteLine(" x1 = {0}, x2 = {1}", x1, x2);Заметьте, в момент вызова метода ему передаются как объекты, подлежащие обмену, так и их тип, одинаковый для обоих объектов. Что произойдет, если попытаться обменять объекты разных типов? Console.WriteLine(" Попытка обмена данными разного типа"); //gen.Swap< int> (ref n1, ref x1); //gen.Swap< double> (ref x1, ref n1); Console.WriteLine(" заканчивается ошибкой еще на этапе компиляции! ");Ошибка, естественно, возникнет. Но! Это ошибка периода компиляции, а не выполнения. Так что механизм работает должным образом. Рассмотрим, как универсальность распространяется на класс. Под универсальностью (genericity) понимается способность класса объявлять используемые им типы как параметры. Класс с параметрами, задающими типы, называется универсальным классом (generic class). Терминология не устоялась, и синонимами термина " универсальный класс " являются термины: обобщение, обобщенный класс, родовой класс, параметризованный класс, класс с родовыми параметрами. В языке С++ универсальные классы называются шаблонами (template). Однако между шаблонами С++ и обобщениями.NET есть большая разница. В С++ при создании экземпляра шаблона с конкретным типом необходим исходный код шаблонов. В отличие от шаблонов С++, обобщения являются не только конструкцией языка С#, но также определены для CLR. Это позволяет создавать экземпляры шаблонов с определенным типом-параметром на языке Visual Basic, даже если обобщенный класс определен на С#. в С# всегда имелась возможность создавать обобщенный код, оперируя ссылками типа object. А поскольку класс object является базовым для всех остальных классов, то по ссылке типа object можно обращаться к объекту любого типа. Таким образом, до появления обобщений для оперирования разнотипными объектами в программах служил обобщенный код в котором для этой цели использовались ссылки типа object. Но дело в том, что в таком коде трудно было соблюсти типовую безопасность, поскольку для преобразования типа object в конкретный тип данных требовалось приведение типов. А это служило потенциальным источником ошибок из-за того, что приведение типов могло быть неумышленно выполнено неверно. Это затруднение позволяют преодолеть обобщения, обеспечивая типовую безопасность, которой раньше так недоставало. Кроме того, обобщения упрощают весь процесс, поскольку исключают необходимость выполнять приведение типов для преобразования объекта или другого типа обрабатываемых данных. Таким образом, обобщения расширяют возможности повторного использования кода и позволяют делать это надежно и просто. Наследование и универсальность являются двумя основными механизмами, обеспечивающими мощность объектной технологии разработки. Наследование позволяет специализировать операции класса, уточнить, как должны выполняться операции. Универсализация позволяет специализировать данные, уточнить, над какими данными выполняются операции. Эти механизмы взаимно дополняют друг друга. Эти механизмы в совокупности обеспечивают бесшовный процесс разработки программных систем, начиная с этапов спецификации и проектирования системы и заканчивая этапами реализации и сопровождения. На этапе задания спецификаций появляются абстрактные, универсальные классы, которые в ходе разработки становятся вполне конкретными классами с конкретными типами данных. Механизмы наследования и универсализации позволяют существенно сократить объем кода, описывающего программную систему, поскольку потомки не повторяют наследуемый код своих родителей, а единый код универсального класса используется при каждой конкретизации типов данных. На этапе спецификации, как правило, создается абстрактный, универсальный класс, где задана только сигнатура методов, но не их реализация, где определены имена типов, но не их конкретизация. Здесь же, используя возможности тегов класса, формально или не формально задаются спецификации, описывающие семантику методов класса. Далее в ходе разработки, благодаря механизму наследования, появляются потомки абстрактного класса, каждый из которых задает реализацию методов. На следующем этапе, благодаря механизму универсализации, появляются экземпляры универсального класса, каждый из которых выполняет операции класса над данными соответствующих типов.
|