为什么以下代码的list1Instance
方法中的列表p
和Main
指向同一个集合?
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()
的返回类型吗?
由于
答案 0 :(得分:31)
事实上,IEnumerable<T>
已经是只读。这意味着您不能用不同的项替换基础集合中的任何项。也就是说,您无法将引用更改为集合中保存的Person
对象。但是,类型Person
不是只读的,因为它是引用类型(即class
),您可以通过引用更改其成员。
有两种解决方案:
struct
作为返回类型(每次返回时都会复制值,因此原始值不会被更改 - 顺便说一下,这可能代价很高)Person
类型的只读属性来完成此任务。答案 1 :(得分:7)
在Get()中返回一个新的Person实例,该实例是p
的副本,而不是p
。您需要一个方法来制作Person对象的深层副本来执行此操作。这不会使它们只读,但它们将与原始列表中的不同。
public IEnumerable<Person> Get()
{
foreach (Person p in l1)
{
yield return p.Clone();
}
}
答案 2 :(得分:2)
它们并不指向相同的.Net集合,而是指向相同的Person
对象。这一行:
List<Person> p = new List<Person>(list1Instance.Get());
将所有人员元素从list1Instance.Get()
复制到列表p
。这里的“复制”一词意味着复制参考文献。因此,您的列表和IEnumerable
恰好恰好指向相同的Person
个对象。
IEnumerable<T>
始终只读。但是,里面的对象可能是可变的,就像在这种情况下一样。
答案 3 :(得分:1)
您可以对列表中的每个项目进行深度克隆,并且永远不会返回对原始项目的引用。
public IEnumerable<Person> Get()
{
return l1
.Select(p => new Person(){
FirstName = p.FirstName,
LastName = p.LastName
});
}
答案 4 :(得分:0)
IEnumerable<T>
只读
p
是一个不依赖于list1instance
的新集合。
你犯的错误就是你认为这一行
list[0].FirstName = "uf1";
只会在修改Person
对象时修改其中一个列表
这两个系列是截然不同的,它们碰巧有相同的项目
要证明它们不同,请尝试在其中一个列表中添加和删除项目,您将看到另一个列表不受影响。
答案 5 :(得分:0)
首先,您班级中的列表是公开的,因此没有什么可以阻止任何人直接访问列表本身。
其次,我将实现IEnumerable并在我的GetEnumerator方法
中返回它return l1.AsReadOnly().GetEnumerator();
答案 6 :(得分:0)
如果您的person对象是真实对象,那么您应该考虑使用不可变版本。
public class Person
{
public FirstName {get; private set;}
public LastName {get; private set;}
public Person(firstName, lastName)
{
FirstName = firstName;
LastName = lastName;
}
}
通过这种方式,无法在创建后更改实例的内容,因此在多个列表中重用现有实例并不重要。
答案 7 :(得分: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";
}