Страницы

Поиск по вопросам

суббота, 28 декабря 2019 г.

ToUpper/Lower vs Unsafe

#c_sharp #unsafe


Думаю всем известно что строки (System.String) неизменяемые по своей природе. Поэтому
часто работа с такими методами, как string.ToUpper() и прочими, порождает новый объект,
почти идентичный предыдущему. 

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

Написал такой расширяющий метод:

public static unsafe String ToUpperUnsafe ( this String value )
{
    fixed ( Char* arrChr = value )
    {
        for ( var i = 0 ; i < value.Length ; ++i )
        {
            var temp = Char.ToUpper( value[ i ] );
            arrChr[ i ] = temp;
        }
    }

    return value;
}


Как видно, на вход принимается один объект, изменяется и возвращается без клонирования.
Вопрос в том, насколько это безопасно. Смотрел бегло исходники оригинального метода,
там много всего, что я не понял, из-за этого возникла мысль, что в определенных ситуациях
это может не прокатить.

p.s. Кстати ванильный метод работает быстрей, зато этот выигрывает в памяти.
    


Ответы

Ответ 1



Так делать нельзя. Дело в том, что весь framework исходит из того, что строки неизменяемы. Поэтому, например, если вы поместите строку в HashSet, а потом измените её, вы потом не сможете её оттуда удалить (а вместе с вами и другие куски программы, находящиеся далеко от вас и не знающие, что вы сломали строку). Метод сортировки, который не учитывает, что у него из-под носа могут заменить значение, имеет право зациклиться бесконечно или вылететь по обращению к неправильному индексу. Представьте себе, что будет, если строка, которую вы ломаете, интернирована. Тогда каждый, у кого была строковая константа "abc", внезапно без предупреждения получит строковую константу "ABC": string lo = "abc", hi = "ABC"; string villain = "abc"; villain.ToUpperUnsafe(); Console.WriteLine(lo == hi); // true Представьте себе так же, что вы сломаете строку, которая является названием типа. Что произойдёт при попытке применить рефлексию? Словом, ваш метод привносит в язык undefined behavour, отсутствие которого выгодно отличало C# от C++. Вся эта катавасия, которую может устроить одна «маленькая» оптимизация, совершенно не стоит выгоды в несколько микросекунд.

Ответ 2



Вопрос в том, насколько это безопасно. Да вообще небезопасно. Думаю всем известно что строки (System.String) неизменяемые по своей природе. ... Я даже не хочу выяснять причины этого Зря не хочешь. Представь всё, что записывается литералами. Это числа. В VB ещё даты. Всё это value-типы. int x = 50; x.Add(10); // Ну представим, что такой метод есть int y = 50; // Ну ты же не ждёшь, что теперь y равен 60? Аналогично со строками: string s = "abc"; s.ToUpper(); string t = "abc"; // Упс.. Твоя реализация сделает t = "ABC" По-моему, одного этого достаточно, чтобы никогда твой метод не использовать. А теперь, почему же reference-тип? Да это просто оптимизация - не надо копировать огромные строки при каждой передаче. Мы просто не меняем оригинал, поэтому семантически нет разницы между value и reference. А вот если мы хотим поменять, то надо создать новую строку.

Ответ 3



Вы только что изобрели велосипед :), используя unsafe, вместо класса StringBuilder, созданный именно для представления изменяемых строк.

Комментариев нет:

Отправить комментарий