Как стать автором
Обновить

Комментарии 91

А можно поинтересоваться что в данном контексте он (StringBuilder ) даст? По моему создавать такой тяжелый экземпляр класса только для последовательной конкатации строк - это не практично. Есть какие-то особые причины?
При использовании Вашего способа результатом каждой конкатенации будет новый экземпляр класса string.
То есть, создается очень много "легких" классов.
Вы меня заинтриговали, сейчас протестирую, чтобы не получилось что я дурак и тупо сотрясаю воздух повторяя Рихтера.
Пойду почитаю Рихтера...
Результат у меня: 2465ms против 2ms



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConcatTest
{
class Program
{
static void Main(string[] args)
{
int count = 10000;
List arr = new List (count);
for (int i = 0; i < count; i++)
{
arr.Add("string №" + i.ToString());
}



DateTime dt1 = DateTime.Now;
string res1 = "";
foreach (string s in arr)
{
res1 += s;
}
DateTime dt2 = DateTime.Now;
StringBuilder res2 = new StringBuilder("");
foreach (string s in arr)
{
res2.Append(s);
}
DateTime dt3 = DateTime.Now;
Console.WriteLine(((TimeSpan)(dt2 - dt1)).TotalMilliseconds.ToString());
Console.WriteLine(((TimeSpan)(dt3 - dt2)).TotalMilliseconds.ToString());
Console.ReadLine();
}
}
}
Весьма убедительно! Спасибо.
2 года спустя…

Еще один вариант (как по мне — самый элегантный):

string.Join("", list.ToArray())

Для 100000 элементов в списке он оказывается даже быстрее, чем StringBuilder.
Кстати Рихтер об этом пишет:) Рихтер, похоже, уже все знает:) И об НЛО и о суперхабре:)
НЛО прилетело и опубликовало эту надпись здесь
а не поделитесь книжком?
НЛО прилетело и опубликовало эту надпись здесь
спасибо за книжку
благодарю
Переписал код на новый с использованием StringBuilder. Еще раз спасибо, раньше вообще не подозревал о таких нагрузках при конкатенации строк.
а полноценные открытые классы там есть? Или только статические методы можно добавить?
Есть partial классы. Их можно расширять как душе вздумается. Для меня сейчас это актуально в основном для расширения функционала DataContext классов, которые генерируются при создании ORM базы данных (LINQ to SQL classes). Очень удобно расширить объектную модель какой-нибудь таблицы "Cities" методом типа GetCityBanks. А потом писать city.GetCityBanks()...
Можно и явно в классе прописать (благо metal генерирует cs файлы):) Хотя конечно partial class - это труъ!:)
Не айс. Хотя бы потому что я часто через дизайнер удаляю и добавляю таблицы, следовательно и определения классов будут удаляться и все ваши и изменения в том числе. Никому не рекомендую руками редактировать сгенерированный cs файл DataContext ORM.
Майкрософт тоже не рекомендует:) об этом пишут в начале сгенерированного файла:) Другое дело, что редактировать можно:) Речь ведь идёт о можно/нельзя, а пытливому уму можно все?
Где там?
Если речь про С#, ответ безусловно есть.
Методы расширения доступны только в 3 редакции языка, и их преимущество я вижу в возможности изменять набор функций класса, недоступного для редактирования (от стороннего разработчика, например).
Да и главное условие, все исходные коды partial классов должны быть на руках во время компиляции. Нельзя расширить класс имея только сборку где он лежит.
easyman прав, StringBuilder работает гораздо быстрее из-за особенностей хранения объектов string в .Net.

От себя добавлю:
вместо

return result.ToString();

лучше писать

string str = result.ToString();
result.Length = 0; //псевдо вызов Dispose(), вдруг боооольшой текст будет:)
return str;
Не думаю что это нужно, после завершения выполнения объект result подлежит сборке мусора
А когда произойдёт сборка мусора? Сейчас? Через час? А память нужна сейчас:)
кому нужна? GC соберет всё в своё время. особенно когда будет заканчиваться память.
Ну, тогда пусть компьютер думает за нас:) А я умываю руки:)
что и делает .net
это микроскопом гвозди забивать - никогда не слышали о premature optimization?
Цитирую:)
Donald Knuth, the renowned computer scientist, once said that “Premature optimization is the root of all evil.”
Ну, все это шутки. А если серьёзно, то слышал.
Прошу всех не увлекаться и не писать тут про garbage collection и прочие вещи managed кода. Это правда отвлекает от темы. Предлагаю специалистам поднять эту тему в отдельном топике. Мне самому было бы интересно почитать.
Чего-то мне и эта строка не нравится:

result.AppendFormat(", {0}", list[i].ToString());

я бы предложил

result.Append(", ");
result.Append(list[i].ToString());
Рихтер утверждает, что AppendFormat один из самых часто используемых методов. Значит не критично в принципе. Что касается практики, я провел некоторые эксперименты и убедился что разницы не видно. Наоборот, иногда ваш метод проигрывал по скорости, скорее всего из-за лишнего вызова функции. Впрочем, этот вопрос остается открытым и думаю в разных ситуациях можно применять тот или иной способ.
Да, рефлектор говорит, что слишком много кода в AppendFormat.
Либо так
result.Append( string.format(", {0}", list[i].ToString()) );
я плакал. вот метод String.Format из .net framework 2.0:
public static string Format(IFormatProvider provider, string format, params object[] args)
{
if ((format == null) || (args == null))
{
throw new ArgumentNullException((format == null) ? "format" : "args");
}
StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8));
builder.AppendFormat(provider, format, args);
return builder.ToString();
}

И на какие выводы сеё напрашивается?
Ну-ка:) Какие выводы?:)
У меня пока один:)
Надо посмотреть в рефлекторе метод AppendFormat:)
вы не поняли, мастер-фломастер сверху сказал, что "что слишком много кода в AppendFormat" ему рефлектор показал, предложив использовать string.format.
пена в том, что этот самый string.format использует StringBuilder.AppendFormat

вывод такой, что сей индивидум даже и не подумал заглянуть в метод string.format а ломанулся писать свои просто чудесные идеи.

вот.
Апсолютно, согласен с предыдущим оратором:)
Идея "глубокая":) И, кстати, я понял:) Только оставил без внимания, чтобы свой последний нерв сильно не терзать.
Тем не менее, хочется посмотреть, что там в методе AppendFormat.
У меня только под рукой рефлектора нет. Поэтому и попросил посмотреть:)
непонял, с кем вы согласны? с mlurker или со мной?
к сожалению, код appendformat действительно большой и разорвет топик :( смысл эго смотреть скажите?

вообще если уж оптимизировать AppendFormat то стоит использовать действительно использовать StringBuilder.Append
Я сегодня такой непонятный.
С Вами я согласен.
ибо AppendFormat внутри исопльзует Append
вы видели код AppendFormat рефлектором?
Согласен, не заглянул - сейчас посмотрел.
Но почему-то результатам тестирования, использование выражения:
result.Append( string.Format(", {0}", list[i]))

у меня получается быстрее (на 10% в среднем), чем
result.AppendFormat(", {0}", list[i])

Видимо jit оптимизирует.
НЛО прилетело и опубликовало эту надпись здесь
ДА! Ruby - хороший язык. И рельсы для него уже смазаны:)
>>public static string ListToString(this IList list)

Думаю без 'this' будет не только эстетичнее смотреться но и работать :–)
Поправьте
Вы ошибаетесь, this необходим. Копайте в сторону extention methods.
Да, что–то я поторопился. Можете дать ссылку на статью о extention methods?
Погуглите, тема очень популярная, наверняка что-нибудь быстро найдете.
Вот тут интересная статья про extention methods в C# в сравнении с Ruby
Вообще-то, бегло посмотрев статью, не увидел там сравнения.
Как я понял, статья рассказывает как с помощью методов расширения приблизить C# (по компактности синтаксиса) к Ruby.
Недавно у кого-то увидел довольно оригинальное использование extention methods.
Ребята создали extention method для строки, который с помощью Reflection создавал объект, имя котрого в строке прописано.
Выглядит примерно так:
IMyClass x = "MyClass".Create();
Ха-ха:)) Надо было Назвать как-нибудь понятнее - eval, например:)
Полезно. Поможет сократить количество кода.
Не, если уж тут о правильности кода заговорили...

Лучше переписать начало метода:


