Сообщений 3    Оценка 156 [+1/-0]         Оценить  
Система Orphus

NameOrIndexParameter

Унифицированный доступ по имени или по индексу

Автор: Paul Bludov
The RSDN Group
Опубликовано: 06.06.2006
Исправлено: 15.04.2009
Версия текста: 1.0
Введение
Способ, используемый в BLToolkit
How much is the fish?
Итоги

Введение

Если имеется некоторый набор объектов, то к ним, как правило, имеется доступ по порядковому номеру в коллекции. Если имеется набор именнованных объектов, то то к ним желательно иметь доступ ещё и по имени.

Один из способов реализации доступа и по имени и по индексу заключается в дублировании нужных методов с параметрами строкового или числового типа. Это позволяет обращаться к элементам как по имени, так и по индексу. Например:

public interface IDataParameterCollection : IList, ICollection, IEnumerable
{
  //…
  object this[string parameterName] { get; set; }
  object this[int index]            { get; set; }
}

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

Альтернативный вариант заключается в добавлении всем нужным методам как строкового, так и числового параметра, например:

public object GetParameter(string name, int index)
{
  return (name != null) ? _parameters[name] : _parameters[index];
}

Этот вариант годится для Visual Basic, где при вызове можно опустить любой из параметров, но совершенно не годится для c#, где вызов такого метода будет выглядеть просто ужасно:

param1 = GetParameter(“foo”, 0);
param2 = GetParameter(null, 1);

Совершенно не понятно, по имени возвращается параметр или всё-таки по порядковому номеру? Понятно, что такой способ привносит больше путанницы, чем пользы, поэтому обычно используется первый вариант для фасадов, а второй глубоко внутри, где его никто не увидит. В качестве иллюстрации такого подхода можно привести класс DbDataAdapter, реализующий множество методов Fill(), которые превращаются в вызов одного единственного FillInternal с 7-ю параметрами.

Способ, используемый в BLToolkit

В BLToolkit’е используется второй способ, но с человеческим лицом. Вместо двух параметров разного типа передаётся один параметр типа NameOrIndexParameter.

NameOrIndexParameter это структура, содержащая ровно два поля: name и index. У этой структуры имеются два оператора неявного преобразования: из строк и из целых чисел.

Это позволяет не создавать объекты типа NameOrIndexParameter явно. Вместо этого их можно создавать прямо из строк или чисел:

NameOrIndexParameter param1 = 12345;   // Неявная инициализация числом.
NameOrIndexParameter param2 = “54321”; // Неявная инициализация строкой.

Параметры функций типа NameOrIndexParameter также можно задавать неявно:

public object GetParameter(NameOrIndexParameter nip)
{
  //…
}
param1 = GetParameter(“54321”);
param2 = GetParameter(12345);

Изменить значение nip’а (англ. “nip” – щепотка) нельзя, можно только создать новый. У созданного nip’а можно в любой момент узнать, как он был инициализирован и произвести соответствующие действия. Для этого служат свойства ByName, Name и Index:

public object GetParameter(NameOrIndexParameter nip)
{
  return nip.ByName ? _parameters[nip.Name] : _parameters[nip.Index];
}

How much is the fish?

Возникает резонный вопрос: «во сколько обходится BLToolkit’у использование nip’ов?» Ни во сколько. Повторяю: ни во сколько. Ещё раз: ни во сколько.

Как же так? Очень просто. NameOrIndexParameter - это структура, содержащая два readonly поля. С точки зрения компилятора это такая же константа, как число или ссылка на строку, но занимающая в памяти 8-мь байт вместо 4-х. Соответственно код, порождаемый jit компилятором для

param1 = GetParameterWithNIP(“54321”);           // 1 параметр типа NameOrIndexParameter
param2 = GetParameterWithTwoParams(null, 12345); // 2 параметра

совершенно одинаковый. Но в таком случае, между

param1 = GetParameterWithNIP(“54321”); // 1 параметр типа NameOrIndexParameter
param2 = GetParameterByName(“54321”);  // 1 параметр типа строка

есть разница. Да, разница есть. Но мы, к счастью, об этом никогда не узнаем. Дело в том, что современные процессоры умеют выполнять несколько независимых инструкций одновременно. В данном случае, помещение одного или двух параметров в стек выполняется ровно за один такт процессора. Соответствено, инициализация nip’а также добавляет одну параллельную инструкцию обнуления неиспользуемого поля.

Что имеет значение, так это обращение к nip’у на предмет выяснения, кто же он: строка или число. Поскольку в свойстве NameOrIndexParameter.ByName имеется оператор сравнения, то скорость выполения будет зависеть от того, насколько правильно процессор будет предугадывать переходы. В циклах он это делает очень хорошо, т.к. располагает статистикой времени исполнения, а без циклов и говорить не о чем. Один лишний такт процессора не может быть предметом спора.

Итоги

Использовать строковые идентификаторы или цифровые – зависит исключительно от Вас. Строковые делают код более читаемым, числовые прибавляют толику скорости. Кроме того, числовые идентификаторы позволяют использовать разные наборы входных данных одного типа. Например:

public Dictionary<int, string> GetDictionary(string query)
{
  using (DbManager db = new DbManager())
  {
    Dictionary<int, string> ret = db.SetCommand(query).ExecuteScalarDictionary<int, string> (0, 1);
  }
}
//…
Dictionary<int, string> firstNames = GetDictionary(“Select PersonID, FirstName FROM Person”);
Dictionary<int, string> lastNames = GetDictionary(“Select PersonID, LastName FROM Person”);

Хотя в данном случае можно переписать запросы таким образом, чтобы вместо FirstName и LastName у полей было имя просто Name, но это необходимо будет сделать повсюду, где предполагается использовать GetDictionary. Использование числовых идентификаторов здесь вполне уместно.


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
    Сообщений 3    Оценка 156 [+1/-0]         Оценить