Таким образом, я должен разработать класс, который работает над набором парных объектов. Между объектами существует непосредственное отображение. Я ожидаю, что клиент класса установит это отображение перед использованием моего класса.
Мой вопрос - то, что лучший способ состоит в том, чтобы позволить пользователю моего класса давать мне ту информацию?
Это должно попросить набор пар как это?
MyClass(IEnumerable<KeyValuePair<Object, Object>> objects)
Или отдельные наборы как это?
MyClass(IEnumberable<Object> x, IEnumerable<Object> y)
Или есть ли другая опция?
Мне нравится первое, потому что отношения являются явными, мне не нравятся они из-за дополнительной работы, они ставят клиент.
Мне нравится второе, потому что типы более примитивны и требуют меньшего количества работы, мне не нравится она, потому что отображение не является явным. Я должен предположить, что порядок является правильным.
Мнения?
В .NET 4 вы должны быть используя Tuple
. Дополнительная информация о классе Tuple в MSDN.
Если у вас есть доступ к нему, используйте Tuple
. Если нет, просто напишите собственный класс Tuple
или Pair
. Я бы избегал использовать KeyValuePair
, просто потому что он многословен и имеет ассоциацию с Dictionary
.
Я предпочитаю первый метод, так как со вторым одна последовательность может быть длиннее другой - он не поддерживает требуемое отображение 1: 1.
Я определенно предпочитаю первое. Как вы говорите, корреляция явно выражена, и она менее подвержена ошибкам, чем вторая, где вам нужно проверить, что обе коллекции имеют одинаковую длину. Конечно, на самом деле может быть уместным предложить / оба / пути, в зависимости от фактического класса, который вы создаете.
Одно: если вы используете .NET 4.0, я бы рекомендовал использовать Tuple
вместо KeyValuePair.
На мой взгляд, второй вариант дает больше работы и вам, и клиенту. Первый вариант безопаснее и труднее ошибиться. Я бы каждый раз выбирал первый вариант или что-то в этом роде.
Я думаю, вы должны выбрать вариант 1. Должны быть явные взаимосвязи, иначе вы просто напрашиваетесь на проблемы.
Я обычно предоставляю и то, и другое, с конструктором KeyValuePair, делегирующим конструктор с двумя аргументами. Лучшее из обоих миров.
Мне нравится первый метод по двум причинам.
Если они уже хранят свои отношения в объекте Dictionary <>, они могут просто передать словарь как есть - дополнительный код не требуется.
Если они используют свое собственное хранилище, то предоставить вам KeyValuePairs в IEnumerable очень просто с помощью оператора yield
Примерно так:
IEnumerable<KeyValuePair<Object,Object>> GetMappedPairs()
{
foreach( var pair in _myCustomData )
{
yield return new KeyValuePair{Key = pair.ID, Value = pair.Data};
}
}
Вариант номер 2 нелегко понять разработчикам, использующим ваш метод, поэтому он будет требуется документация и комментарии, чтобы объяснить, как его использовать.
Кстати, вам, вероятно, лучше всего будет объявить ваш метод универсальным вместо жесткого кодирования KeyValuePair
MyClass<TKey,TValue>( IEnumerable<KeyValuePair<TKey,TValue>> pairs );
Возможно, стоит подумать, зачем вы вообще раскрываете перечислимое. Вы можете использовать метод Add(Object a, Object b), который полностью скрывает ваш внутренний способ работы с парами. Затем клиент мог бы установить свои собственные средства добавления к нему отношений в виде множеств или по отдельности.
Конечно, если вам все еще нужен метод, принимающий перечислимое, то первый вариант, вероятно, лучше: явное отношение. Но вы можете использовать или создать тип Tuple или Pair для использования вместо KeyValuePair, предполагая, что отношения между ними не являются отношениями типа "ключ-значение".
А что если сделать что-то вроде этого?
// The client can choose to put together an IEnumerable<...> by hand...
public MyClass(IEnumerable<KeyValuePair<object, object>> pairs)
{
// actual code that does something with the data pairs
}
// OR the client can pass whatever the heck he/she wants, as long as
// some method for selecting the Xs and Ys from the enumerable data is provided.
// (Sorry about the code mangling, by the way -- just avoiding overflow.)
public static MyClass Create<T>
(IEnumerable<T> source, Func<T, object> xSelector, Func<T, object> ySelector)
{
var pairs = source
.Select(
val => new KeyValuePair<object, object>(
xSelector(val),
ySelector(val)
)
);
return new MyClass(pairs);
}
Это позволит клиентам писать код вроде этого:
// totally hypothetical example
var stockReturns = StockReturns.Create(prices, p => p.Close, p => p.PrevClose);
Я бы предпочел KeyValuePair из двух, которые вы упомянули, поскольку он более выразителен и сохраняет взаимосвязь между объектами.
Но я бы предпочел создать класс, который будет содержать ссылку на вашу пару. На мой взгляд, это более читабельно, и никогда не помешает создать дополнительный класс или структуру для выражения ваших фактических действий.
(Псевдокод)
class MyPair
{
public TypeA One;
public TypeB Two;
}
MyClass(IEnumerable<MyPair> objects)
В некоторых ответах упоминается Tuple <,>
, который читается так же, как KeyValuePair, но более гибкий, поскольку может содержать более двух параметров.
[Edit - дополнительная информация о кортежах / классах после хорошего ночного сна]
Я бы использовал вторую версию (потому что она проще) + комментарии + статические / динамические проверки. Если вы можете использовать контракты кода, постарайтесь убедиться, что коллекции имеют одинаковую длину, а не null. Если нет, то сделайте то же самое с Debug.Assert
, а также с if ... throw ArgumentException
. В качестве альтернативы вы можете создать свой собственный объект, содержащий пару, но тогда становится сложнее использовать универсальные шаблоны для членов пары. Кроме того, когда вы создаете объект, предназначенный для хранения в контейнере, вы должны правильно реализовать GetHashCode
, Equals
и т. Д. В первой книге «Эффективный C #» есть элемент на этом.
Как правило, я предпочитаю не перерабатывать сигнатуры методов.