if (list == null || list.Count
Обрезалось...

if (list == null || list.Count
В общем, надо возвращать просто String.Empty, если список null или пустой :)

P.S. А метод AppendFormat всё-таки медленее двух Append.
Вот.. а тут возникает вопрос про String.Empty.
Это, конечно, оффтоп. Но есть мнение, что String.Empty лучше заменить на объявление пустой строки... т.е. просто ""!
Не слышал об этом (слышал об обратном). Можно ссылочку или объяснение по-подробнее?
Например, тут.
Раздел "Проверка на инициализацию строки".
Но где-то в блогах gotdotnet я встречал более понятное и развернутое объяснение со ссылками на первые версии frameworkа и подробным анализом. К сожалению, не нашёл.
Да, забавная статья. Могу посоветовать почитать "рецензию" на неё на рсдне (http://www.rsdn.ru/forum/message/2748130.1.aspx).

Самая быстрая проверка на пустоту строки - это str == null || str.Length == 0. Я же в своём комментарии хотел сказать, что чем создавать класс StringBuilder при пустом списке нужно возвращать просто пустую строку.

А использовать String.Empty или две двойные кавычки - это вопрос стиля программирования (я лично предпочитаю String.Empty).
Возможно я поторопился, когда дал эту статью в качестве примера.
В общем разницы большой не вижу.
(str == null || str.Length == 0) - очень быстро, труъ:)
у класса String есть статический метод IsNullOrEmpty, который в рефлеторе в принципе выглядит как
if (value != null)
{
return (value.Length == 0);
}
return true;
С этим методом есть проблема, описанная, например, здесь -http://msmvps.com/blogs/bill/archive/2006/04/04/89234.aspx.

Хотя, конечно, если писать нормально код, то таких ошибок не будет, но, всё же, предпочитаю пользоваться самописным методом, делающим то же самое.
спасибо за интересный линк, правда мне так и не удалось воспроизвести описанную ошибку
А насколько оправдано использование этих расширений?
Я про случай, когда я в своём классе определю метод с именем и параметрами, что и у расширителя. Тогда, если повезёт, у меня проект собиратся не будет, иначе потенциальная бага.
Может лучше явно использовать статические методы?
Ими вообще можно не пользоваться (это просто "синтаксический сахар"). Исполнение происходит все равно в .Net 2.0, поэтому компилятор преобразует метод расширения в метод класса.
Кажется, в простых словах, это так происходит.
Тут некоторая неточность. Методы расширения требуют поддержки со стороны .NET (подумайте, что может быть, например, с extension method при наследовании классов и определении методов с такой же сигнатурой в наследуемом классе). Т.е. этот пример будет компилироваться и работать только с .NET 3.5 (т.к. System.Core.dll существует только начиная с этой версии).
подумайте, что может быть, например, с extension method при наследовании классов и определении методов с такой же сигнатурой в наследуемом классе

Каких-таких наследуемых классах? Методы расширения по определению статические.

А если будет два метода-расширителя, подходящих по сигнатуре, то проект не соберется в принципе — надо будет явно указать, какой именно использовать.
Не совсем понял связи между предложениями первого абзаца. Или Вы считаете, что статические методы не наследуются? Или я что-то упускаю?

А второе Ваше возражение явно адресовано не мне, т.к. я ничего не утверждал о 2-х и более extension methods. Я имел в виду класс-наследник, у которого есть нестатический метод с названием, подобным названию метода-расширителя. Т.к. extension methods (даже будучи статическими) вызываются как метод экземпляра, то возможна некоторая путаница.

Вот, например, что делает данный код?


public class MyClass
{
public static void Main(string[] args)
{
new MyClass().RunMe();
new MyNewClass().RunMe();
new MyAnotherNewClass().RunMe();

MyClass x = new MyClass();
x.RunMe();

x = new MyAnotherNewClass();
x.RunMe();
}
}

public class MyNewClass : MyClass
{

}

public class MyAnotherNewClass : MyNewClass
{
public void RunMe()
{
Console.WriteLine("MyAnotherNewClass.RunMe()");
}
}

public static class Utils
{
public static void RunMe(this MyClass item)
{
Console.WriteLine("MyClass.RunMe()");
}
}

А, тьфу, по первому пункту это я не так понял. Почему-то подумал, что собираетесь наследовать класс, в котором объявлен extension method. Под конец рабочего дня думать вредно :)

А данный пример выведет, конечно же, что-то типа
MyClass.RunMe()
MyClass.RunMe()
MyAnotherNewClass.RunMe()
MyClass.RunMe()
MyClass.RunMe()
т.к. методы-расширители менее приоритетны, чем собственные методы класса.
когда-нибудь из с# получится наконец что-то хорошее. только оно будет копией языка Smalltalk...
Extension methods - очень мощное оружие, но и очень опасное. Почти как указатели. Применять необходимо с очень большой опаской и все методы должны быть снабжены комментариями - иначе при review такого кода получите очень много вопросов.
НЛО прилетело и опубликовало эту надпись здесь
А если класс sealed? А если тип класса неизвестен или вообще анонимный?
Честно почитал топик, почитал комменты. Ужаснулся. Взгустнул.

1. Не каждый Дот-нетчик знает как работает тот станок, на котором он точит детальки. Многие пытались, но до фишина не дошли. Так и ходим, не зная броду.

2. Пример с StringBuilder'ом (1-я ветка коммента) убил наповал. 2008 год уже, а у .Net все те же проблемы, как в стареньком K&R C language.

3. Создание экземпляра класса через Reflection... Вы бы еще побитовый сдвиг через strtoint делали..

4. Некоторые вещи поражают воображение. Видимо в Микрософте разрабы совсем ку-ку. Почему-то даже в таком "недоязыке" [(с) чей-то] как PHP, такие вещи, как создание объекта произвольного класса или транформация массива / списка в строку реализовано нативно.

