Как получить ссылку на поле обьекта структуры, или на сам обьект, если обьекты хранятся в List?

Имеется вложенный List, который хранит обьекты структуры. Мне нужно изменить одно поле в структуре. Есть вариант копировать все значения поля и вставлять новый элемент в список, и удалять старый обьект, но это очень медленно и никто так не делает. Понимаю, что для таких случаев есть классы, но мне нужно сделать это со структурой.

Читал, что это только из list нельзя узнать ссылку на обьект, но кроме list незнаю альтернатив, которые позволяют изменять количество элементов во время выполнения программы. Пытался вот так: ref имя структуры rf = ref mass[i][j]; Пытался узнавать ссылку только того поля, которое мне нужно ref char rf = ref mass[i][j].object; Но так тоже не работает Выдаёт ошибку Cannot modify the return value of 'List...' because it is not a variable

Тупой, но наглядный пример кода с проблемой.

namespace c_sharp
{
    public struct dogs
    {
        public struct dog
        {
            public dog(int weight, int age)
            {
                this.weight = weight;
                this.age = age;
            }
            public int weight, age;
        }

        public static void add_dog(int weight, int age, int house_num, List<List<dogs.dog>> doggs)
        {
            doggs[house_num].Add(new dog(weight, age));
        }

        public static void change_age(List<List<dogs.dog>> doggs)
        {
            int old_age, i, j;
            bool br = false;
            Console.WriteLine("Enter the age of dog which you want to change age");
            old_age = Convert.ToInt32(Console.ReadLine());
            for (i=0; i<doggs.Count(); i++)
            {
                for (j=0; j<doggs[i].Count(); j++)
                {
                    if (old_age == doggs[i][j].age)
                    {
                        br = true;
                        break;
                    }
                    if (br) break;
                }
            }

            Console.WriteLine("Enter new age");
            int new_age = Convert.ToInt32(Console.ReadLine());

            ref int rf = ref doggs[i][j].age; // здесь проблема
            rf = new_age;

            // нужно присвоить полю обьекта doggs[i][j].age значение переменной new_age
        }

    }
    class Program
    {
        static void Main(string[] args)
        {
            Console.OutputEncoding = System.Text.Encoding.Default;
            List<List<dogs.dog>> doggs = new List<List<dogs.dog>>();
            for (int i = 0; i < 5; i++) doggs.Add(new List<dogs.dog>());

            dogs.add_dog(20, 5, 1, doggs);
            dogs.add_dog(50, 14, 3, doggs);

            dogs.change_age(doggs);
        }
    }
}

Ответы (2 шт):

Автор решения: Andrew

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

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

someList[20] = someValue;

А если запись является кастомной структурой или классом с полями - ты можешь менять только поле

someList[20].someField = someValue;

И независимо от того это структура или класс, ты можешь менять его внутри списка. Что ты НЕ можешь менять - так это readOnly (readonly/const) обьекты.

Так же ты не можешь менять обьекты любых списков которые перебираются циклом foreach - но это легко обойти используя иные циклы.

→ Ссылка
Автор решения: Alexander Petrov

Кратко: никак.
Со списком это невозможно.


Давайте упростим пример, чтобы было легче разобраться.

public struct Dog
{
    public Dog(int weight, int age) =>
        (Weight, Age) = (weight, age);

    public int Weight;
    public int Age;
}

Поместим в список три собаки. Потом у второй из них изменим вес.
Для этого создаём новый экземпляр структуры, в которой одно из значений копируем из старой, а второе задаём новым.
При этом в списке не надо ничего удалять, сдвигать и т. д.

var dogs = new List<Dog>();

for (int i = 1; i <= 3; i++)
    dogs.Add(new Dog(i * 10, i));

foreach (var dog in dogs)
    Console.WriteLine(dog.Weight + " " + dog.Age);
Console.WriteLine();

// Меняем значение. Потолстел пёсик.
dogs[1] = new Dog(25, dogs[1].Age);

foreach (var dog in dogs)
    Console.WriteLine(dog.Weight + " " + dog.Age);
Console.WriteLine();

Размер структуры Dog - 8 байтов: два поля int по 4 байта. Их копирование так же быстро, как копирование ссылки на объект. Поэтому о производительности в данном случае беспокоиться не нужно.


Вы пытаетесь в своём коде использовать ref - указатель на экземпляр структуры в коллекции. Но, как вы правильно заметили, это не работает с такой коллекцией, как List<T>. Именно потому, что это изменяемая в размере коллекция.
Если бы у вас был массив вместо списка, то можно было бы использовать следующий код:

var dogs = new Dog[3];

for (int i = 1; i <= 3; i++)
    dogs[i - 1] = new Dog(i * 10, i);

foreach (var dog in dogs)
    Console.WriteLine(dog.Weight + " " + dog.Age);
Console.WriteLine();

dogs[1].Weight = 25; // это работает

ref Dog d = ref dogs[2];
d.Weight = 35; // это тоже работает

foreach (var dog in dogs)
    Console.WriteLine(dog.Weight + " " + dog.Age);
Console.WriteLine();

Работоспособность этого связана с особым статусом массивов. Индексатор массива возвращает ссылку на элемент. В то время как индексатор List<T> возвращает сам элемент структуры.


Если вам очень-очень нужно получать ссылку на объект в списке, то можно написать свою реализацию коллекции с индексатором, возвращающим ссылку ref. Смотрите, например, Performance traps of ref locals and ref returns in C#. Но при этом следует использовать ref readonlyl структуры, иначе производительность пострадает.

→ Ссылка