Ключ, взятый из коллекции ключей, отсутствует в словаре
От: Воронин Иван Россия  
Дата: 30.03.22 10:11
Оценка: :)
Есть:

  Сортированный список:
private SortedList<TargetKey, Target> NewTarget = new SortedList<TargetKey, Target>();

  Структура значений:
    public struct Target
    {
        public String Code;
        public String Name;
        public String ItemCode;
        public String ItemName;
        public String DirecionCode;
        public String DirecionName;
        public String ParentCode;
        public Boolean isNP; //???
        public Byte Level;
        public DateTime DateBegin;
        public DateTime DateEnd;
        public void PrintToExcelSheet(ExcelWorksheet worksheet, int Row)
        {
            worksheet.Cells[Row, 1].Value = Row - 1;
            worksheet.Cells[Row, 2].Value = ItemCode;
            worksheet.Cells[Row, 3].Value = ItemName;
            worksheet.Cells[Row, 4].Value = DirecionCode;
            worksheet.Cells[Row, 5].Value = DirecionName;
            worksheet.Cells[Row, 6].Value = Level;
            worksheet.Cells[Row, 7].Value = DateEnd.ToString("dd.MM.yyyy");
            return;
        }
    }

  Класс ключей:
    public class TargetKey : IEquatable<TargetKey>, IComparable<TargetKey>
    {
        private String _Code;
        private String _Name;
        private byte _Level;

        public TargetKey()
        {
            this._Code = "";
            this._Name = "";
            this._Level = 0;
        }

        public TargetKey(String Code, String Name, byte Level)
        {
            this._Code = Code;
            this._Name = Name;
            this._Level = Level;
        }

        public void Set(String Code, String Name, byte Level)
        {
            this._Code = Code;
            this._Name = Name;
            this._Level = Level;
        }

        public String Code
        {
            get { return _Code; }
            set { _Code = value; }
        }

        public String Name
        {
            get { return _Name; }
            set { _Name = value; }
        }

        public byte Level
        {
            get { return _Level; }
            set { _Level = value; }
        }

        public override bool Equals(object obj)
        {
            if (obj == null) return false;
            TargetKey objAsTargetKey = obj as TargetKey;
            if (objAsTargetKey == null) return false;
            else return Equals(objAsTargetKey);
        }

        public bool Equals(TargetKey x)
        {
            return this._Code.Equals(x._Code) && this._Name.Equals(x._Name) && this._Level.Equals(x._Level);
        }

        public override int GetHashCode()
        {
            return this._Code.GetHashCode() + this._Name.GetHashCode() + this._Level.GetHashCode();
        }

        public int CompareTo(TargetKey compareTargetKey)
        {
            if (this == compareTargetKey) return 0;
            if (this < compareTargetKey) return -1;
            return 1;
        }

        public static bool operator ==(TargetKey a, TargetKey b)
        {
            if (a is null && b is null) return true;
            if (a is null || b is null) return false;
            if (a._Code == b._Code && a._Name == b._Name && a._Level == b._Level) return true;
            else return false;
        }

        public static bool operator !=(TargetKey a, TargetKey b) => !(a == b);

        public static bool operator <(TargetKey a, TargetKey b)
        {
            if (a is null || b is null) return false;
            if (a._Code.CompareTo(b._Code) < 0) return true;
            if (a._Name.CompareTo(b._Name) < 0) return true;
            if (a._Level.CompareTo(b._Level) < 0) return true;
            return false;
        }

        public static bool operator >(TargetKey a, TargetKey b)
        {
            if (a is null || b is null) return false;
            if (a._Code.CompareTo(b._Code) > 0) return true;
            if (a._Name.CompareTo(b._Name) > 0) return true;
            if (a._Level.CompareTo(b._Level) > 0) return true;
            else return false;
        }

        public static bool operator <=(TargetKey a, TargetKey b)
        {
            return (a < b) || (a == b);
        }
        public static bool operator >=(TargetKey a, TargetKey b)
        {
            return (a > b) || (a == b);
        }
    }


