2009-11-19 8 views
1

EDIT:Является ли обычной (или поощряемой) практикой перегружать функцию для принятия IEnumerable <T>, ICollection <T>, IList <T> и т. Д.?

Из ответов, это было сделано достаточно ясно для меня, как на самом деле должен быть реализован проект, я спрашиваю о ниже. С учетом этих предложений (и в ответ на комментарий, вежливо указывающий, что в моем примере кода нет даже компиляции), я отредактировал следующий код, чтобы отразить общее мнение. Вопрос, который остается, может уже не иметь смысла в свете кода, но я оставляю его так же, как и для потомков.


Предположим, у меня есть три перегруженные функции, один принимая IEnumerable<T>, один принимая ICollection<T> и один принимая IList<T>, что-то вроде следующего:

public static T GetMiddle<T>(IEnumerable<T> values) { 
    IList<T> list = values as IList<T>; 
    if (list != null) return GetMiddle(list); 

    int count = GetCount<T>(values); 

    T middle = default(T); 
    int index = 0; 

    foreach (T value in values) { 
     if (index++ >= count/2) { 
      middle = value; 
      break; 
     } 
    } 

    return middle; 
} 

private static T GetMiddle<T>(IList<T> values) { 
    int middleIndex = values.Count/2; 
    return values[middleIndex]; 
} 

private static int GetCount<T>(IEnumerable<T> values) { 
    // if values is actually an ICollection<T> (e.g., List<T>), 
    // we can get the count quite cheaply 
    ICollection<T> genericCollection = values as ICollection<T>; 
    if (genericCollection != null) return genericCollection.Count; 

    // same for ICollection (e.g., Queue<T>, Stack<T>) 
    ICollection collection = values as ICollection; 
    if (collection != null) return collection.Count; 

    // otherwise, we've got to count values ourselves 
    int count = 0; 
    foreach (T value in values) count++; 

    return count; 
} 

Идея заключается в том, что, если я у меня есть IList<T>, что облегчает мою работу; с другой стороны, я все еще могу работать с ICollection<T> или даже с IEnumerable<T>; реализация этих интерфейсов не так эффективна.

Я не был уверен, что это даже сработает (если среда выполнения сможет выбрать перегрузку на основе переданного параметра), но я протестировал ее и, похоже, это.

Мой вопрос: есть ли проблема с этим подходом, о котором я не думал? В качестве альтернативы, это на самом деле хороший подход, но есть лучший способ его достижения (возможно, попытавшись отбросить аргумент values до IList<T> и запустить более эффективную перегрузку, если бросок работает)? Мне просто интересно узнать мысли других.

+0

«GetMiddle for» IEnumerable и «ICollection» как «не собираются компилировать, поскольку« не все пути возвращают значение ». GetMiddle 'List собирается сбой, если передан список «нулевой элемент» с исключением исключения диапазона индекса. best, – BillW

+0

@BillW: Да, я понимаю это, хотя справедливо, что вы его воспитываете. Эти примеры кода были просто для освещения моего вопроса. –

+0

@ Dan Извините, что я «придирчивый, придирчивый»: это новая область исследований для меня, и я с сожалением могу сказать, что пока неясно, просто добавив что-то вроде возврата по умолчанию (T), позаботится обо всех возможных случаях и непонятно, что такое «лучшая практика» с точки зрения обработки исключений в таких методах утилиты: но, возможно, мне стоит просто задать вопрос о StackOverFlow :) best, – BillW

ответ

5

Если вы посмотрите, как методы расширения LINQ реализованы с использованием Reflector, вы можете увидеть, что несколько методов расширения на IEnumerable < T>, например Count(), пытаются передать последовательность ICollection < T> или IList < T> для оптимизации работы (например, с помощью ICollection < T>.Count, а не итерирование через IEnumerable < T> и подсчет элементов). Таким образом, ваш лучший выбор, скорее всего, примет IEnumerable < T>, а затем сделайте такие оптимизации, если доступны ICollection < T> или IList < T>.

+0

Прошу простить, возможно, «наивное» наблюдение за вашим, безусловно, отличным ответом: когда вы говорите «тогда сделайте такую ​​оптимизацию, если доступны ICollection или IList », это говорит о том, что могут быть случаи, когда ICollection и IList недоступны, и я пытаюсь представить, какими могут быть эти случаи. Если вы хотите уточнить, это было бы очень полезно. Спасибо, – BillW

+1

@BillW: Просто потому, что объект реализует 'IEnumerable ', он не следует, что он реализует любой из этих других интерфейсов.Я мог бы написать свой собственный класс, который позволяет вам перечислить его, но не предоставляет «Добавить», «Содержит», «Удалить» и т. Д. Это сценарий, о котором говорит Триллиан. –

+1

«Я пытаюсь представить, какими могут быть эти случаи» - наиболее распространенным случаем является «ленивая итерация» с использованием ключевого слова yield в блоке итератора. – Joe

0

Обычно при проектировании интерфейсов вы хотите принять тип «наименьшего общего знаменателя» для аргументов. Для типов возврата это вопрос некоторых дебатов. Я вообще думаю, что создание вышеуказанных перегрузок является излишним. Это самая большая проблема - это введение ненужных кодовых путей, которые теперь должны быть протестированы. Лучше иметь один метод, который выполняет операцию в одну сторону и работает в 100% случаев. С приведенными выше перегрузками вы можете иметь непоследовательность в поведении и даже не осознавать этого, или, что еще хуже, вы можете случайно ввести изменение в одном, а не в других экземплярах.

