Если есть фрагмент кода, который вы повторяете все время в соответствии с «Не повторяйте себя», вы должны поместить его в свою собственную библиотеку и вызвать его. Имея это в виду, есть 2 аспекта, чтобы получить правильный ответ здесь. Первое - это ясность и краткость кода, который вызывает библиотечную функцию. Второе - это влияние foreach на производительность.
Сначала давайте подумаем о ясности и краткости в коде вызова.
Вы можете выполнить foreach несколькими способами:
- для цикла
- цикл foreach
- Collection.ForEach
Из всех способов составить список foreach. Каждый с ламбой - самый ясный и краткий.
list.ForEach(i => Console.Write("{0}\t", i));
Так что на данном этапе это может выглядеть как List.ForEach - это путь. Однако какова производительность этого? Это правда, что в этом случае время записи в консоль будет определять производительность кода. Когда мы знаем что-то о производительности определенной языковой функции, мы, безусловно, должны хотя бы рассмотреть это.
Согласно измерениям производительности Дастона Кэмпбелла foreach самый быстрый способ перебора списка в оптимизированном коде - использовать цикл for без вызова List.Count.
Цикл for, однако, является многословной конструкцией. Это также рассматривается как очень итеративный способ ведения дел, который не соответствует современной тенденции к функциональным идиомам.
Так можем ли мы получить краткость, ясность и производительность? Мы можем с помощью метода расширения. В идеальном мире мы создали бы метод расширения на консоли, который берет список и записывает его с разделителем. Мы не можем этого сделать, потому что Console - это статический класс, а методы расширения работают только с экземплярами классов. Вместо этого нам нужно поместить метод расширения в сам список (согласно предложению Дэвида Б.):
public static void WriteLine(this List<int> theList)
{
foreach (int i in list)
{
Console.Write("{0}\t", t.ToString());
}
Console.WriteLine();
}
Этот код будет использоваться во многих местах, поэтому мы должны выполнить следующие улучшения:
- Вместо использования foreach мы должны использовать самый быстрый способ итерации коллекции, представляющий собой цикл for с кэшированным счетчиком.
- В настоящее время только List может быть передан в качестве аргумента. Как библиотечная функция, мы можем обобщить ее, приложив небольшое усилие.
- Использование List ограничивает нас только списками, использование IList позволяет этому коду работать и с массивами.
- Поскольку метод расширения будет в IList, нам нужно изменить имя, чтобы было понятнее, к чему мы пишем:
Вот как будет выглядеть код функции:
public static void WriteToConsole<T>(this IList<T> collection)
{
int count = collection.Count();
for(int i = 0; i < count; ++i)
{
Console.Write("{0}\t", collection[i].ToString(), delimiter);
}
Console.WriteLine();
}
Мы можем улучшить это еще больше, разрешив клиенту передавать разделитель. Затем мы могли бы предоставить вторую функцию, которая пишет в консоль со стандартным разделителем, например:
public static void WriteToConsole<T>(this IList<T> collection)
{
WriteToConsole<T>(collection, "\t");
}
public static void WriteToConsole<T>(this IList<T> collection, string delimiter)
{
int count = collection.Count();
for(int i = 0; i < count; ++i)
{
Console.Write("{0}{1}", collection[i].ToString(), delimiter);
}
Console.WriteLine();
}
Так что теперь, учитывая, что нам нужен короткий, четкий и эффективный способ записи списков в консоль, у нас есть один. Вот весь исходный код, включая демонстрацию использования функции библиотеки:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleWritelineTest
{
public static class Extensions
{
public static void WriteToConsole<T>(this IList<T> collection)
{
WriteToConsole<T>(collection, "\t");
}
public static void WriteToConsole<T>(this IList<T> collection, string delimiter)
{
int count = collection.Count();
for(int i = 0; i < count; ++i)
{
Console.Write("{0}{1}", collection[i].ToString(), delimiter);
}
Console.WriteLine();
}
}
internal class Foo
{
override public string ToString()
{
return "FooClass";
}
}
internal class Program
{
static void Main(string[] args)
{
var myIntList = new List<int> {1, 2, 3, 4, 5};
var myDoubleList = new List<double> {1.1, 2.2, 3.3, 4.4};
var myDoubleArray = new Double[] {12.3, 12.4, 12.5, 12.6};
var myFooList = new List<Foo> {new Foo(), new Foo(), new Foo()};
// Using the standard delimiter /t
myIntList.WriteToConsole();
myDoubleList.WriteToConsole();
myDoubleArray.WriteToConsole();
myFooList.WriteToConsole();
// Using our own delimiter ~
myIntList.WriteToConsole("~");
Console.Read();
}
}
}
=============================================== ========
Вы можете подумать, что это должен быть конец ответа. Однако есть еще одно обобщение, которое можно сделать. Из вопроса Толстяка неясно, всегда ли он пишет на консоль. Возможно, что-то еще должно быть сделано в foreach. В этом случае ответ Джейсона Бантинга даст эту общность. Вот его ответ снова:
list.ForEach(i => Console.Write("{0}\t", i));
Это если мы не сделаем еще одно уточнение в наших методах расширения и добавим FastForEach, как показано ниже:
public static void FastForEach<T>(this IList<T> collection, Action<T> actionToPerform)
{
int count = collection.Count();
for (int i = 0; i < count; ++i)
{
actionToPerform(collection[i]);
}
Console.WriteLine();
}
Это позволяет нам выполнять любой произвольный код для каждого элемента в коллекции , используя самый быстрый из возможных методов итерации .
Мы даже можем изменить функцию WriteToConsole для использования FastForEach
public static void WriteToConsole<T>(this IList<T> collection, string delimiter)
{
collection.FastForEach(item => Console.Write("{0}{1}", item.ToString(), delimiter));
}
Итак, теперь весь исходный код, включая пример использования FastForEach:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleWritelineTest
{
public static class Extensions
{
public static void WriteToConsole<T>(this IList<T> collection)
{
WriteToConsole<T>(collection, "\t");
}
public static void WriteToConsole<T>(this IList<T> collection, string delimiter)
{
collection.FastForEach(item => Console.Write("{0}{1}", item.ToString(), delimiter));
}
public static void FastForEach<T>(this IList<T> collection, Action<T> actionToPerform)
{
int count = collection.Count();
for (int i = 0; i < count; ++i)
{
actionToPerform(collection[i]);
}
Console.WriteLine();
}
}
internal class Foo
{
override public string ToString()
{
return "FooClass";
}
}
internal class Program
{
static void Main(string[] args)
{
var myIntList = new List<int> {1, 2, 3, 4, 5};
var myDoubleList = new List<double> {1.1, 2.2, 3.3, 4.4};
var myDoubleArray = new Double[] {12.3, 12.4, 12.5, 12.6};
var myFooList = new List<Foo> {new Foo(), new Foo(), new Foo()};
// Using the standard delimiter /t
myIntList.WriteToConsole();
myDoubleList.WriteToConsole();
myDoubleArray.WriteToConsole();
myFooList.WriteToConsole();
// Using our own delimiter ~
myIntList.WriteToConsole("~");
// What if we want to write them to separate lines?
myIntList.FastForEach(item => Console.WriteLine(item.ToString()));
Console.Read();
}
}
}