Список заполнен. Беру из него коллекцию ключей, прохожу по ней и смотрю значения, соответствующие ключу.

            ICollection<TargetKey> tKey = NewTarget.Keys;
            foreach (TargetKey tT in tKey)
            {
                MessageBox.Show(NewTarget[tT].Code + " | " + NewTarget[tT].Name + " | " + NewTarget[tT].Level);
            }


Вылетает исключение: System.Collections.Generic.KeyNotFoundException: "Данный ключ отсутствует в словаре."
Как в словаре может отсутствовать ключ, взятый из самого этого ключа?
Единственное, что могу предположить — некорректная перегрузка операторов сравнения и/или компаратора, но не понимаю, где именно.

Помогите найти проблемы.

ЗЫ. Если что, это не является моей профессиональной деятельностью.
Re: Ключ, взятый из коллекции ключей, отсутствует в словаре
От: 4058  
Дата: 30.03.22 11:13
Оценка:
Здравствуйте, Воронин Иван, Вы писали:

ВИ>Список заполнен. Беру из него коллекцию ключей, прохожу по ней и смотрю значения, соответствующие ключу.


ВИ>
ВИ>            ICollection<TargetKey> tKey = NewTarget.Keys;
ВИ>            foreach (TargetKey tT in tKey)
ВИ>            {
ВИ>                MessageBox.Show(NewTarget[tT].Code + " | " + NewTarget[tT].Name + " | " + NewTarget[tT].Level);
ВИ>            }
ВИ>


Если это самоцель, то проще не индексироваться по ключу:

            foreach (var pair in NewTarget)
            {
                Console.WriteLine(pair.Value.Code + " | " + pair.Value.Name + " | " + pair.Value.Level);
            }


т.к. пары (в SortedList) уже будут упорядочены по ключу.

ВИ>Вылетает исключение: System.Collections.Generic.KeyNotFoundException: "Данный ключ отсутствует в словаре."

ВИ>Как в словаре может отсутствовать ключ, взятый из самого этого ключа?
ВИ>Единственное, что могу предположить — некорректная перегрузка операторов сравнения и/или компаратора, но не понимаю, где именно.

Чтобы это понять, надо узнать значение ключа (tT) на котором это происходит.
Re: Ключ, взятый из коллекции ключей, отсутствует в словаре
От: samius Россия http://sams-tricks.blogspot.com
Дата: 30.03.22 11:16
Оценка:
Здравствуйте, Воронин Иван, Вы писали:

ВИ>Единственное, что могу предположить — некорректная перегрузка операторов сравнения и/или компаратора, но не понимаю, где именно.

Бинго!

ВИ>Помогите найти проблемы.

Здесь используется что-то вроде лексикографического порядка. В нем важно, что нет смысла сравнивать очередную пару полей, если предыдущая пара не совпала полностью.
Ну т.е. результат сравнения "abc" и "bcd" уже очевиден по сравнению первых символов.
Re: Ключ, взятый из коллекции ключей, отсутствует в словаре
От: karbofos42 Россия  
Дата: 30.03.22 11:49
Оценка: 88 (4) +3
Здравствуйте, Воронин Иван, Вы писали:

ВИ>Единственное, что могу предположить — некорректная перегрузка операторов сравнения и/или компаратора, но не понимаю, где именно.


Нужно убедиться в том, что a.CompareTo(b) и b.CompareTo(a) возвращают не противоречащие друг другу результаты.
Если смотрим оператор <, то при a.Code < b.Code возвращается сразу a < b.
Должны тогда и при a.Code > b.Code сразу возвращать, что a > b, но там идёт переход к сравнению Name и уже от него зависит кто меньше.
В итоге одновременно можно получить утверждения "a < b" и "a > b" и работа с таким типом будет непредсказуема.
В идеале и оператор > нужно привести в соответствие оператору <, а то там такая же история, но именно на коллекцию оно вроде бы не должно влиять в данном случае.
Re: Ключ, взятый из коллекции ключей, отсутствует в словаре
От: xpalex  
Дата: 31.03.22 08:28
Оценка: 83 (3) +2
Здравствуйте, Воронин Иван, Вы писали:

