13 марта 2011 г.

Подсчёт введённых символов при помощи javascript

Пусть у нас на странице есть поле ввода (однострочное или многострочное) и у него есть заранее известный id. Нам нужно подсчитывать количество символов и отображать это значение на экране. Это делается легко и просто при помощи javascript. Напишем такую функцию:


function CalculateCharsInTextArea(TextElementId, CaptionElementId) {
var textControl = document.getElementById(TextElementId);
var s = textControl.value;
var captionControl = document.getElementById(CaptionElementId);
captionControl.innerText = "Введено " + s.length + " символов";
}

Первый параметр - это id текстового поля, второй параметр - id элемента, на котором требуется отображать количество символов. Это может быть практически любой элемент, даже простой div. Полдела сделано.

Теперь нужно вызывать эту функцию каждый раз, когда изменяется текст в поле ввода. Кажется логичным, что эту функцию следует вызывать в событии onchange. Однако не тут-то было. Работает как-то глючно и отображается неправильное количество символов. Методом проб и ошибок я выявил правильное событие - это onkeyup. Данное событие наступает после того, как пользователь отпустит нажатую клавишу. Тогда количество символов отображается корректно и в любом браузере.

Пример html разметки для вызова функции:

<input type="text" id="txtMessage" onkeyup="CalculateCharsInTextArea('txtMessage', 'txtCharCount')" value="" />
<span id="txtCharCount"></span>

Парсинг основных типов данных из строки

Все базовые классы .NET Framework имеют метод TryParse, который парсит строку и преобразует её в соответствующий тип данных. Если же строка имеет недопустимый формат, исключения не возникает, а целевая переменная инициализируется значением по умолчанию. Такое поведение очень удобно использовать при парсинге текстовых данных (например, пользовательского ввода на странице сайта).

Итак, пусть у нас имеется исходная строка, из которой нужно извлечь значение для целевой переменной типа int:

string Input = "  777  ";

Я предлагаю использовать такую конструкцию:

int Output;
if (Input != null && int.TryParse(Input, out Output))
{
        // здесь выполняем необходимое действие, например, вывод на экран
        Console.WriteLine(Output);
}

Сначала нам надо проверить, что строка определена (т.к. строка - это ссылочный тип данных). В предыдущих постах я уже рассказывал, почему стоит проверять ссылку на null прежде всего, если условие составное.

Далее вызывается, собственно, сам метод TryParse. Первым параметром передаём ему исходную строку, вторым параметром передаём целевую переменную, в которую надо занести значение из строки. Она передаётся с параметром out, т.е. она будет проинициализирована внутри этого метода. Метод возвращает true - если преобразование прошло успешно. В противном случае - false. Именно поэтому TryParse я предлагаю вызывать в условии, ведь если строка будет в неправильном формате, никаких действий выполняться не будет.

Обратите внимание, что строка может содержать помимо самого числа ещё и пробелы с обоих сторон.

Теперь более сложный пример. А конструкция та же.


string Input = "  777,05  ";
double Output;
if (Input != null && double.TryParse(Input, out Output))
{
Console.WriteLine(Output);
}

Здесь мы преобразуем десятичное число. Его формат должен соответствовать региональным настройкам. В России разделителем между целой и дробной частями является запятая. В США - точка. Это надо учитывать, т.к. если формат не будет соответствовать региональным настройкам, метод вернёт false.

То же самое с DateTime.


string Input = "13 марта 2011, 15:08";
DateTime Output;
if (Input != null && DateTime.TryParse(Input, out Output))
{
Console.WriteLine(Output);
}

Дата соответствует российскому формату и строка будет преобразована корректно в значением "13.03.2011 15:08:00". А теперь представьте, насколько сложно разобрать эту строку самописным методом?) Так что не будем изобретать очередной велосипед.

Ну и наконец, интервал времени TimeSpan. Перед точкой указано количество дней, после неё идут часы, минуты и секунды.


string Input = "  12.2:04:05  ";
TimeSpan Output;
if (Input != null && TimeSpan.TryParse(Input, out Output))
{
Console.WriteLine(Output.Days);
}


На экран будет выведено "12", т.е. количество дней.

8 марта 2011 г.

Форматирование даты и времени при помощи DateTime.ToString()

Ещё один пример того, когда функционал доступен "из коробки", а начинающие всё равно пытаются написать свой велосипед. Итак, форматирование даты и времени при помощи метода ToString(). Ничего сложного, давайте лучше выведем простые мнемонические правила для запоминания, чтобы каждый раз не лазить в MSDN.

Для примера возьмём произвольную дату, например: 01.02.2003 04:05:06. Преобразовать строку с этой датой в объект DateTime очень просто:

DateTime ExampleDate = DateTime.Parse("01.02.2003 04:05:06");

Опять же, не нужно изобретать велосипед. Итак, форматирование даты при помощи ToString() всегда выполняется с учётом текущих региональных установок. Если мы находимся на компьютере с российскими установками, то все названия месяцев и дней недели будут на русском.

Вызов ToString() без параметров вернёт похожую на исходную строку:
01.02.2003 4:05:06
Почти то же самое, только час идёт без ведущего нуля.

А теперь будем вызывать ToString(), передавая ему в качестве аргумента строку форматирования:
ExampleDate.ToString("y");

Год обозначается маленькой буквой y (year).

  • Строка из одной "y" - это две последних цифры года без ведущего нуля (если он есть).
  • Строка из двух букв "yy" - две последние цифры с ведущим нулём.
  • Три или четыре "yyyy" - все 4 цифры года. Для наглядности используйте 4 символа.

