![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
Если имеется некоторый набор объектов, то к ним, как правило, имеется доступ по порядковому номеру в коллекции. Если имеется набор именнованных объектов, то то к ним желательно иметь доступ ещё и по имени.
Один из способов реализации доступа и по имени и по индексу заключается в дублировании нужных методов с параметрами строкового или числового типа. Это позволяет обращаться к элементам как по имени, так и по индексу. Например:
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’е используется второй способ, но с человеческим лицом. Вместо двух параметров разного типа передаётся один параметр типа 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]; } |
Возникает резонный вопрос: «во сколько обходится 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. Использование числовых идентификаторов здесь вполне уместно.
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |