Как посчитать число строк кода с помощью PowerShell

Узнать число строк кода С# (или LOC — lines of code ) в проекте на C# можно узнать одной строчкой PowerShell:

(dir -Include *.cs -Recurse -Exclude *designer.cs, AssemblyInfo.cs | select-string "^(s*)$" -notMatch).Count

Данная команда проходит по всем .cs файлам в текущем каталоге и его подкаталогах (опция —Recurse) исключая файлы AssemblyInfo.cs и файлы дизайнеров форм. select-string «^(s*)$» -notMatch исключает из подсчета пустые строки

Как избежать повторной подписки на события в C#

В механизме событий .NET отсутствует стандартная возможность избежать повторной подписки на сообщения. То есть, при повторной подписке на один и тот же обработчик будет вызываться столько раз, сколько раз была произведена подписка на событие. Но можно реализовать такую функциональность в программе самостоятельно одним из двух способов. Первый способ — удалять обработчик через оператор -= при добавлении нового подписчика и тут же его добавлять:

public class NonDuplicateEventArgs : EventArgs
    {
      public NonDuplicateEventArgs(string message)
      {
        Message = message;
      }

      public string Message { get; set; }
    }

    protected event EventHandler<nonDuplicateEventArgs> _nonDuplicateEvent;

    /// <summary>
    /// Событие с проверкой на добавление обработчиков-дубликатов путем удаления и добавления обработчика заново
    /// </summary>
    public event EventHandler<nonDuplicateEventArgs> NonDuplicateEvent
    {
      add
      {
        // пытаемся удалить обработчик, затем добавляем снова
        _nonDuplicateEvent -= value;
        _nonDuplicateEvent += value;
      }
      remove { _nonDuplicateEvent -= value; }
    }

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

using System.Linq;

 private EventHandler<nonDuplicateEventArgs> _nonDuplicateEventInvocList;

    /// <summary>
    /// Событие с проверкой  на добавление  обработчиков-дубликатов через GetInvocationList
    /// </summary>
    public event EventHandler<nonDuplicateEventArgs> NonDuplicateEventInvocList
    {
      add
      {
        if (_nonDuplicateEventInvocList == null ||
            !_nonDuplicateEventInvocList.GetInvocationList().Contains(value))
        {
          _nonDuplicateEventInvocList += value;
        }
      }
      remove { _nonDuplicateEventInvocList -= value; }
    }

Для того, чтобы решить, что же выбрать в своем приложении, я сделал небольшой тест. Как и ожидалось, метод с GetInvocationList для проверки одного и того же обработчика работает существенно медленнее, чем удаление и добавление — более чем в 5 раз медленнее (11.7 секунд против 2.2)

Action measureTime = (action, actionCaption) =>
{
var time = DateTime.Now;
action();
TimeSpan span = DateTime.Now — time;
Console.WriteLine(«{0} time: {1:0.000}», actionCaption,span.TotalSeconds);
};

int testCount = 10000000;

//замеряем скорость добавления одного и того же подписчика на событие
SmartEvents smartEvents = new SmartEvents();
measureTime(() =>
{

for (int i = 0; i < testCount; i++) { smartEvents.NonDuplicateEvent += OnNonDuplicateEvent; } }, "NonDuplicate"); measureTime(() =>
{

for (int i = 0; i < testCount; i++) { smartEvents.NonDuplicateEventInvocList += OnNonDuplicateEvent; } }, "NonDuplicateEvent with InvocationList"); [/sourcecode]

Однако если увеличивать количество обработчиков, то разница между двумя способами становится все менее заметной. Для 6 обработчиков разница будет несущественна, но первый метод все-таки быстрее, поэтому стоит использовать именно его.

//замеряем скорость добавления нескольких подписчиков в случайном порядке

var methods = new EventHandler[]
{
OnNonDuplicateEvent, OnNonDuplicateEvent2,
OnNonDuplicateEvent3, OnNonDuplicateEvent4,
OnNonDuplicateEvent5, OnNonDuplicateEvent6
};
testCount /= methods.Length;//чтобы долго не ждать

Random r = new Random(0);

var list = Enumerable.Range(0, testCount).Select(z => methods[r.Next(methods.Length)]).ToArray();

