Способы создания константы IEnumerable <TSomeType>...? - PullRequest
8 голосов
/ 03 ноября 2010

Может быть, это глупый вопрос ... Но как лучше (с точки зрения производительности и памяти) создать постоянную IEnumerable<TSomeType> ...?

Если невозможно определить «лучший» способ, каковы мои варианты? Как вы думаете, есть ли наиболее подходящий способ сделать это?

Например:

  • var enumerable = (IEnumerable<TSomeType>) new List<TSomeType> { Value1, Value2, Value3 };
  • var enumerable = (IEnumerable<TSomeType>) new TSomeType[] { Value1, Value2, Value3 };
  • (некоторые другие опции; например, Linq Select).

Обратите внимание, что память и производительность представляют собой проблему здесь - мы говорим о действительно ограниченной среде (маленькое устройство с установленным .NET).

Заранее спасибо.

Ответы [ 5 ]

9 голосов
/ 03 ноября 2010

Ну, ни List<T>, ни массивы не являются неизменяемыми, поэтому они отсутствуют, если вы действительно после неизменности - вызывающая сторона может привести результат и затем изменить его.

Вы можете создать List<T> и заверните это в ReadOnlyCollection<T>.Если ничто больше не имеет ссылки на исходный список, то он фактически неизменен, за исключением отражения.

Если вы на самом деле не заботитесь об неизменности, то есть если вы доверяете всему коду, чтобы не связываться с ним, - тогдаМассив будет наиболее производительным подходом, почти наверняка.Существуют различные оптимизации уровня CLR, благодаря которым они работают невероятно быстро.Однако в этом случае я не приведу к IEnumerable<T> - я бы просто выставил его как массив.Это ускорит итерацию, чем если бы компилятору пришлось вызывать GetEnumerator().

Если компилятор C # видит оператор foreach в массиве, он генерирует вызовы, чтобы перейти прямо к индексатору и использоватьсвойство Length ... и затем CLR также сможет удалить проверку границ, обнаружив образец.

Аналогичным образом, если вы решите использовать List<T>, оставьте его как List<T> -таким образом, вы можете использовать List<T>.Enumerator - который является структурой - напрямую, без упаковки.

РЕДАКТИРОВАТЬ: Стив Мегсон выдвигает идею использования LINQ для этого.На самом деле, вы, вероятно, можете добиться большего, чем это, потому что, получив перечислитель базового списка, вы можете безопасно передать , что , вызывающей стороне, по крайней мере, для всех коллекций, о которых я знаю.Таким образом, вы можете получить:

public class ProtectedEnumerable<T> : IEnumerable<T>
{
    private readonly IEnumerable<T> collection;

    public ProtectedEnumerable(IEnumerable<T> collection)
    {
        this.collection = collection;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return collection.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Это означает, что при итерации будет только крошечный удар - только один делегированный вызов GetEnumerator().Сравните это с использованием Enumerable.Select, что потребует дополнительного нажатия на каждый вызов MoveNext() (а также проекцию no-op).

5 голосов
/ 03 ноября 2010

Если это константа, я бы пошел с массивом, так как нет необходимости в дополнительных функциях, которые предоставляет список для добавления и удаления элементов. Массив также будет иметь минимальный объем памяти.

4 голосов
/ 04 ноября 2010

Хотя Джон ответил на вопрос о том, как сделать список неизменным, я хотел бы также отметить, что даже если список неизменен, содержащиеся в нем объекты не являются автоматически неизменными.

Даже если вы сделаете список неизменным (например, скопировав его содержимое в ReadOnlyCollection<T>), вы все равно сможете манипулировать свойствами содержащихся объектов, если они не имеют неизменяемого типа . Как только вы раздадите ссылки на объекты, содержащиеся в списке, вызывающий код может манипулировать этими объектами.

Давайте рассмотрим пример:

class Person
{
    public string Name { get; set; }
}
class Group
{
    private readonly IEnumerable<Person> _persons;
    public Group(IEnumerable<Person> persons)
    {
        _persons = new ReadOnlyCollection<Person>(persons.ToList());
    }
    public IEnumerable<Person> Persons
    {
        get
        {
            foreach (var person in _persons)
            {
                yield return person;
            }
        }
    }
}

Тогда у нас есть следующий код:

List<Person> persons = new List<Person>( new[]{new Person { Name = "Fredrik Mörk" }});
Group smallGroup = new Group(persons);
Console.WriteLine("First iteration");
foreach (var person in smallGroup.Persons)
{
    Console.WriteLine(person.Name);
    person.Name += " [altered]";
}
Console.WriteLine("Second loop");
foreach (var person in smallGroup.Persons)
{
    Console.WriteLine(person.Name); // prints "Fredrik Mörk [altered]"
}

Как видите, даже если мы сделали список 1017 * эффективно неизменным, *Person не является неизменяемым типом. Поскольку свойство Persons класса Group передает ссылки на реальные объекты Person, вызывающий код может легко манипулировать объектом.

Один из способов защитить себя от этого - представить коллекцию как IEnumerable<T>, используя yield return и некоторый механизм клонирования, чтобы удостовериться, что вы не раздали исходные ссылки на объекты. Например, вы можете изменить класс Person на следующий:

class Person
{
    public string Name { get; set; }

    // we add a copy method that returns a shallow copy...
    public Person Copy()
    {
        return (Person)this.MemberwiseClone();
    }   
}

class Group
{
    private readonly IEnumerable<Person> _persons;
    public Group(IEnumerable<Person> persons)
    {
        _persons = new ReadOnlyCollection<Person>(persons.ToList());
    }
    public IEnumerable<Person> Persons
    {
        get
        {
            foreach (var person in _persons)
            {
                // ...and here we return a copy instead of the contained object
                yield return person.Copy();
            }
        }
    }
}

Теперь вышеприведенная программа не изменит имя экземпляра Person внутри списка, но его собственную копию. Однако обратите внимание, что теперь у нас есть то, что можно назвать мелкая неизменность : если Person в свою очередь будет иметь элементы, которые не являются неизменяемыми, то для этих объектов существует та же проблема, и так далее ...

Эрик Липперт написал серию постов из 10 статей на эту тему в 2007 году. Первая часть здесь: Неизменность в C # Часть первая: Виды неизменности .

2 голосов
/ 03 ноября 2010

Вы можете взглянуть на ReadOnlyCollection<T>, который оборачивает существующий IList<T> как доступный только для чтения и может быть приведен к IEnumerable<T>

http://msdn.microsoft.com/en-us/library/ms132474.aspx

0 голосов
/ 03 ноября 2010

AFAIK, из-за менее сложной структуры, правило будет таким: Если вам нужно увеличить или уменьшить размер коллекции, используйте список, если нет, и вы используете его только для перечисления, используйте массив.

Я не думаю, что константа и обычная переменная сильно влияют на создание объекта.

...