Как сделать IEnumerable доступным только для чтения?

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

Спасибо

11.12.2008 14:20:26
8 ОТВЕТОВ
РЕШЕНИЕ

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

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

  • Используйте structтип возврата (который создает копию значения каждый раз, когда оно возвращается, поэтому исходное значение не будет изменено - кстати, это может быть дорогостоящим)
  • Используйте свойства Personтипа « только для чтения » для выполнения этой задачи.
34
15.06.2017 18:20:42
Но вы можете бросить любую IEnumerable<T>спину к T[], List<T>или любого типа это на самом деле, и изменить это элементы (сами, а не их свойства).
Shimmy 25.11.2015 00:24:18
@Shimmy Если мы собираемся получить техническую информацию о том, что вы можете сделать, вы также можете использовать отражение, чтобы получить доступ к закрытым членам любого класса и делать все, что вы хотите. Просто потому , что вы можете сделать что - то (например , бросок IEnumerableк его фактическому типу) не означает , что вы никогда не должны.
Alex 22.09.2016 09:04:13

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

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

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

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

2
15.06.2017 18:22:05

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

public IEnumerable<Person> Get()
{
    foreach (Person p in l1)
    {
        yield return p.Clone();
    }
}
7
25.11.2015 00:22:16

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

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

0
11.12.2008 14:30:34

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

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

return l1.AsReadOnly().GetEnumerator();
0
11.12.2008 14:33:03

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

public IEnumerable<Person> Get()
{
  return l1
    .Select(p => new Person(){
      FirstName = p.FirstName,
      LastName = p.LastName
    });
}
1
11.12.2008 14:40:11

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

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

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

0
11.12.2008 14:42:59

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

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

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
28.11.2018 02:12:32