measureTime(() =>
{

for (int i = 0; i < testCount; i++) { smartEvents.NonDuplicateEvent += list[i]; } }, "NonDuplicate for" + methods.Length + "subscribers"); measureTime(() =>
{

for (int i = 0; i < testCount; i++) { smartEvents.NonDuplicateEventInvocList += list[i]; } }, "NonDuplicateEvent with InvocationList" + methods.Length + "subscribers"); } [/sourcecode] Полный код решения с тестами можно скачать по ссылке.

Under the Hood of .NET Memory Management

Компания Redgate представила первую часть книги, посвященной проблемам менеджемента памяти. Первая часть книги «Under the Hood of .NET Memory Management» доступна для скачивания.

Автообновление приложений .NET

В этой статье рассматривается два наиболее простых способа реализации автообновления приложения .Net. Первый способ — с помощью стандартной технологии Microsoft — Click Once, второй — с помощью опенсорного компонента NET Application Updater Component.

Обновление с помощью ClickOnce

Процесс использования ClickOnce описан в этой статье. Однако, установка с помощью этой технология делает неправильный мед» не позволяет установить программу для всех пользователей, поэтому переходим к следующему пункту.

Сразу оговорюсь, что в итоге в Windows 7 хранение в папке ProgramData требует администраторских прав и в итоге, описанное здесь решение можно рассматривать только в качестве собственной реализации автообновления, исходный код которого всегда можно изменить для своих нужд.

Обновление с помощью NET Application Updater Component

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

1. Создаем новый проект Windows Forms, например с именем MySimpleSample

2. Добавляем проекты AppStart и AppUpdater. Добавляем Reference на на проект AppUpdater. В AppUpdater есть ссылка на класс AppStartConfig, лежащий в проекте AppStart, поэтому проекты AppStart и AppUpdater лучше положить на одном уровне файловой структуры.

3. Добавляем на форму компонент AppUpdater ( Со окна Toolbox-AppUpdater Components). Тут были некоторые проблемы, скорее всего это связано с обновлением проекта до Visual Studio 2010 . UpdateLog хочет писать в файл AppUpdate.log, но текущим каталогом, возвращаемым функцией GetLogFilePath является путь к Visual Studio, куда, естественно запись запрещена. Можно поправить выбор каталога на что-нибудь такое :

DirectoryInfo DI = new DirectoryInfo(Assembly.GetCallingAssembly().Location);

В классе AppDownloader нас будет ожидать еще один сюрприз — захардкоженное имя конфига «AppStart.config». Явная недоделка — исправляем хотя бы на константу для централизации.

Читать далее Автообновление приложений .NET

Когда использовать Parallel.ForEach, а когда PLINQ

Мой перевод статьи «When Should I Use Parallel.ForEach? When Should I Use PLINQ?», опубликованный ранее на Хабрахабре.

Введение

Обычно, при оптимизации программы для многоядерных компьютеров первым шагом является выяснение возможности разделения алгоритма на части, выполняющиеся параллельно. Если для решения задачи необходимо параллельно обрабатывать отдельные элементы из большого набора данных, то первыми кандидатами станут новые возможности параллельности в .NET Framework 4: Parallel.ForEach и Parallel LINQ (PLINQ)

Parallel.ForEach

Класс Parallel содержит метод ForEach, представляющий собой многопоточную версию обычного цикла foreach в C#. Подобно обычному foreach, Parallel.ForEach выполняет итерации над перечислимыми данными (enumerable), но с использованием множества потоков. Одна из более часто используемых перегрузок Parallel.ForEach выглядит следующим образом:

public static ParallelLoopResult ForEach<tsource>(
			 IEnumerable</tsource><tsource> source,
			 Action</tsource><tsource> body)

Ienumerable указывает на последовательность, по которой нужно выполнить итерации, а Action body задает делегат, вызываемый для каждого элемента. Полный список перегрузок Parallel.ForEach можно найти здесь.

PLINQ

Родственный с Parallel.ForEach PLINQ представляет собой модель программирования для паралелльных операций над данными. Пользователь определяет операцию из стандартного набора операторов, включающих в себя проекции, фильтры, агрегирование и т.д. Подобно Parallel.ForEach PLINQ достигает параллельности, разбивая входную последовательность на части и обрабатывая элементы в разных потоках.

В статье выделяются различия между этими двумя подходами к параллельности. Разбираются сценарии использования, в которых лучше всего использовать Parallel.ForEach вместо PLINQ и наоборот.

Выполнение независимых операций

Читать далее Когда использовать Parallel.ForEach, а когда PLINQ

Восстановление счетчиков производительности Windows 7