Тут есть одна особенность: если вы передадите строку именно "y", то будет отображён не только год и немного не в том формате, т.к. это воспринимается как стандартная предустановленная строка формата в платформе .NET. Чтобы этого не происходило, добавьте в эту строку любой символ, хоть пробел в конце. Но чаще всего вы будете использовать набор из различных частей даты, поэтому такой ошибки возникать не будет.

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

"y ": "3 "
"yy": "03"
"yyy": "2003"
"yyyy": "2003"

Месяц. Обозначается большой буквой "M", т.к. маленькой обозначается минута. Все правила форматирования аналогичны, только три "M" дают трёхбуквенное обозначение месяца:

"M ": "2 "
"MM": "02"
"MMM": "фев"
"MMMM": "Февраль"
Опять же не забудьте, что строка из одного символа "M" - стандартное форматирование и вернёт не то, что вы ожидаете (а вы ожидаете номер месяца без ведущего нуля). Поэтому добавляйте ещё один символ.

День. Обозначается малой буквой "d". Правила такие же:

"d ": "1 "
"dd": "01"
"ddd": "Сб"
"dddd": "суббота"
Уже можно выявить закономерность, что три буквы форматирования показывают сокращённое строковое представление, а четыре - полное строковое представление.

Час. У нас в России используется 24-часовой формат времени (буква "H" большая). В США - 12-ти часовой (буква "h" малая). Изменим исходную дату на 01.02.2003 14:05:06, чтобы продемонстрировать различия. У номера часа названия быть не может, поэтому более двух одинаковых символов смысла использовать нет:

"h ": "2 "
"hh": "02"
"H ": "14 "
"HH": "14"

Минута и секунда. Минута обозначается малой буквой "m" (вспомним, что большая "M" - это месяц). Секунда обозначается малой буквой "s". Тут во всех странах используется один и тот же формат, поэтому комментарии излишни:

"m ": "5 "
"mm": "05"
"s ": "6 "
"ss": "06"

Теперь повторим правила для лучшего запоминания.

  • Один символ - двухзначное число без ведущего нуля.
  • Два символа - двухзначное число с ведущим нулём.

Если число больше или равно десяти, то разницы между этими двумя видами форматирования вы не увидите.

  • Три символа - сокращённое название.
  • Четыре символа - полное название.

Названия могут быть только у месяца и дня недели. Исключение - это год, т.к. три и четыре символа отображаются в 4-х значный номер года.

  • "M" - месяц, а "m" - минута.
  • "H" - 24-часовой формат часа, а "h" - 12-часовой формат.

Всё остальное пишется строчными буквами.

Помимо символов, которые предусмотрены для форматирования, вы можете использовать любые другие символы (например, слова и знаки препинания). Типичное форматирование, которое применяется в подавляющем большинстве блогов и форумов:

"написано d MMMM yyyy в HH:mm": "написано 1 февраля 2003 в 14:05"

Если материал оказался полезен для Вас, нажмите на кнопку Google+ в конце этой заметки.

5 марта 2011 г.

Разница между явным приведением типа и оператором as

Пусть у нас есть несколько простейших классов, соответствующих геометрическим фигурам: круг, квадрат и прямоугольник.


public class Circle
{
public int radius;

public Circle(int radius)
{
this.radius = radius;
}
}

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

public class Square
{
protected int width;

public Square(int width)
{
this.width = width;
}
}

У квадрата есть параметр ширина.

public class Rectangle : Square
{
private int height;

public Rectangle(int width, int height)
: base(width)
{
this.height = height;
}
}

У прямоугольника есть параметры ширина и высота. Этот класс логичнее наследовать от квадрата, т.к. квадрат - это прямоугольник, у которого ширина равна высоте. Обратите внимание, что в конструкторе сначала вызывается конструктор базового класса для установки ширины, т.к. за ширину отвечает базовый класс.

Теперь рассмотрим ситуацию, когда нам понадобилось упаковать экземпляры круга и прямоугольника в общий для них класс, которым является класс object. Это общий для всех классов в платформе .NET. Классический принцип объектно-ориентированного программирования (ООП), когда мы можем хранить ссылку на производный класс в ссылке базового класса.


// прямоугольник 3 x 6
object Object1 = new Rectangle(3, 6);
// круг с радиусом 4
object Object2 = new Circle(4);

Теперь нам понадобилось поместить эти объекты в объекты типа Square. Традиционный подход - это явное приведение. Но что, если нам заранее неизвестно, совместим ли приводимый объект с классом квадрат?

Square SquareFromRectangle = (Square)Object1;
Square SquareFromCircle = (Square)Object2; // здесь возникнет исключение

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

Square SquareFromRectangle = Object1 as Square;
Square SquareFromCircle = Object2 as Square;

Конечно же, все полученные ссылки сразу же следует проверить на null, чтобы в дальнейшем не возникло исключения уже из-за ссылки на null.

Инициализация всех свойств класса в одной инструкции

Немного об инициализации публичных членов класса. Довольно часто бывает так, что нужно в коде проинициализировать члены класса (как правило, свойства) при создании объекта. Итак, пусть у нас есть довольно простой класс для хранения всех полей одной записи.


public class Record
{
public string Header { get; set; }
public string Text;
public DateTime Created { get; set; }
}

Обратите внимание, что в классе имеется как свойство (Header), так и открытый член класса (Text). Традиционно все эти поля при создании объекта мы инициализируем либо через параметры конструктора, либо, если его нет, как в нашем случае, отдельно каждое поле.


Record NewRecord = new Record();
NewRecord.Header = "Заголовок";
NewRecord.Text = "Текст записи.";
NewRecord.Created = DateTime.Now;

Вроде бы ничего, но если в классе таких параметров штук 20, то получается очень громоздко. Более краткая запись в одну строчку:

Record NewRecord = new Record() { Header = "Заголовок", Text = "Текст записи.", Created = DateTime.Now };

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

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