一个通用列表在内存中是否是连续存储的?

3

有各种文章,例如 msdn.microsoft.com/en-us/library/ms379570(v=vs.80).aspx,似乎表明通用 List 的项是存储在连续的内存中。然而,我无法找到任何明确的陈述来证明这一点,无论是肯定还是否定。有人知道吗?请注意,很清楚地指出数组是连续的,但这个问题适用于List。

此外,有没有办法直接访问 List 使用的内存?


我认为你需要在泛型列表上调用ToArray()方法,以便开始直接访问它的内存。 - dustyhoppe
1个回答

7
List<T>的源代码非常明确:
public class List<T> : IList<T>, System.Collections.IList, IReadOnlyList<T>
{
    private const int _defaultCapacity = 4;

    private T[] _items;

一个 List<T> 包含一个 T[] 数组,所以如果数组是连续的(是的),那么 List<T> 也是连续的 :-)
并且从 List<T> 的 MSDN 页面得知:
"The List class is the generic equivalent of the ArrayList class. It implements the IList generic interface by using an array whose size is dynamically increased as required."
至于第二个问题:
"Also is there any way to access the memory used by a List directly?"
你似乎更偏向于理论程序员,所以答案是否定的。对于阅读此文的 "实际" 程序员,他们可以使用:
public static class ArrayFromList
{
    public static FieldInfo GetField<T>()
    {
        return ArrayFromListImpl<T>.Field;
    }

    public static T[] GetArray<T>(List<T> list)
    {
        return ArrayFromListImpl<T>.GetArray(list);
    }

    public static class ArrayFromListImpl<T>
    {
        public static readonly FieldInfo Field;

        public static readonly Func<List<T>, T[]> GetArray;

        static ArrayFromListImpl()
        {
            Field = typeof(List<T>)
                .GetFields(BindingFlags.Instance | BindingFlags.NonPublic)
                .Where(x => x.FieldType == typeof(T[]))
                .Single();

            var par = Expression.Parameter(typeof(List<T>));
            var exp = Expression.Lambda<Func<List<T>, T[]>>(Expression.Field(par, Field), par);
            GetArray = exp.Compile();
        }
    }
}

使用方法如下:

var lst = new List<int> { 1, 2, 3 };
var ptr = ArrayFromList.GetArray(lst);

请注意,这在一个“理论上”的List<T>上不起作用,因为它将数组隐藏在子对象中 :-)(但在.NET和Mono上可以工作:-))

3
是的,但这只是当前实现的细节,而不是公共接口层面的保证。 - goneskiing
@goneskiing 现在好了吗?请注意,这是C#,不是C++。.NET库没有对其所有特性进行啰嗦的描述。它们更多地是“这是Microsoft .NET...开心点”。 - xanatos
4
请注意,如果 T 是引用类型,那么句柄在内存中将是连续的,但实际对象不会连续。 - Lucas Trzesniewski
其实我并不是很理论!我有一些代码,可以使用 pin_ptr 将 .NET 数组数据固定,并将指针传递给一些非托管的 C++ 代码,以避免进行相当大的内存复制。我有一些无法更改的代码,它给了我一个 List 而不是 Array,所以我想做同样的事情。我喜欢你的代码。作为一个不熟悉 C# 的人,我认为这是使用反射来查找 _items 成员变量吗? - goneskiing
请注意,我只在类型能够进行按位复制的情况下执行此操作。 - goneskiing
2
@goneskiing 此处使用反射查找一个数组,而在微软的实现中该数组被命名为_items :-) 然后使用一些技巧来缓存对该数组的访问,以便于每种 List<T> 类型只使用一次反射,而连续使用则不再需要。请记住,该数组可能比 List<T>Count 更大! - xanatos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接