How to make IEnumerable<T> readonly?

∥☆過路亽.° 提交于 2019-12-20 19:57:24

问题


Why are the lists list1Instance and p in the Main method of the below code pointing to the same collection?

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";
        }
    }

Can we change this behavior with out changing the return type of List1.Get()?

Thanks


回答1:


In fact, IEnumerable<T> is already readonly. It means you cannot replace any items in the underlying collection with different items. That is, you cannot alter the references to the Person objects that are held in the collection. The type Person is not read only, however, and since it's a reference type (i.e. a class), you can alter its members through the reference.

There are two solutions:

  • Use a struct as the return type (that makes a copy of the value each time it's returned, so the original value will not be altered — which can be costly, by the way)
  • Use read only properties on the Person type to accomplish this task.



回答2:


Return a new instance of Person that is a copy of p instead of p itself in Get(). You'll need a method to make a deep-copy of a Person object to do this. This won't make them read only, but they will be different than those in the original list.

public IEnumerable<Person> Get()
{
    foreach (Person p in l1)
    {
        yield return p.Clone();
    }
}



回答3:


They aren't pointing to the same .Net collection, but rather, to the same Person objects. The line:

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

copies all the Person elements from list1Instance.Get() to list p. The word "copies" here means copies the references. So, your list and IEnumerable just happen to point to the same Person objects.

IEnumerable<T> is always readonly, by definition. However, the objects inside may be mutable, as in this case.




回答4:


You could make a deepclone of each item in the list, and never return references to your original items.

public IEnumerable<Person> Get()
{
  return l1
    .Select(p => new Person(){
      FirstName = p.FirstName,
      LastName = p.LastName
    });
}



回答5:


IEnumerable<T> is readonly

p is a new collection which doesn't depend on list1instance. The mistake you made, is that you thought that this line list[0].FirstName = "uf1";
would only modify one of the lists, when on fact you're modifying the Person object.
The two collections are distinct, they just happen to have the same items.
To prove that they are different, try adding and removing items from one of the lists, and you'll see that the other one isn't affected.




回答6:


First of all, your List in your class is public, so there's nothing stopping anyone from directly accessing the list itself.

Secondly, I would implement IEnumerable and return this in my GetEnumerator Method

return l1.AsReadOnly().GetEnumerator();



回答7:


If your person object is a real object then you should consider using an immutable version.

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

In this way its not possible to change the content of the instance once created and therefore it isn't important that existing instances are reused in multiple lists.




回答8:


This code returns a derived class, so as requested the return type hasn't changed.

It does throw an error if you try and change a field (via property) so is 'read only'. If you did want to be able to change values without affecting the original the clone answer above is better.

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";
    }


来源:https://stackoverflow.com/questions/359495/how-to-make-ienumerablet-readonly

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!