Люди, вы вообще в своем уме? Вы же в себе хороните разработчика.
Спасибо за дельный совет. Пошел учить PHP с нативной реализацией объектов. Кстати, PHP компилируется в машинный код или все еще интерпретируемый язык?
Эм.. в машинный код не переводится. Переводится в байт-код, который ZendEngine внутри себя держит и выполняет.

Вы меня не совсем правильно поняли :) Я не агитирую конкретно за PHP. Я предлагаю оглянуться по сторонам, потому что замыкание на одной платформе ведет к деградации и лишает возможности выбора.

Поверьте, .Net далеко не пуп вселенной ;-)
>Создание экземпляра класса через Reflection...
а почему собственно нет? при разработке плагинов весьма полезно
Ну, тогда уж вместо for надо использовать foreach, т.к. не каждый IList реализован в виде массива - например, тот же StringBuilder внутри себя держит LinkedList, и поиск в нем занимает не O(1), а O(n).


public static string ListToString(this IList list)
{
StringBuilder result = new StringBuilder();

foreach (var item in list)
if (result.Length == 0)
result.Append(item.ToString());
else
result.AppendFormat(", {0}", item.ToString());

return result.ToString();
}
Большое спасибо! Приятно видеть среди пустых комментариев толковое замечание. Приму к сведению, но переделывать уже не стану. Топик считаю исчерпанным.
Вы не правы. Каждый IList устроен в виде массива. Посмотрите внимательно описание этого интерфейса. Среди public properties найдете Item.

Может вы путаете IList с IEnumerable?
Это ж в любой книжке про Design Patterns есть. Гради Буча все идем читать :)
Просто, красиво, элегантно. Отлично! Спасибо.
>> Extension methods - очень мощное оружие, но и очень опасное. Почти как указатели. Применять необходимо с очень большой опаской и все методы должны быть снабжены комментариями - иначе при review такого кода получите очень много вопросов.

С чего вы так решили? Существует язык компонентный паскаль (Оберон 2), его создатель Вирт, во многих статьях писал, что текущая реализация ООП (в языка C++, Delphi, C#) является не очень удачной (его гложили отличия класса от типа) и в своём языке реализовал ООП основываясь только на записях (обычные типы), к которым можно прицеплять методы, как раз с помощью подобия extension methods.

http://www.inr.ac.ru/~info21/cpascal/cp_report_1.4_rus.htm

Процедуры, описанные глобально , могут быть связаны с каким-либо типом записей, описанным в том же модуле. Такие процедуры называют методами [methods], связанными с данным типом записей. Связь выражается посредством указания типа принимающего параметра в заголовке описания процедуры. Получающий параметр может быть VAR или IN параметром типа T или параметром-значением типа POINTER TO T, где T — тип записей. Метод связан с типом T и считается в нем локальным.

ProcedureHeading = PROCEDURE [Receiver] IdentDef [FormalParameters] MethAttributes.

Receiver = "(" [VAR | IN] ident ":" ident ")".

MethAttributes = ["," NEW] ["," (ABSTRACT | EMPTY | EXTENSIBLE)].

Если метод M связан с типом T0, он также неявно связан с любым потомком T1 типа T0. Однако если метод M' (с тем же именем, что и у M) описан как связанный с T1, он становится связан с T1 вместо M. M' считается переопределением M для T1. Списки формальных параметров M и M' должны соответствовать [match], кроме случаев, когда M — процедура-функция, возвращающая указательный тип. В последнем случае тип результата функции M' должен быть расширением типа результата M (ковариантность) (см. Приложение A). Если M и T1 экспортируются (см. гл. 4), то M' тоже должен экспортироваться.



----------------------
При этом злые языки говорят, что мелкомягкие сотрудничают с Виртом и перетаскивают интересные вещи себе в C#. При этом любовь к перетаскиванию уже явна была неоднократна (с тем же LINQ), поэтому неверить как-то не получается.

И ещё пример: Оберон был первым компилируемым языком с поддержкой автоматической чистки мусора (возможно, тоже наследие Оберона).
Просьба к Вам и вообще ко всем - давайте код красивый в блоге-то писать)
String.Empty, например.
public static string AsFormat(this string format, params object[] args)
{
return String.Format(format, args);
}

и теперь можно:
string message = "Hello {0}, my name is {1}".AsFormat("Habraman", "SHSE");
По моему это некрасиво.
а Sring.Format("something {0} {1} ...", obj1, obj2) красиво?
стандартно
тоже полезное расширение
public static string Join(this IEnumerable<string> items, string separator)
{
  return string.Join(separator, items.ToArray());
}
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории