views:

205

answers:

12

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<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?

+1  A: 

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.

Alex Humphrey
+1  A: 

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.

Christian Hayter
Yes, that's what I said.
Christian Hayter
A: 

I think you have to go with option 1. There must be explicit relationships involved or you're just asking for trouble.

KP
+5  A: 

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.

JSBangs
Also, last I checked, KeyValuePair doesn't serialize to XML, so that's another good reason not to use it if that's important to you.
AaronLS
+5  A: 

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

Mikael Svenson
Why should MyPair implement IEnumerable?
Alex Humphrey
@Alex, because it shouldn't :) You are right, and I apparently need to sleep.
Mikael Svenson
Dont worry about it matey - I can relate to the sleep thing :)
Alex Humphrey
They let you sleep at your company?!
Jay
@Jay, Europe :) 10pm, and my kid woke me up way too early. But I guess I could sleep at work since almost everyone is on vacation.
Mikael Svenson
@Mikael: They give you time off to go home?! I've heard rumors of companies that let people go home almost EVERY NIGHT ... Probably just a myth, though.
Jay
+9  A: 

In .NET 4 you should be using Tuple<T1,T2>. More info on the Tuple class at MSDN.

tvanfosson
.NET 4 only: http://msdn.microsoft.com/en-us/library/dd268536.aspx#versionsTitleToggle
Korbinian
For the sake of the argument. Why use Tuple instead of your own pairing class?
Mikael Svenson
@korbinian -- that's what I thought, but MSDN threw me off by including a link to "previous versions" that includes .NET 3.5. Unfortunately, the remarks there (which I didn't look) truthfully indicate the reality that it is only .NET 4.
tvanfosson
@Mikael - so that you don't have to maintain a class that already exists. If you need more than a simple container, though, it might be worth it.
tvanfosson
@tvanfosson, I see your point, but I think a class with a good name is better, at least if it's used more than one place. I use Tuple almost exclusively for return types to avoid "out", and only if the output is consumed in one place. Sort of a fire and forget. Same as I use KeyValuePair in dictionary loops.
Mikael Svenson
I felt I had too much to say on this for a comment, so I blogged about it - http://techmikael.blogspot.com/2010/07/too-tuple-or-not-too-tuple.html
Mikael Svenson
A: 

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.

Hamish Grubijan
A: 

I usually provide both, with the KeyValuePair constructor delegating into the 2-arg constructor. Best of both worlds.

benjismith
Just curious - I understand how you can go from the two arg method to the one arg by doing a Zip - how do you do it the other way around?
Alex Humphrey
public MyClass(KeyValuePair<K, V> pair) : this(pair.Key, pair.Value) { }
benjismith
+1  A: 

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.

Korbinian
A: 

I like the first method for two reasons.

  1. If they are storing their relationships in a Dictionary<> object already, they can just hand off the dictionary as is - no extra code required.

  2. 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 );
Paul Alexander
A: 

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.

CodexArcanum
A: 

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);
Dan Tao