Тема для многих не новая, но её тупость не теряет актуальности. Речь идёт об "интернировании строк", коей MS конечно же горда до усрачки!
Грабли, которые она заботливо разложила, не нужно долго искать, вот простой код:
var s1 = "stroka";
var s2 = "stroka";
var lst = new List<string>();
lst.Add(s1);
lst.Add(s2);
Console.WriteLine("I=" + lst.IndexOf(s2));
Как вы уже догадались, выведется "I=0". НО ПОЧЕМУ?? Где логика? В какой идиотской стране два одинаково выглядящих дома имеют ОДИН АДРЕС?
А чем строки отличаются от домов? Да ничем! Выглядеть одинаково != быть одним и тем же.
Но здесь ручка грабель только начинается, вот слегка модифицированный код:
var s1 = "stroka";
var s2 = "stroka";
var lst = new List<string>();
lst.Add(s1);
lst.Add("чупакабра");
lst.Add(s2);
lst.Remove(s2);
Console.WriteLine(Fmt.JSON(lst));
Вывод (логичный с т.з. MS) будет ["чупакабра","stroka"] , но мы же хотели удалить ВТОРОЙ экземпляр "stroka", какого чёрта?? Мы-то (наивные) ожидаем ["stroka", "чупакабра"]!
Чувствуете, как на лбу почёсывается шишка? Вот это оно! ЗАБОТА и "преждевременная оптимизация" 20-летних усатых "сеньоров". Как так можно глупо подходить к проблеме?? Неужели им самим не прилетало от подобных оптимизаций? Ах, да... они же "пилят компилятор", какое им дело до ваших... хм... ничтожных "прикладных задач"!
Думаете, в Core кто-то уберёт эти грабли? Как бы не так! Будут жрать, плакать, снова колоться, но кактус не бросят.
По-уму, надо было оставить сравнение строк (s1 == s2) ровно таким же, как для объектов — через сравнение ссылок. И ТОЛЬКО если речь идёт о контенте, сделать отдельный оператор
(s1 eq s2) или case-insensitive вариант (s1 ieq s2). Всё. Но разве может "поколение с бейсиком головного мозга" думать категориями Си?! Не даром они этот бейсик до 2021 года дотащили — по уму и инструмент.
Кому-то задачка может показаться узконаправленной, но вот не так это. Всплыла она на "простой ГУЙне", где всего-то и нужны были упорядоченные строки. "Чего может быть проще?", — подумал мой мозг и забыл надеть каску.
Ну и напоследок вопрос: кому-то вообще пригождались их вот эти "интернирования"?? Что, прямо в каждой программе миллионы одинаковых строк, что надо было усираться и экономить байты? Сэкономили — с гулькин писюн, а насрали на 20 лет вперёд! И они за это ещё получают зарплату...
Да, и чтобы мой гнев был полезным, вот в какой код вылилась простая задача "удалить из листбокса выделенные строки"!
var gen = lbxExtraData.ItemContainerGenerator;
var victimIdxs = new List<int>();
for (int i = extData.Count - 1; i >= 0; i--) { // extData - это враппер (List<string>)lbxExtraData.ItemsSourcevar iv = (ListBoxItem)gen.ContainerFromIndex(i);
if (iv.IsSelected) victimIdxs.Add(i);
}
foreach (var idx in victimIdxs)
extData.RemoveAt(idx);
lbxExtraData.RefreshView();
Вместо того, чтобы просто взять выделенные элементы (в отдельный массив) и удалить их из списка. Да...
Здравствуйте, Kolesiki, Вы писали:
K>Тема для многих не новая, но её тупость не теряет актуальности.
Тупость не теряет актуальности, но вот Майкрософта ли?..
K>Речь идёт об "интернировании строк"
Интернирование тут ни при чём. При чём дефолтный компаратор. Если ты не передаёшь компаратор в явном виде, то подписываешься на то, что его выберут за тебя.
K>В какой идиотской стране два одинаково выглядящих дома имеют ОДИН АДРЕС? K>А чем строки отличаются от домов? Да ничем! Выглядеть одинаково != быть одним и тем же.
Вот тебе два разных одинаково выглядящих дома, имеющих разный адрес:
K>Неужели им самим не прилетало от подобных оптимизаций?
K>По-уму, надо было оставить сравнение строк (s1 == s2) ровно таким же, как для объектов — через сравнение ссылок.
Обобщённые алгоритмы стандартной библиотеки не используют operator ==. Они используют либо переданный компаратор, либо дефолтный, который в свою очередь резолвит сравнение через переопределённый метод Equals, или фоллбэчатся к сравнению ссылок.
Здравствуйте, Qbit86, Вы писали:
Q>Интернирование тут ни при чём. При чём дефолтный компаратор. Если ты не передаёшь компаратор в явном виде, то подписываешься на то, что его выберут за тебя.
Ну хорошо, Стамбул — город контрастов! Но ведь граблей-то это не отменяет! Вот ещё более жизненный пример:
var s1 = "stroka";
var s2 = Console.ReadLine();// здесь вводим strokavar lst = new List<string>();
lst.Add(s1);
lst.Add("чупакабра");
lst.Add(s2);
lst.Remove(s2);
Console.WriteLine(Fmt.JSON(lst));
На выходе:
stroka
["чупакабра","stroka"]
Я согласен, с интернированием протупил, но компаратор-то всё равно с граблями! Нельзя просто за юзера решить "ты хочешь сравнить строки" (вместо адресов). Это не говоря о том, что даже само сравнение может быть ещё и case insensitive.
Здравствуйте, Kolesiki, Вы писали:
K>Нельзя просто за юзера решить "ты хочешь сравнить строки" (вместо адресов).
Поэтому большинство стандартных алгоритмов поиска/сравнения имеют перегрузки, принимающие на вход IEqualityComparer<T>. И только если пользователь игнорирует эти перегрузки, то алгоритмы используют EqualityComparer<T>.Default.
K>Это не говоря о том, что даже само сравнение может быть ещё и case insensitive.
Да, поэтому многие code style guide'ы и статические анализаторы предписывают явно задавать стратегию сравнения строк.
BCL предоставляет готовую пачку экземпляров компараторов строк StringComparer (реализующего IEqualityComparer<string?>) под разные нужды:
• StringComparer.Ordinal,
• StringComparer.InvariantCultureIgnoreCase, etc.
Их и нужно передавать в алгоритмы типа Dictionary<string, TValue>, Linq, etc.
И соответственно собственный библиотечный код пишут терминах IEqualityComparer<T>.
Прикладной же код может использовать для сравнения статические функции с третьим параметром — стратегией StringComparison:
• string.Equals(s1, s2, StringComparison.Ordinal),
• string.Equals(s1, s2, StringComparison.InvariantCultureIgnoreCase), etc.
Чего не нужно использовать для сравнения строк ни библиотечному, ни прикладному коду — operator ==() или экземплярный метод string.Equals().
K>Нельзя просто за юзера решить "ты хочешь сравнить строки" (вместо адресов).
Тебе нужно явно передать ReferenceEqualityComparer.Instance:
An IEqualityComparer{Object} that uses reference equality (object.ReferenceEquals(object?, object?)) instead of value equality (object.Equals(object?)) when comparing two object instances.
— /ReferenceEqualityComparer.cs
А для поиска использовать List<T>.FindIndex(). Правда, он получает на вход Predicate<string>, а не IEqualityCmparer<T>, но это тривиально оборачивается лямбдой.
Да, здесь уже нужно учитывать, что литералы интернируются.
Но закладываться в логике на сравнение по ссылке вместо сравнения по значению — это в принципе какой-то супер-специфичный сценарий. «Так не носят.» В исходниках BCL этот ReferenceEqualityComparer почти не используется.
Здравствуйте, Kolesiki, Вы писали:
K>Тема для многих не новая, но её тупость не теряет актуальности. K>
K>var s1 = "stroka";
K>var s2 = "stroka";
K>
Всё верно. Если s1 и s2 содержат одни и те же данные.
Критикуете — предлагайте.
Например, пусть ИИ догадывается, что вам нужно удалить.
Как правильно заметили есть RemoveAt, которым я неоднократно пользовался ранее.
Спасибо, что напомнили!
Здравствуйте, Kolesiki, Вы писали:
K>Тема для многих не новая, но её тупость не теряет актуальности. Речь идёт об "интернировании строк", коей MS конечно же горда до усрачки!
Интернирование не при чем.
Строки обладают структурной эквивалентностью, то есть ведут себя как value-типы.
Это все в спецификации написано, стоило бы почитать.
Здравствуйте, Kolesiki, Вы писали:
K>Да, и чтобы мой гнев был полезным, вот в какой код вылилась простая задача "удалить из листбокса выделенные строки"!
Даже если бы для List<string>.Remove изменили поведение. Тогда бы гнев переключился бы на List<int>.Remove, List<char>.Remove ? В них все равно придется конкретнее указывать — что нужно удалить.
Если приходится извращаться, то это недостатки ListBox, а не List<T>.Remove.
Здравствуйте, Kolesiki, Вы писали:
K>Да, и чтобы мой гнев был полезным, вот в какой код вылилась простая задача "удалить из листбокса выделенные строки"!
... K>Вместо того, чтобы просто взять выделенные элементы (в отдельный массив) и удалить их из списка. Да...
Жду сообщений от пользователей, которые будут спрашивать как им в ListBox'е из двух одинаковых строк выбрать ту, которую они действительно хотят удалить, а не ту, которая точно так же выглядит.
А причем тут интернирование? Есть ссылочная эквивалентность, есть структурная. Вот пример с аналогичным поведением без всяких строк:
record Address
{
public string Street { get; init; }
public int House { get; init; }
}
class Program
{
static void Main(string[] args)
{
var a1 = new Address { Street = "Svobody", House = 2 };
var a2 = new Address { Street = "Svobody", House = 2 };
var xs = new List<Address> { a1, a2 };
Console.WriteLine(xs.IndexOf(a2) == 0);
Console.WriteLine(ReferenceEquals(a1, a2));
}
}
Выводит:
True
False
Интернирование — это механизм оптимизации, который вообще никак на поведение не влияет, т.к. строки в дотнете неизменяемые. Да, есть unsafe, где можно натворить дел, но вроде как само слово "unsafe" недвусмысленно намекает, что, прежде чем туда лезть, неплохо бы в остальном хорошо разобраться.