16

为什么列表list1Instance和下面代码pMain方法指向同一个集合?

class Person
    {
        public string FirstName = string.Empty;
        public string LastName = string.Empty;

        public Person(string firstName, string lastName) {
            this.FirstName = firstName;
            this.LastName = lastName;
        }
    }

    class List1
    {
        public List<Person> l1 = new List<Person>();

        public List1()
        {
            l1.Add(new Person("f1","l1"));
            l1.Add(new Person("f2", "l2"));
            l1.Add(new Person("f3", "l3"));
            l1.Add(new Person("f4", "l4"));
            l1.Add(new Person("f5", "l5"));
        }
        public IEnumerable<Person> Get()
        {
            foreach (Person p in l1)
            {
                yield return p;
            }

            //return l1.AsReadOnly(); 
        }

    }  

    class Program
    {

        static void Main(string[] args)
        {
            List1 list1Instance = new List1();

            List<Person> p = new List<Person>(list1Instance.Get());           

            UpdatePersons(p);

            bool sameFirstName = (list1Instance.l1[0].FirstName == p[0].FirstName);
        }

        private static void UpdatePersons(List<Person> list)
        {
            list[0].FirstName = "uf1";
        }
    }

我们可以在不改变返回类型的情况下改变这种行为List1.Get()吗?

谢谢

4

8 回答 8

41

事实上,IEnumerable<T> 已经是 readonly 了。这意味着您不能用不同的项目替换基础集合中的任何项目。也就是说,您不能更改对集合中保存的对象的引用。Person但是,该类型Person不是只读的,而且由于它是引用类型(即 a class),您可以通过引用更改其成员。

有两种解决方案:

  • 使用 astruct作为返回类型(每次返回时都会复制该值,因此原始值不会被更改 - 顺便说一句,这可能会很昂贵)
  • 使用该类型的只读属性Person来完成此任务。
于 2008-12-11T14:24:04.170 回答
8

在 Get() 中返回一个新的 Person 实例,它是 的副本p而不是自身。p您需要一种方法来制作 Person 对象的深层副本来执行此操作。这不会使它们成为只读的,但它们将与原始列表中的不同。

public IEnumerable<Person> Get()
{
    foreach (Person p in l1)
    {
        yield return p.Clone();
    }
}
于 2008-12-11T14:29:38.713 回答
2

它们不是指向同一个 .Net 集合,而是指向同一个Person对象。该行:

List<Person> p = new List<Person>(list1Instance.Get()); 

将所有 Person 元素从复制list1Instance.Get()到 list p。这里的“副本”一词是指复制参考文献。因此,您的列表IEnumerable恰好指向相同的Person对象。

IEnumerable<T>根据定义,它始终是只读的。但是,内部的对象可能是可变的,就像在这种情况下一样。

于 2008-12-11T14:26:15.057 回答
1

您可以对列表中的每个项目进行深度克隆,并且永远不要返回对原始项目的引用。

public IEnumerable<Person> Get()
{
  return l1
    .Select(p => new Person(){
      FirstName = p.FirstName,
      LastName = p.LastName
    });
}
于 2008-12-11T14:40:11.330 回答
1

如果您的 person 对象是真实对象,那么您应该考虑使用不可变版本。

 public class Person
 {
     public FirstName {get; private set;}
     public LastName {get; private set;}
     public Person(firstName, lastName)
     {
         FirstName = firstName;
         LastName = lastName;
     }
  }

通过这种方式,一旦创建实例就不可能更改其内容,因此在多个列表中重用现有实例并不重要。

于 2008-12-11T14:42:59.750 回答
0

IEnumerable<T> 只读的

p是一个不依赖于list1instance. 您犯的错误是您认为这一行 list[0].FirstName = "uf1";
只会修改其中一个列表,而实际上您正在修改Person对象。
这两个集合是不同的,它们只是碰巧有相同的项目。
要证明它们不同,请尝试从其中一个列表中添加和删除项目,您会发现另一个不受影响。

于 2008-12-11T14:30:34.453 回答
0

首先,您班级中的列表是公开的,因此没有什么可以阻止任何人直接访问列表本身。

其次,我将实现 IEnumerable 并在我的 GetEnumerator 方法中返回它

return l1.AsReadOnly().GetEnumerator();
于 2008-12-11T14:33:03.717 回答
0

此代码返回一个派生类,因此请求的返回类型没有改变。

如果您尝试更改字段(通过属性),它确实会引发错误,因此“只读”也是如此。如果您确实希望能够在不影响原始值的情况下更改值,那么上面的克隆答案会更好。

class  Person
{
    public virtual string FirstName { get; set; }
    public virtual string LastName { get; set; }


    public Person(string firstName, string lastName) {
        this.FirstName = firstName;
        this.LastName = lastName;
    }

}

class PersonReadOnly : Person
{
    public override string FirstName { get { return base.FirstName; } set { throw new Exception("setting a readonly field"); } }
    public override string LastName { get { return base.LastName; } set { throw new Exception("setting a readonly field"); } }

    public PersonReadOnly(string firstName, string lastName) : base(firstName, lastName)
    {
    }
    public PersonReadOnly(Person p) : base(p.FirstName, p.LastName)
    {

    }

}

class List1
{
    public List<Person> l1 = new List<Person>();

    public List1()
    {
        l1.Add(new Person("f1", "l1"));
        l1.Add(new Person("f2", "l2"));
        l1.Add(new Person("f3", "l3"));
        l1.Add(new Person("f4", "l4"));
        l1.Add(new Person("f5", "l5"));
    }
    public IEnumerable<Person> Get()
    {
        foreach (Person p in l1)
        {
            yield return new PersonReadOnly(p);
        }
        //return l1.AsReadOnly(); 
    }

}  
class Program
{

    static void Main(string[] args)
    {
        List1 list1Instance = new List1();

        List<Person> p = new List<Person>(list1Instance.Get());           

        UpdatePersons(p);

        bool sameFirstName = (list1Instance.l1[0].FirstName == p[0].FirstName);
    }

    private static void UpdatePersons(List<Person> list)
    {
        // readonly message thrown
        list[0].FirstName = "uf1";
    }
于 2018-11-28T01:53:58.367 回答