创建常量IEnumerable <TSomeType>的方法......?

rse*_*nna 9 .net c# memory performance c#-3.0

也许这是一个愚蠢的问题......但是创造一个常数IEnumerable<TSomeType>...... 最好的(表现和记忆的)方式是什么?

如果无法定义"最佳"方式,哪些是我的选择?您有什么看法,您认为有最合适的方法吗?

例如:

  • var enumerable = (IEnumerable<TSomeType>) new List<TSomeType> { Value1, Value2, Value3 };
  • var enumerable = (IEnumerable<TSomeType>) new TSomeType[] { Value1, Value2, Value3 };
  • (其他一些选项;例如Linq Select).

请考虑内存和性能一个问题 - 我们谈论的是一个非常有限的环境(安装了.NET的小型设备).

提前致谢.

Jon*_*eet 9

好吧,既不List<T>是数组也不是数组都是不可变的,所以如果你真的在不可变性之后它们会被淘汰 - 调用者可以转换结果然后修改它.

你可以创建一个List<T>并将其包装在一个ReadOnlyCollection<T>.如果没有任何东西对原始列表有任何引用,那么它实际上是不可变的,除非反射.

如果你实际上并不关心不变性 - 即如果你相信所有的代码都不会弄乱它 - 那么数组几乎可以肯定是最高效的方法.有各种CLR级别的优化使它们的工作速度极快.但是,在这种情况下我不会强制转换IEnumerable<T>- 我只是将它暴露为数组.与编译器必须调用相比,这将使迭代更快GetEnumerator().

如果C#编译器foreach在数组上看到一个语句,它会生成调用直接转到索引器并使用该Length属性......然后CLR也能够删除边界检查,发现模式.

同样,如果你决定一起去List<T>,那就把它留下来List<T>- 这样你就可以List<T>.Enumerator直接使用- 这是一个结构 - 没有拳击.

编辑:Steve Megson提出了使用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这意味着迭代时只有一点点命中 - 只需要一次委托调用GetEnumerator().将其与使用相比较,Enumerable.Select每次调用时都需要额外的委托命中MoveNext()(以及无操作投影).


Jac*_*ope 5

如果它是常量,我会使用数组,因为不需要列表提供的额外功能来添加和删除项目.阵列也将具有最小的内存占用.


Fre*_*örk 5

虽然Jon 已经回答了使列表不可变的问题,但我还想指出,即使列表是不可变的,其包含的对象也不会自动不可变。

即使您使列表不可变(例如通过将其内容复制到 a 中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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我们有以下代码:

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]"
}
Run Code Online (Sandbox Code Playgroud)

如您所见,即使我们使列表有效地不可变,*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();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,上面的程序不会改变Person列表中实例的名称,而是改变它自己的副本。但是,请注意,我们现在拥有所谓的浅层不变性:如果Person反过来又会有不可变的成员,那么这些对象也存在同样的问题,依此类推……

Eric Lippert 在 2007 年就该主题撰写了由 10 部分组成的系列博客文章。第一部分在这里:C# 中的不变性第一部分:不变性的种类