What is the most inuitive way to ask for objects in pairs?
So I have to design a class that works on a collection of paired objects. There is a one-to-one mapping between objects. I expect the client of the class to have established this mapping before using my class.
My question is what is the best way to allow the user of my class to give me that information?
Is it to ask for a collection of pairs like this?
MyClass(IEnumerable<KeyValuePair<Object, Object>> objects)
Or seperate collections like this?
MyClass(IEnumberable&l开发者_高级运维t;Object> x, IEnumerable<Object> y)
Or is there another option?
I like the first because the relationship is explicit, I don't like it because of the extra work it puts on the client.
I like the second because the types are more primitive and require less work, I don't like it because the mapping is not explicit. I have to assume the order is correct.
Opinions please?
In .NET 4 you should be using Tuple<T1,T2>
. More info on the Tuple class at MSDN.
If you have access to it, use Tuple<T, R>
. If you don't, just write a generic Tuple
or Pair
class of your own. I would avoid using KeyValuePair
, just because it's verbose and has an association with Dictionary
.
I would prefer KeyValuePair of the two you mention as it's more expressive and keeps the relationship between the objects.
But I would rather create a class which holds a reference to your pair. This is more readable in my opinion, and it never hurts to create an extra class or struct to express your actual actions.
(Pseudo-code)
class MyPair
{
public TypeA One;
public TypeB Two;
}
MyClass(IEnumerable<MyPair> objects)
There's mention of Tuple<,>
in some answers, which is as readable as KeyValuePair, but more flexible as it can contain more than two parameters.
[Edit - more indepth about Tuples/classes after a good nights sleep]
To Tuple or Not To Tuple
The first is my preferred method, as one sequence could be longer than the other with the second - it doesn't maintain the required 1:1 mapping.
In my opinion, the second option gives both you and the client more work to do. The first option is safer and harder to get wrong. I would pick the first option or something like it every time.
I'd certainly prefer the first. As you say, the correlation in explicit, and it's less error-prone than the second where you need to test that both collections are of the same length. Of course, it may actually be appropriate to offer /both/ ways, depending on the actual class you're building.
One thing, if you use .NET 4.0, I'd recommend using Tuple<T1, T2>
instead of KeyValuePair.
I think you have to go with option 1. There must be explicit relationships involved or you're just asking for trouble.
I would use the second version (because it is simpler) + comments + static / dynamic checks. If you can use code contracts, try to make sure that collections are of the same length, not null. If not, then do the same with Debug.Assert
as well as if ... throw ArgumentException
. Alternatively, you could create your own object containing the pair, but then it becomes harder if you want to use generics for members of the pair. Also, when you create an object that is meant to be stored in a container, you have to properly implement GetHashCode
, Equals
, etc. The first "Effective C#" book has an item on this.
As a general rule, I prefer not to over-engineer method signatures.
I usually provide both, with the KeyValuePair constructor delegating into the 2-arg constructor. Best of both worlds.
I like the first method for two reasons.
If they are storing their relationships in a Dictionary<> object already, they can just hand off the dictionary as is - no extra code required.
If they are using their own custom storage then giving you KeyValuePairs in an IEnumerable is really easy with the yield statement
Something like this:
IEnumerable<KeyValuePair<Object,Object>> GetMappedPairs()
{
foreach( var pair in _myCustomData )
{
yield return new KeyValuePair{Key = pair.ID, Value = pair.Data};
}
}
Option number 2 is not easily understood by developers using your method so it would require documentation and comments to explain how to use it.
Incidentally, you would probably be served best by declaring your method as a generic instead of hard coding KeyValuePair
MyClass<TKey,TValue>( IEnumerable<KeyValuePair<TKey,TValue>> pairs );
It may be worth considering why you expose an enumerable at all. You could use an Add(Object a, Object b) method that completely hides your internal way of dealing with the pairs. The client could then set up their own means of adding relations to it as either sets or singly.
Of course if you still need a method taking an enumerable, then the first option is probably best: an explicit relationship. But you consider using or making a Tuple or Pair type to use instead of KeyValuePair, assuming the relationship between them isn't one of Key to Value nature.
Now what if you did something like this?
// 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);
}
This would allow clients to write code like this:
// totally hypothetical example
var stockReturns = StockReturns.Create(prices, p => p.Close, p => p.PrevClose);
精彩评论