z3nth10n
z3nth10n

Reputation: 2441

What is the difference between declaring an explicit generic parameter and use a constraint?

Which is the difference between doing this:

    public static bool IsNullOrEmpty<TKey, TValue>
        (this Dictionary<TKey, List<TValue>> dictionary, TKey key)
    {
        return !dictionary.ContainsKey(key) || 
           dictionary.ContainsKey(key) && dictionary[key].Count == 0;
    }

And this:

    public static bool IsNullOrEmpty<TKey, TValue>
        (this Dictionary<TKey, TValue> dictionary, TKey key)
         where TValue : List<TValue>
    {
        return !dictionary.ContainsKey(key) || 
           dictionary.ContainsKey(key) && dictionary[key].Count == 0;
    }

As far as I can notice, the compiler doesn't tell me that there isn't anything wrong. But, is this any approach better than the other? Will it have any different return value (because I don't realize about this yet)?

Upvotes: 0

Views: 281

Answers (1)

Eric Lippert
Eric Lippert

Reputation: 660004

First off: your best bet for answering this question yourself would have been try it. You would have soon seen that when you attempted to call the function in its second form, it does not work.

That said, let's dig into this a bit. We have

public static bool IsNullOrEmpty<TKey, TValue>(
  this Dictionary<TKey, List<TValue>> dictionary, TKey key)

versus

public static bool IsNullOrEmpty<TKey, TValue>(
  this Dictionary<TKey, TValue> dictionary, TKey key)
  where TValue : List<TValue>

Why is the second one wrong? Well, what type argument would you like to pass for TValue? Suppose we have a Dictionary<string, List<int>> in hand. What is the TValue we can use? It's not int, because that doesn't meet the constraint: int does not derive from List<int>. But it is not List<int> either because List<int>> does not derive from List<TValue>, which is List<List<int>>.

So, now we know why the second one is wrong. Let's now answer more questions:

Under what circumstances does this sort of "recursive" constraint make sense?

Suppose we are trying to find the maximum key in a dictionary:

public static TKey MaxKey<TKey, TValue>(
  this Dictionary<TKey, TValue> dictionary)
  where TKey : IComparable<TKey>
{
  if (dictionary.Count == 0) throw ...
  TKey best = default(TKey);
  bool first = true;
  foreach(TKey k in dictionary.Keys)
  {
    if (first || best.CompareTo(k) < 0)
      best = k;
    first = false;
  }
  return best;
}

Here it totally makes sense to constrain TKey to IComparable<TKey>; we're going to compare keys.

What are some other ways that people use and misuse this pattern?

See https://blogs.msdn.microsoft.com/ericlippert/2011/02/03/curiouser-and-curiouser/ for many examples.

Why is the first method less than perfect?

Because it does not handle these two cases:

First, suppose we have a multidictionary but it is not from keys to lists:

Dictionary<string, int[]>

or

Dictionary<string, Stack<int>>

or

Dictionary<string, IEnumerable<int>>

or whatever.

Second, it also does not handle the case

class MyList : List<int> {}
...
Dictionary<string, MyList>

though that case is pretty rare; you ought not to be extending List<T> normally.

What is the right way to implement my dictionary method for maximum generality?

Here's one way:

public static bool IsEmpty(this IEnumerable items)
{
  // EXERCISE: Why is this implementation bad? 
  // EXERCISE: Can you improve it?
  foreach(var item in items)
    return false;
  return true;
}

public static bool IsNullOrEmpty<TKey, TValue>(
  this Dictionary<TKey, TValue> dictionary, TKey key) 
  where TValue : IEnumerable
{
  return !dictionary.ContainsKey(key) || dictionary[key].IsEmpty();
}

For even more generality you might use IDictionary instead of Dictionary.

Upvotes: 3

Related Questions