Если вы можете сделать это с помощью IEnumerable<T>, тогда используйте это, если нет, то используйте наименее необходимый интерфейс.

+1

То, что вы говорите, имеет смысл, хотя я считаю, что есть оправдание для использования определенных оптимизаций, если предоставленные аргументы могут быть переданы определенным типам, как предложено другими ответами, для повышения производительности. Мне кажется, лучший компромисс заключается в том, чтобы сделать метод «самый низкий общий знаменатель» единственным публичным и потенциально использовать внутри себя, где это необходимо, более эффективные перегрузки. –

1

Я бы сказал, что это необычно и потенциально сбивает с толку, так что вряд ли это будет хорошим выбором для публичного API.

Вы можете принять параметр IEnumerable < T> и внутренне проверить, действительно ли это ICollection < T> или IList < T>, и оптимизировать соответственно.

Это может быть аналогичным некоторым оптимизации в некоторых методах расширения IEnumerable < T> в .NET 3.5 Framework.

+1

«... и внутренне проверить, есть ли это ...» Единственная проблема с этим подходом заключается в том, что он также создает больше путей кода. Держите его простым и оптимизируйте только тогда, когда профайлер говорит вам, что это ваш ответ по умолчанию. –

+0

@ csharptest.net: Это очень смелый совет дать без какой-либо квалификации, например «предполагая, что вы не пишете высокопроизводительный код» (не беспокоясь о производительности, пока это абсолютно необходимо, может быть хорошо во многих ситуациях, но если производительность всегда критический, тогда вам нужно всегда думать об этом). –

+0

Если вы так обеспокоены производительностью, вы можете выйти из виртуальной машины! Это еще одна тема для обсуждения. –

2

Я думаю, что одна из версий, принимающая IEnumerable <T>, была бы способ пойти и проверить внутри метода, если этот параметр является одним из более производных типов коллекций. С трех версиях, как вы предлагаете, вы теряете преимущество эффективности, если кто-то проходит вам (выполнения) IList <T>, что компилятор статически рассматривает IEnumerable <T>:

 IList<string> stringList = new List<string> { "A", "B", "C" }; 
     IEnumerable<string> seq = stringList; 
     Extensions.GetMiddle(stringList); // calls IList version 
     Extensions.GetMiddle(seq);  // calls IEnumerable version 
+0

Хорошая точка! Я думаю ты прав; единственный публичный метод и (потенциально) многократные частные перегрузки методов - это путь. –

0

Нет. Это, конечно, редкость.

В любом случае. С IList <T> наследует от ICollection <T> и IEnumerable <T> и ICollection <T> наследуется от IEnumerable <T>, ваша единственная проблема будет производительность типов IEnumerable <T>.

Я просто не вижу причин, чтобы перегрузить функцию таким образом, обеспечивая различные сигнатуры для достижения точно такой же результат и принимать те же типы в качестве параметров (не важно, если у вас есть IEnumerable <T> или IList <T> , вы сможете передать его любой из трех перегрузок); это просто вызовет путаницу.

Когда вы перегружаете функцию, это просто способ передать другой тип параметра, который вы не можете передать функции, если у него не будет такой подписи.

Не оптимизируйте, если это действительно необходимо. Если вы хотите оптимизировать, сделайте это под прикрытием. Вы не будете притворяться, что кто-то использует ваш класс, чтобы быть в курсе этой «оптимизации», чтобы решить, какую подпись метода использовать, не так ли?

1

Я действительно безразличен. Если бы я это видел, я бы не подумал об этом. Но идея Джо имеет заслугу. Это может выглядеть следующим образом.

public static T GetMiddle<T>(IEnumerable<T> values) 
{ 
    if (values is IList<T>) return GetMiddle((IList<T>)values); 
    if (values is ICollection<T>) return GetMiddle((ICollection<T>)values); 

    // Use the default implementation here. 
} 

private static T GetMiddle<T>(ICollection<T> values) 
{ 
} 

private static T GetMiddle<T>(IList<T> values) 
{ 
} 
+0

Да, это в значительной степени то, что я имел в виду после прочтения некоторых других ответов. Благодаря! –

1

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

Если самый естественный способ реализации логики для ICollection<T> было бы написать специальный метод для этого, не было бы ничего особенно плохого в общественную перегрузку, которая принимает в ICollection<T>, и имея перегрузку IEnumerable<T> называют ICollection<T> один, если задан объект, который реализует ICollection<T>. Наличие такой перегрузки должно быть общедоступным, но это не повредит.С другой стороны, в тех случаях, когда объект реализует как IEnumerable<T> и ICollection, но не ICollection<T> (например, List<Cat> реализует IEnumerable<Animal> и ICollection, но не ICollection<Animal>), один, возможно, захотите использовать оба интерфейса, но это не может быть сделано без либо typecasting в методе, который их использует, либо передавая метод, который использует их как ссылку ICollection, так и ссылку IEnumerable<T>. Последний был бы очень уродливым в публичном методе, и прежний подход потерял бы преимущества перегрузки.

 Смежные вопросы

  • Нет связанных вопросов^_^