用其他列表初始化的通用列表是否复制值或引用

本文关键字:列表 引用 复制 其他 初始化 是否 | 更新日期: 2023-09-27 17:54:04

如果我有一个List<T> Foo,并且通过将Foo传递给Bar的构造函数来初始化另一个List<T> Bar,那么Bar是否可以访问Foo中的原始对象?还是Bar中的对象是单独的副本?

这里有一个愚蠢的例子:

class Car
{
    public string Make   { get; private set; }
    public string Model  { get; private set; }
    public string Year   { get; private set; }
    public int FuelLevel { get; private set; } = 0;
    public int OilLevel  { get; private set; } = 0;
    public Car(string make, string model, string year)
    {
        Make = make;
        Model = model;
        Year = year;
    }
    public void Refuel()
    {
        FuelLevel = 100;
    }
}
class Program
{
    public static void Main(string[] args)
    {
        List<Car> CarsThatJoeOwns = new List<Car> { new Car("Ford", "Explorer", "2005"),
                                                    new Car("Hyundai", "Elantra", "2011") };
        // For some reason, Paul owns the exact same types of cars that Joe owns...
        List<Car> CarsThatPaulOwns = new List<Car> (CarsThatJoeOwns);
        foreach (Car car in CarsThatPaulOwns)
        {
            car.Refuel(); // <---- does this affect the cars that Joe owns too?
        }
    }
}

用其他列表初始化的通用列表是否复制值或引用

Car是一个引用类型,因此,将发生以下情况:

var car = new Car("Ford", "Explorer", "2005");
var carReference = car;
carReference.Refuel();
//Will have the value of 100, even if no method was called in the car
//object, but because it is a reference type, calling Refuel method
//on carReference will also affect to the variable referenced by
//carReference (car)
var fuelLevel = car.FuelLevel

在您的代码示例中,您有一个特定的部分:

List<Car> CarsThatJoeOwns = new List<Car>
{
    new Car("Ford", "Explorer", "2005"),
    new Car("Hyundai", "Elantra", "2011")
};
// For some reason, Paul owns the exact same types of cars that Joe owns...
List<Car> CarsThatPaulOwns = new List<Car> (CarsThatJoeOwns);

分配CarsThatPaulOwns时调用的List<T>构造函数将已经存在的列表CarsThatJoeOwns作为参数,该列表实现了接口ICollection<T>,因此CarsThatJoeOwns可以被广播到ICollection<T>

此外,请查看泛型List类的构造函数的源代码:

public List(IEnumerable<T> collection) {
    if (collection==null)
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
    Contract.EndContractBlock();
    ICollection<T> c = collection as ICollection<T>;
    if( c != null) {
        int count = c.Count;
        if (count == 0)
        {
            _items = _emptyArray;
        }
        else {
            _items = new T[count];
            c.CopyTo(_items, 0);
            _size = count;
        }
    }    
    else {                
        _size = 0;
        _items = _emptyArray;
        // This enumerable could be empty.  Let Add allocate a new array, if needed.
        // Note it will also go to _defaultCapacity first, not 1, then 2, etc.
        using(IEnumerator<T> en = collection.GetEnumerator()) {
            while(en.MoveNext()) {
                Add(en.Current);                                    
            }
        }
    }
}

因为CarsThatJoeOwns可以被广播到ICollection<T>,所以行c.CopyTo(_items, 0);将被执行,并且它执行以下操作(如Microsoft参考源中所示(:

public void CopyTo(T[] array, int arrayIndex) {
    // Delegate rest of error checking to Array.Copy.
    Array.Copy(_items, 0, array, arrayIndex, _size);
}

而且,正如MSDN文档中所述,Array.Copy将执行以下操作:

备注。。。如果sourceArray和destinationArray都是引用类型的数组,或者两者都是Object类型的数组执行复制。数组的浅层副本是新数组包含对与原始数组相同的元素的引用。这个元素本身或元素引用的任何内容都不是复制。相反,数组的深层副本复制元素和元素直接或间接引用的所有内容。

请注意"数组的浅副本是一个新数组,包含对与原始数组相同元素的引用">部分。简单地说,新列表CarsThatPaulOwns将保存对CarsThatJoeOwns列表中已经存在的对象的引用,foreach循环由定义

foreach (Car car in CarsThatPaulOwns)
{
    car.Refuel();
}

也会影响CCD_ 21列表中的值。反之亦然(在CarsThatJoeOwns列表上调用换料方法也会影响CarsThatPaulOwns列表(。

这是List<T> 的构造函数

    public List(IEnumerable<T> collection) {
        if (collection==null)
            ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
        Contract.EndContractBlock();
        ICollection<T> c = collection as ICollection<T>;
        if( c != null) {
            int count = c.Count;
            if (count == 0)
            {
                _items = _emptyArray;
            }
            else {
                _items = new T[count];
                c.CopyTo(_items, 0);
                _size = count;
            }
        }    
        else {                
            _size = 0;
            _items = _emptyArray;
            // This enumerable could be empty.  Let Add allocate a new array, if needed.
            // Note it will also go to _defaultCapacity first, not 1, then 2, etc.
            using(IEnumerator<T> en = collection.GetEnumerator()) {
                while(en.MoveNext()) {
                    Add(en.Current);                                    
                }
            }
        }
    }

如果集合可以是ICollection<T>,那么它将被复制到一个新的数组中。

如果没有,则执行Add:

    public void Add(T item) {
        if (_size == _items.Length) EnsureCapacity(_size + 1);
        _items[_size++] = item;
        _version++;
    }

这两个实例看起来都是在复制项的引用。但是,传递给构造函数的数组(也称为Foo(已丢失其引用。这意味着,对第一个列表的任何更新都不会更新传入的列表。

是。在您的情况下,新列表将参照现有的Car对象进行初始化。因此,调用Refuel函数将导致Paul和Joe两辆车都被修改(因为它们是同一个对象(。