Страницы: 1
RSS
VBA. При удалении элементов из копии словаря, удаляются соответствующие элементы из словаря-источника
 
Всем привет.
Столкнулся с таким неприятным моментом.
Есть необходимость работать с Копией словаря, в т.ч. и с удалением элементов из нее.
Словарь-родитель должен при этом оставаться неизменным, но при удалении элементов из копии так-же удаляется элемент из основного словаря
Не хотелось-бы каждый раз при изменении копии перезаполнять основной, да и смысл в копии тогда теряется.
В чем причина такого поведения? Как обойти?
Спасибо
Скрытый текст

УПС. Была такая тема у соседей
Вопрос, наверное, исчерпан в той теме)
Хотя, как мне кажется, остался не раскрыт вопрос - ПОЧЕМУ?
Если я создаю переменную (dicCopy), то под нее выделяется свой участок памяти (или ошибаюсь?)
Вот что пишут у соседей
Цитата
Апострофф
Привет!
Наверно потому, что
dic2 = dic1
т.е. это один и тот же объект
?

Мне нужно было для решения в ЭТОЙ теме
Изменено: Sanja - 27.03.2026 08:20:51 (нашел обсуждение на другом форуме)
Согласие есть продукт при полном непротивлении сторон
 
Цитата
Sanja написал:
остался не раскрыт вопрос - ПОЧЕМУ?
Давай на другом примере
Код
Set wb = ActiveWorkbook
Set ws = wb.ActiveSheet

Если мы wb изменим у нас ws изменится?
А теперь давай представим, такую запись:
Код
Set wb = ActiveWorkbook
Set ws = wb '.ActiveSheet
 
Цитата
Sanja написал:
'копия основного словаря
Set dicCopy = dic
очень правильно написано - КОПИЯ. А для объектов - не просто копия, а ссылочная копия. Т.е. dicCopy это ссылка на dic, а не аналог CopyFile. Т.е. делая так ты не создаешь новый словарь со своими элементами, а просто ссылаешься на созданный ранее. С его элементами. Потому что ссылка идет на один и тот же участок памяти, что у исходного dic. Т.е. ты даже не ссылаешься, а "связываешь" два объекта.
Это как ссылка на ячейки на листе - поставил =A1 и та ячейка, в которой эта ссылка полностью повторяет значение ячейки, на которую ссылается. Со всеми вытекающими. Только в случае с Set dicCopy = dic эта связь двустороння. Измени что-то в одном - изменится в другом.
В общем-то, часто используемые конструкции типа:
Код
Set rr = Range("A1") 

ведут себя так же. и это же никого не удивляет :)
Изменено: Дмитрий(The_Prist) Щербаков - 27.03.2026 09:38:00
Даже самый простой вопрос можно превратить в огромную проблему. Достаточно не уметь формулировать вопросы...
 
Sanja, Причина в этой строке:
Код
Set dicCopy = dic
Ты не создаёшь копию словаря, а просто присваиваешь ссылку на тот же объект. В VBA объекты (включая Scripting.Dictionary) работают по ссылке:dic - указывает на объект в памяти dicCopy - после Set dicCopy = dic указывает на тот же самый объект. В итоге у тебя: dic и dicCopy - это один и тот же словарь и у тебя любое Remove из dicCopy = удаление из dic. В вашем случае нужно создать новый объект и вручную скопировать элементы:
Код
Set dicCopy = CreateObject("Scripting.Dictionary")

For Each iKey In dic.Keys
    dicCopy.Add iKey, dic(iKey)
Next
Ну и полный рабочий код будет таким:
Код
Option Explicit

Sub Remove_From_dicCopy_v2()
    Dim iTmp, iKey

    Dim dic         As Object
    Set dic = CreateObject("Scripting.Dictionary")

    Dim dicCopy     As Object
    Set dicCopy = CreateObject("Scripting.Dictionary")

    With Worksheets("Лист1")
        .Range("D2:E" & .Cells(.Rows.Count, "A").End(xlUp).Row).ClearContents

        Dim arr()
        arr = .Range("A2:A" & .Cells(.Rows.Count, "A").End(xlUp).Row).Value

        ' заполняем основной словарь
        For Each iKey In arr
            iTmp = dic(iKey)
        Next

        ' создаем РЕАЛЬНУЮ копию
        For Each iKey In dic.Keys
            dicCopy.Add iKey, dic(iKey)
        Next

        ' удаляем из копии
        For Each iKey In dicCopy.Keys
            If iKey Mod 3 = 0 Then dicCopy.Remove iKey
        Next

        .Range("D2").Resize(dic.Count) = Application.Transpose(dic.Keys)
        .Range("E2").Resize(dicCopy.Count) = Application.Transpose(dicCopy.Keys)
    End With

End Sub
 
Не, это все я понимаю.
Не дает покоя некоторая противоречивость)
Тут то мы создаеи ДВА НЕЗАВИСИМЫХ объекта
Код
Set dic = CreateObject("Scripting.Dictionary")
Set dicCopy = CreateObject("Scripting.Dictionary")

но почему
Цитата
MikeVol написал: dicCopy - после Set dicCopy = dic указывает на тот же самый объект.
Наверное это перекликается с ЭТОЙ темой
Согласие есть продукт при полном непротивлении сторон
 
Цитата
Sanja написал:
мы создаеи ДВА НЕЗАВИСИМЫХ объекта
Да. Притом при таком коде второй объект вообще нет смысл создавать через CreateObject, т.к. ты его переопределяешь как раз этой строкой:
Код
Set dicCopy = dic

