Товарищи, встал перед такой проблемой:
Мне необходимо реализовать нечто вроде хранилища объектов, которое бы выдавало по одному экземпляру указанного объекта на пользователя
Попробую пояснить с помощью псевдокода:
// Добавим в хранилище правило создания объекта
storage.Add("tmp", () => new Foo());
{
// В хранилище пока нет сгенерированных объектов
// Так что создаётся новый, сохраняется и возвращается
Foo tmp0 = storage["tmp"];
}
// Здесь ссылка на tmp0 уже недействительна
// Так что единственная ссылка на тот созданный объект лежит внутри хранилища
// Возвращаем тот же объект, что был в tmp0
Foo tmp1 = storage["tmp"];
// Тот объект, что был создан для tmp0, а теперь хранится в tmp1, уже занят
// Так что создаём новый объект, сохраняем его в хранилище и его же и возвращаем
Foo tmp2 = storage["tmp"];
То есть при завершении области видимости для tmp0
хранилище, содержащее в себе ссылку на тот же объект, его не удаляет, а сохраняет до следующего запроса подобного объекта.
То есть логика такая:
Такое поведение нужно мне по следующей причине: создание Foo
- трудоёмкий процесс. Помимо этого Foo
не является потокобезопасным, так что на руках у каждого пользователя Foo
должен быть свой уникальный экземпляр, который, возможно, уже был создан и использован ранее.
Вроде как нигде нет «счётчика» ссылок на объекты, так как сборщику мусора плевать, сколько там раз была объявлена ссылка на определённый объект. Важно лишь то, есть ли таковая ссылка вообще
Так что даже не знаю, возможно ли вообще реализовать подобное средствами C#
...
UPD:
Я никак не могу выбрать, какой ответ пометить галочкой, ибо ответы от iluxa1810 и default locale являются более правильными для общего случая. Однако в рамках моей весьма специфичной задачи более подходит метод, описанный в ответе от John...
Можно вывернуться, создав класс-обертку.
Я постараюсь вкратце сейчас, потому что с телефона.
Суть в том, что мы используем финализатор обертки, чтобы отловить событие удаления обертки и вернуть наш экземпляр в строй.
Создаём наш класс-обертку:
class FooWrapper
{
public event Action<int> Final;
private Foo foo;
private int id;
public FooWrapper(Foo foo, int id)
{
this.foo = foo;
this.id = id;
}
// Вот и наш финализатор
~FooWrapper()
{
if (Final != null)
Final(id)
}
}
В самой программе создаём два словаря. Один - для неиспользуемых экземпляров foo, а второй - для используемых.
Думаю, логика более или менее понятна?
1) по требованию пользователя ищется в словарях экземпляр Foo. В данном случае по Id.
2) если нет, то создаётся Foo и помещается во второй словарь. Его мы передаём в FooWrapper и обязательно подписываемся на событие Final. Полученный FooWrapper уже отдаем пользователю.
3) после того, как у пользователя пропадают все ссылки на FooWrapper срабатывает финализатор, в котором срабатывает событие Final, передающее ID нашего Foo.
4) В основной программе мы получаем этот Id и переносим из второго словаря в первый.
Главный недостаток, что в данном случае мы полностью исключает возможность взаимодействия напрямую с foo, только через методы, который в fooWrapper пропишешь.
Вот тут похожий вопрос и там предоставляется решение в виде использования WeakReference
-это такая ссылка на объект, которая не препятствует сборки мусора. У него есть свойство IsAlive
, которое говорит жив ли объект или уже был собран сборщиком мусора.
Мне видится, что это вы можете задействовать в своей задаче. В вашем случае, объект будет жив до тех пор, пока кто-то на него ссылается из кода.
Проверить, есть ли ссылка на объект в коде нельзя. .NET
ушли от подсчета кол-ва ссылок на объект в пользу построения графа доступности объектов.
Как альтернативный вариант- это ввести внутри вашего хранилища подсчет, заставляя пользователя вызывать спец. метод. Но тут все строится на доверии... Например, вдруг забудут что-либо вызвать => объект будет зарегистрирован за кем-то.
Возможно, в Net найдется механизм, который решит задачу, в том виде, в каком Вы ее написали. Но, если честно, мне это решение кажется неочевидным и хрупким:
неочевидным, т.к. конец блока кода легко пропустить при чтении, и, поэтому, редко используется для критичных операций:
} //за одним символом здесь кроется важная операция по освобождению объекта.
Как разработчик, я ожидаю, что блоки кода можно свободно переставлять при рефакторинге. В данном случае при этом нужно будет очень внимательно отследить срок жизни всех переменных. Ситуация усложнится если ссылки на Foo
будут сохраняться в полях других классов (объекты которых сами будут храниться в коллекциях и захватываться в лямбдах).
хрупким, т.к. в данном случае логика будет сильно зависеть от работы сборщика кода, которую сложно контролировать.
Для решения Вашей проблемы (дорогостоящие объекты, которые желательно переиспользовать) подойдет шаблон проектирования «объектный пул». Пулы широко используются для схожих задач (хранения соединений к БД, например).
В простейшем варианте Вам достаточно:
Foo
интерфейс IDisposable
.storage
отслеживать вызов метода Dispose
для созданных Foo
и освобождать объекты.storage
объекты в блок using
Foo
в отдельный интерфейс, который и возвращать из storage
. Реализацию Foo
оставить доступной только для storage
, чтобы никто не мог переопределить методы.Решение скучное и предполагает изменение вызывающего кода. Зато в данном варианте процесс освобождения объектов становится очевидным.
Ну и код будет выглядеть примерно так:
using(var tmp0 = fooPool.GetFoo("tmp"))
{
//работаем с одним объектом
}
using(var tmp1 = fooPool.GetFoo("tmp"))
{
using(var tmp2 = fooPool.GetFoo("tmp"))
{
//работаем с двумя объектами
}
}
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Какие существуют виды рекламных бордов и как выбрать подходящий?
Ребят, как мне сделать обновление в базу данных в RedBean? При заходе на статью он видит её id и какие то поля из бд, но при нажатии на кнопку id становится...
Есть сайт на wordpress с установленным плагином idcommerce в котором есть функция вывода верхнего меню
Есть запрос $query=mysqli_query($connect, "SELECT * FROM lvl_base WHERE steam LIKE '$rank'"); который в последующем обрабатывается5 таких запросов на одной странице в таблици...