По каким-то магическим причинам некоторые части механизма счетчиков производительности могут быть нарушены. Проявлением этого дефекта будет сообщение «Cannot load Counter Name data because an invalid index » was read from the registry.» при попытке чтения счетчиков производительности средствами .NET Framework. Если на форме находится компонент, то исключение сгенерируется прямо в функции InitializeComponent после вызова PostInit на компоненте отображения счетчиков производительности, что можно считать откровенной недоработкой, так как в этом случае скорее нужно было бы просто отображать текст с ошибкой.

Для того, чтобы починить счетчики производительности мне хватило выполнения команды:

 lodctr /R

Если это не помогает, то нужно обновить счетчики производительности .NET, для этого вызвать команду (Путь указан для 64-битной версии .NET 4, придется указать путь, актуальный в конкретной операционной системе.

 lodctr " c:WindowsMicrosoft.NETFramework64v4.0.30319corperfmonsymbols.ini"

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

Cсылки по теме:

Ошибка "Access to modified closure"

При неполном понимании мощных механизма инструментов языка можно делать неожиданные ошибки. Одним из таких случаев является использование итератора цикла в качестве переменной замыкания. Рассмотрим следующий пример, который интуитивно должен удалять из текста указанные гласные буквы.

 IEnumerable<char> query = "Not what you might expect";

 foreach (char vowel in "aeiou")
    query = query.Where(c => c != vowel);

    Console.WriteLine("Error:");
    foreach (char c in query)
    Console.Write(c); // Not what yo might expect

Казалось бы, все хорошо (если, конечно, не установлен Visual Assist или Resharper, тогда мы увидим предупреждение «Access to modified closure»), однако, после выполнения программы мы получим строчку «Not what yo might expect», удалилась только последняя буква. Чтобы понять, что произошло, нужно вспомнить, во что преобразуется цикл foreach, а преобразуется он в следующее:


IEnumerable<char> vowels = "aeiou";
using (IEnumerator<char> rator = vowels.GetEnumerator())
{
  char vowel;
  while (rator.MoveNext())
  {
    vowel = rator.Current;
    query = query.Where (c => c != vowel);
  }
}

На каждом шаге цикла мы добавляем в качестве условия проверку на неравенство переменной vowel. «Ленивый» оператор where захватывает одну и ту же переменную, поэтому при обращении к последовательности IEnumerable проверка и будет проходить на неравенство только одной переменной, получившей последнее значение. Для того, чтобы получить ожидаемый результат, необходимо добавить внутри цикла локальную переменную — тогда каждый раз будет захватываться новая переменная с новым значением.


foreach (char vowel in "aeiou")
{
  char cur = vowel;
  query = query.Where(c => c != cur);
}

CLR via C# third Edition


Так и не дождавшись перевода CLR via С#,заказал оригинальное издание на Amazon. На удивление почта России не подвела — при оценке срока доставки 1 сентября, уведомление о посылке я увидел 2 числа. Жалко, что обложка мягкая, но не помню, когда в последний раз видел такую белую бумагу в российских книжках. Книга не зря наделена пятью звездочками рейтинга. Конечно, Рихтер иногда чересчур уходит в особенности реализации платформ, но тем не менее, пожалуй, это единственный способ узнать все подробности внутреннего устройства платформы CLR. Постоянное внимание к вопросам производительности, особенностям реализации той или иной функциональности, конечно, не пригодится при клепании формочек, но дает более глубокое понимание того, зачем вообще нужна эта функциональность, почему она работает именно так, а не иначе. Книга просто обязательна к прочтению любым профессиональным C# программистом.

Парсинг HTML в .NET приложении

Вопрос разбора данных HTML страниц для вытаскивания из них данных может встать перед любым разработчиком. Для того, чтобы уменьшить количество рутинной работы можно воспользоваться библиотекой Html Agility Pack

Например, на странице с аудио-записями Вконтакте альбомы хранятся в блоках div с атрибутом class= «audio_filter»

Пример:

<div id="album99999" class="audio_filter"
onmouseover="Audio.listOver(this)"
onmouseout="Audio.listOut(this)"
onclick="Audio.loadAlbum(11103462)">Trip-hop</div>

Код поиска таких блоков на C#:

      String page;
      //Получение содержимого html-страницы в строку page
      //...
      byte[] byteArray = Encoding.GetEncoding(1251).GetBytes(page);
      MemoryStream stream = new MemoryStream(byteArray);

      HtmlDocument doc = new HtmlDocument();

      doc.Load(stream);
      HtmlNode root = doc.DocumentNode;

      foreach (HtmlNode div in root.SelectNodes("//div"))
      {
        HtmlAttribute att = div.Attributes["class"];
        if ((att == null) || (att.Value == null) || (!att.Value.Equals("audio_filter")))
          continue;

        int index = div.Id.IndexOf("album");
        if (index != 0)
          continue;

        Album album = new Album() { Name = div.InnerText, ID = div.id };     //Новый альбом, где название содержимое блока

      }

Скачивание музыки из Вконтакте по альбомам — VKontakte Music Backuper

Vkontakte Music Backuper

Те, кому интересно конечное приложение для скачивания музыки из Вконтакте по альбомам, могут скачать его по ссылке. Для запуска требуется .Net Framework 3.5 — можно скачать с сайта Microsoft.com. Пользоваться очень просто — ввести электронную почту, пароль, авторизоваться. Если нужно, изменить ID пользователя для которого показан список аудио. Далее поставить галочки на альбомах, которые нужно скачать и нажать кнопку «скачать альбомы». Можно качать отдельные композиции. С теми же, кого интересуют буквы и реализация, продолжаем. Пользователи Linux и Mac Os могут воспользоваться приложением с помощью проекта Mono, в Ubuntu мне было достаточно выполнить полную установку Mono командой sudo apt-get install mono-complete. Далее можно запускать программу из консоли командой mono VkontakteMusicBackuper.exe либо обычным двойным кликом. Кроме того,можно воспользоваться сервисом Muzico.biz, чтобы слушать и скачивать и записи прямо с сайта.

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

Конечно же, ссылки на mp3-файлы доступны в исходном коде страницы. Кроме того, есть расширения в виде букмарклетов, такие как savefrom.net, которые отображают ссылку на скачивание прямо при воспроизведении музыки или видео. Существует очень удобное приложение для поиска и скачивания музыки из вконтакте под названием VKMusic. Хоть там и заявлена поддержка альбомов, те альбомы, что создает сам пользователь скачать нельзя.

Поэтому пишем свой велосипед. Знания о том на C# сделать скачивание файлов по http, парсить html  рано или поздно пригодятся. Правильнее было бы использовать Вконтакте API, но там все равно тоже нет возможности получения альбомов.

1. Авторизация

При авторизации пользователя в ответе возвращается ID пользователя, а так же remixsid — ключ, хранящийся в cookies браузера и передаваемый при последующих запросах к сайту. Повторяем авторизацию Вконтакте:

 public class VKUserInfo
  {
    public String SID { get; set; }
    public int UserID { get; set; }
  }

  public static class VKUtils
  {
    /// <summary> Авторизация к Vkontakte.ru с получением id и SID пользователя </summary>
    public static VKUserInfo LoginToVkontakte(String email, String password)
    {
      //создаем запрос
      HttpWebRequest wrGETURL = (HttpWebRequest)WebRequest.Create("http://vkontakte.ru/login.php?m=1&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;email=" + email + ";pass=" + password);

      //Запрещаем редирект
      wrGETURL.AllowAutoRedirect = false;
      //Выставляем таймаут
      wrGETURL.Timeout = 100000;

      //получаем весь ответ
      HttpWebResponse myHttpWebResponse = (HttpWebResponse)wrGETURL.GetResponse();

      //получаем Headers,пришедшие в ответе
      string headers = myHttpWebResponse.Headers.ToString();

      //записываем ответ в поток
      StreamReader myStreamReadermy = new StreamReader(myHttpWebResponse.GetResponseStream(), Encoding.GetEncoding(1251));
      //получаем строку с ответом
      string page = myStreamReadermy.ReadToEnd();

      //это регулярное выражение,которое выдирает из переменной headers(а ее мы получили выше) значение sid
      Regex sidregex = new Regex("sid=([a-z0-9]+); exp");
      Match ssid = sidregex.Match(headers);
      String sid = ssid.Groups[1].Value;

      //Получение ID пользователя
      int id = 0;
      String location = myHttpWebResponse.Headers["Location"];
      if ( (location != null ) &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp; (location.Length>3))
      {
        location = location.Substring(3);
        Int32.TryParse(location, out id);
      }

      VKUserInfo info = new VKUserInfo(){ SID =  sid, UserID  = id};
      return info;
    }
}

2.Получение списка аудио-записей

Читать далее Скачивание музыки из Вконтакте по альбомам — VKontakte Music Backuper