Вот что будет, когда ты сначала определяешь диапазоны так:
Код
Set r1 = Range("A1")
Set r2 = Range("A2")

а потом сделаешь так:
Код
Set r2 = r1

?
Здесь абсолютно тоже самое.

P.S. Не знаю, даст ли эффект, но попробую технически это описать более-менее просто.
При создании любого объекта в памяти ОС выделяется под него кусочек памяти. Если точнее - ссылка на этот кусочек. Это мы все знаем.
Код
Set dic = CreateObject("Scripting.Dictionary")          'создали новый объект, ОС выделила под него ссылку на кусочек памяти
Set dicCopy = CreateObject("Scripting.Dictionary")  'создали новый объект, ОС выделила под него ссылку на кусочек памяти

Да, у каждого объекта свой кусочек. Пока что эти кусочки памяти живут отдельной жизнью, т.к. не было никаких команд, которые бы говорили о другом.
Заполнили dic. Его кусочек памяти расширился. У dicCopy пока остался прежним - мы же его не заполняли.
А теперь мы переопределяем dicCopy, делая ссылку на dic:
Код
Set dicCopy = dic

и вот здесь вступает в роль такая штука как оптимизация памяти. Зачем заполнять еще один кусок памяти одними и теми же данными? Незачем. Поэтому ОС не копирует все данные dic в ячейку памяти, выделенную под dicCopy - а создает там указатель на ячейку памяти dic. И теперь они связаны. По сути у них теперь одна ячейка памяти на двоих. И в итоге невозможно изменить один объект, не изменив второй.
Изменено: Дмитрий(The_Prist) Щербаков - 27.03.2026 10:39:34
Даже самый простой вопрос можно превратить в огромную проблему. Достаточно не уметь формулировать вопросы...
 
Да, я переопределяю ВТОРОЙ объект, но почему изменяется ПЕРВЫЙ? Т.е. из двух объектов, после таких манипуляций, остается один?
Согласие есть продукт при полном непротивлении сторон
 
Цитата
Sanja написал:
Да, я переопределяю ВТОРОЙ объект, но почему изменяется ПЕРВЫЙ?
выше дописал ПыСы :)
Изменено: Дмитрий(The_Prist) Щербаков - 27.03.2026 10:39:59
Даже самый простой вопрос можно превратить в огромную проблему. Достаточно не уметь формулировать вопросы...
 
Дмитрий, вот теперь все встало на место, спасибо большое)
Согласие есть продукт при полном непротивлении сторон
 
ещё интересная ссылка по теме
 
Цитата
написал:
ещё  интересная ссылка  по теме
Дополнение к тому, что написано по ссылке. Там описано для VB.NET.
В VBA для создания ссылки на объект нужно использовать Set obj1 = obj2. Если использовать только оператор =, то ссылки не будет.
Код
Set range1 = range2 'создаст ссылку на объект
Код
range1 = range2 'присвоит только значение(или массив значений) без создания ссылки на объект
 
Цитата
МатросНаЗебре написал:
присвоит только значение
тогда доп.уточнение: присвоит свойство по умолчанию. При этом тоже есть свои подводные камни :) Лучше так не делать, потому что свойство по умолчанию VBA может выбрать на свое усмотрение :) Выполните код, но внимательно следите за выполнением - специально добавил Stop:
Код
Sub UnexpectedProperty()
    Range("B1:B2").value = "1"
    Range("A1:A2").value = Range("B1:B2").value
    Stop
    Range("B1:B2") = "1"
    Range("A1:A2") = Range("B1:B2")
End Sub

во второй части свойство не указывается и  VBA применит ClearContents для Range("A1:A2").
Даже самый простой вопрос можно превратить в огромную проблему. Достаточно не уметь формулировать вопросы...
 
да ссылка больше для терминов была - "поверхностное копирование", "глубокое копирование" - вроде как общепринятые в программировании и как раз для объяснения сути темы, но ни в одном сообщении не использовались
 
Цитата
Дмитрий(The_Prist) Щербаков написал:  свойство по умолчанию VBA может выбрать на свое усмотрение...
VBA применит ClearContents...
В этом конкретном случае или всегда ClearContents?
Согласие есть продукт при полном непротивлении сторон
 
Цитата
Sanja написал:
В этом конкретном случае или всегда ClearContents?
смотря что имеется в виду. При попытке присвоения Range = Range без указания свойства и с кол-вом ячеек больше одной - я бы сказал всегда :)
Даже самый простой вопрос можно превратить в огромную проблему. Достаточно не уметь формулировать вопросы...
 
Sanja, добрый день!
код:
Код
Set dic = CreateObject("Scripting.Dictionary")
Set dicCopy = CreateObject("Scripting.Dictionary")

это просто тип переменной (в данном случае объектной). То есть, мы создаем два указателя одного типа (СЛОВАРЬ).
То есть создаем словарь dic и заполняем его. Теперь dic будет указывать на начало области памяти, знимаемое этим словарем. То есть это указатель на область памяти, откуда потом, при обращении к элементам словаря, будут считываться значения. Указател это просто ПАЛЕЦ, указывающий на область памяти. То есть "пойди туда (куда УКАЗЫВАЮ) и начинай считывать данные".
При написании
Код
dicCopy=dic
мы просто вторым ПАЛЬЦЕМ (dicCopy) указываем на область памяти, что и ПАЛЕЦ dic.
То есть, если значения изменяются в первом словаре, то они будут изменяться и во втором. Так как адреса переменных в словаре dic и в словаре dicCopy имеют один и тот же адрес.
Думаю, что более опытные участники подправят меня, если я ошибся где.
Страницы: 1
Читают тему
Наверх