В чем разница между объявлением явного универсального параметра и использованием ограничения? - PullRequest
0 голосов
/ 29 января 2019

Какая разница между этим:

    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;
    }

И этим:

    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;
    }

Насколько я могу заметить, компилятор не говорит мне, что нет 'ничего плохогоНо этот подход лучше, чем другой?Будет ли оно иметь другое возвращаемое значение (потому что я еще не понимаю об этом)?

1 Ответ

0 голосов
/ 29 января 2019

Прежде всего: лучшая ставка для ответа на этот вопрос была бы попробуйте .Вскоре вы увидели, что когда вы пытались вызвать функцию во второй форме, она не работает.

Тем не менее, давайте немного разберёмся с этим.У нас есть

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

против

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

Почему второй неверен?Ну, какой аргумент типа вы хотели бы передать для TValue?Предположим, у нас в руке Dictionary<string, List<int>>.Что такое TValue, который мы можем использовать?Это не int, потому что это не соответствует ограничению: int не происходит от List<int>.Но это не List<int>, потому что List<int>> не происходит от List<TValue>, то есть List<List<int>>.

Итак, теперь мы знаем, почему второе неверно.Давайте теперь ответим на дополнительные вопросы:

При каких обстоятельствах имеет смысл этот вид "рекурсивного" ограничения?

Предположим, мы пытаемся найти максимум ключ в словаре:

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;
}

Здесь имеет смысл ограничить TKey до IComparable<TKey>;мы собираемся сравнить ключи.

Как другие люди могут использовать этот шаблон и злоупотреблять им?

См. https://blogs.msdn.microsoft.com/ericlippert/2011/02/03/curiouser-and-curiouser/ для многих примеров.

Почему первый метод не идеален?

Поскольку он не обрабатывает эти два случая:

Во-первых, предположим, что у нас есть многоцелевое выражение, но оно не из ключей для списков:

Dictionary<string, int[]>

или

Dictionary<string, Stack<int>>

или

Dictionary<string, IEnumerable<int>>

или что-либо еще.

Во-вторых, это также не относится к делу

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

хотя этот случай довольно редкий;вам не следует расширять List<T> как обычно.

Как правильно реализовать метод словаря для максимальной универсальности?

Вот один из способов:

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();
}

Для большей общности вы можете использовать IDictionary вместо Dictionary.

...