Как сделать IEnumerable <T>только для чтения? - PullRequest
11 голосов
/ 11 декабря 2008

Почему списки 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()?

Спасибо

Ответы [ 8 ]

31 голосов
/ 11 декабря 2008

На самом деле IEnumerable<T> уже доступен только для чтения . Это означает, что вы не можете заменить какие-либо элементы в базовой коллекции другими элементами. То есть вы не можете изменить ссылки на Person объекты, которые содержатся в коллекции. Однако тип Person предназначен не только для чтения, и, поскольку он является ссылочным типом (то есть class), вы можете изменять его элементы посредством ссылки.

Есть два решения:

  • Используйте struct в качестве типа возврата (который создает копию значения каждый раз, когда оно возвращается, поэтому исходное значение не будет изменено & mdash; кстати, это может быть дорогостоящим)
  • Используйте свойства только для чтения для типа Person для выполнения этой задачи.
7 голосов
/ 11 декабря 2008

Возвращает новый экземпляр Person, который является копией p вместо p самого себя в Get (). Для этого вам понадобится метод создания глубокой копии объекта Person. Это не сделает их доступными только для чтения, но они будут отличаться от тех, что указаны в исходном списке.

public IEnumerable<Person> Get()
{
    foreach (Person p in l1)
    {
        yield return p.Clone();
    }
}
2 голосов
/ 11 декабря 2008

Они указывают не на одну и ту же коллекцию .Net, а на одни и те же Person объекты. Линия:

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

копирует все элементы Person из list1Instance.Get() в список p. Слово «копии» здесь означает, что копии ссылок. Итак, ваш список и IEnumerable просто указывают на одни и те же Person объекты.

IEnumerable<T> это всегда только для чтения, по определению. Однако объекты внутри могут быть изменяемыми, как в этом случае.

1 голос
/ 11 декабря 2008

Вы можете создать глубокий клон каждого элемента в списке и никогда не возвращать ссылки на свои оригинальные элементы.

public IEnumerable<Person> Get()
{
  return l1
    .Select(p => new Person(){
      FirstName = p.FirstName,
      LastName = p.LastName
    });
}
0 голосов
/ 28 ноября 2018

Этот код возвращает производный класс, поэтому по запросу тип возвращаемого значения не изменился.

Выдает ошибку, если вы пытаетесь изменить поле (через свойство), поэтому '1003 * только для чтения ' Если вы хотите иметь возможность изменять значения, не затрагивая оригинал, ответ клона лучше.

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";
    }
0 голосов
/ 11 декабря 2008

Если ваш персональный объект является реальным объектом, вам следует рассмотреть возможность использования неизменяемой версии.

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

Таким образом, невозможно изменить содержимое экземпляра после его создания, и поэтому не важно, чтобы существующие экземпляры повторно использовались в нескольких списках.

0 голосов
/ 11 декабря 2008

Прежде всего, ваш Список в вашем классе общедоступен, поэтому ничто не мешает кому-либо напрямую получить доступ к самому списку.

Во-вторых, я бы реализовал IEnumerable и вернул его в моем методе GetEnumerator

return l1.AsReadOnly().GetEnumerator();
0 голосов
/ 11 декабря 2008

IEnumerable<T> - только для чтения

p - это новая коллекция, которая не зависит от list1instance. Ошибка, которую вы сделали, заключается в том, что вы думали, что эта строка list[0].FirstName = "uf1";
изменил бы только один из списков, когда на самом деле вы изменяете объект Person.
Две коллекции отличаются друг от друга, они просто имеют одинаковые предметы.
Чтобы доказать, что они разные, попробуйте добавить и удалить элементы из одного из списков, и вы увидите, что на другой не влияет.

...