Проблема тут:
ВИ>
ВИ>        public static bool operator <(TargetKey a, TargetKey b)
ВИ>        {
ВИ>            if (a is null || b is null) return false;
ВИ>            if (a._Code.CompareTo(b._Code) < 0) return true;
ВИ>            if (a._Name.CompareTo(b._Name) < 0) return true;
ВИ>            if (a._Level.CompareTo(b._Level) < 0) return true;
ВИ>            return false;
ВИ>        }

ВИ>        public static bool operator >(TargetKey a, TargetKey b)
ВИ>        {
ВИ>            if (a is null || b is null) return false;
ВИ>            if (a._Code.CompareTo(b._Code) > 0) return true;
ВИ>            if (a._Name.CompareTo(b._Name) > 0) return true;
ВИ>            if (a._Level.CompareTo(b._Level) > 0) return true;
ВИ>            else return false;
ВИ>        }

ВИ>


Реализация SortedList использует массив для хранения элементов и свойство SortedList.Item[] использует бинарный поиск для получения индекса элемента в массиве.

Соответственно, что бы дойти до существующего ключа бинарным поиском, нужна корректная реализация интерфейса System.IComparable (метод
public int CompareTo(TargetKey compareTargetKey)
) для типа ключа.

Пусть есть ключи k1, k2, где k1 = TargetKey("3", "a", 1) и k2 = TargetKey("2", "b", 1))
k2.Compare(k1) == -1, т, к. оператор "<" вернет true в строке
if (a._Code.CompareTo(b._Code) < 0) return true;

и k1.Compare(k2) == -1, т, к. оператор "<" вернет true в строке
if (a._Name.CompareTo(b._Name) < 0) return true;


при добавлении в список первого ключа — он попадет в первую позицию списка (индекс 0),
при добавлении второго , т.к. k2 < k1 — k2 попадет в первую, а k1 переместится во вторую (индекс 1).

про попытыке найти элемент по ключу k1, будет взят ключ для сравнения из середины списка (индекс = floor(list.size/2) — 1 == 0), т.е. k2.
т.к. k1.CompareTo(k2) == -1, то алгоритм поиска попытается искать слева от индекса 0. Что и приводит к исключению.


Вообще референсным способом добавления операций сравнения является реализция кода сравнения в единственном месте (в методе CompareTo), а реализации операторов должны переиспользовать CompareTo. В вашем примере сделано наоборот (две реализации и обе некорректных) и из-за этого появилось нарушение контракта для реализации IComparable
Отредактировано 31.03.2022 8:41 xpalex . Предыдущая версия .
Re[2]: Ключ, взятый из коллекции ключей, отсутствует в словаре
От: Воронин Иван Россия  
Дата: 11.04.22 07:44
Оценка:
Здравствуйте, karbofos42, Вы писали:

K>Нужно убедиться в том, что a.CompareTo(b) и b.CompareTo(a) возвращают не противоречащие друг другу результаты.


Точно.
В соседнем классе пишу корректно, в этом — нет.
На одних данных получил правильный результат — дальше слепота.
Re[2]: Ключ, взятый из коллекции ключей, отсутствует в словаре
От: Воронин Иван Россия  
Дата: 11.04.22 07:44
Оценка:
Здравствуйте, xpalex, Вы писали:

X>Вообще референсным способом добавления операций сравнения является реализция кода сравнения в единственном месте (в методе CompareTo), а реализации операторов должны переиспользовать CompareTo. В вашем примере сделано наоборот (две реализации и обе некорректных) и из-за этого появилось нарушение контракта для реализации IComparable


Спасибо, буду знать.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.