C#, как записать в STDERR сообщение об ошибке из консольного приложения.

Пост из серии спрашивали — отвечаем.

Итак, как в C# записать в STDERR. STDERR — это стандартный поток вывода для сообщений об ошибках, туда правильно организованные консольные приложения отправляют, собственно, сообщения об ошибках. Отправить в STDERR сообщение можно с помощью объекта Console.Error:

Console.Error.WriteLine("Write to STDERR.");

Код тестового приложения

static void Main(string[] args)
{
    Console.WriteLine("Write to STDOUT.");
    Console.Error.WriteLine("Write to STDERR.");
}

Проверка

Для проверки можно создать BAT-файл, вызывающий тестовое приложение и переопределяющий (копия) вывод со стандартных потоков в файлы stderr_.txt и stdout_.txt. stderr и stdout являются зарезервированными системными именами, потому к именам файлов надо что-то добавить, знак _ в данном случае:

Экран при выполнении тестового приложения:

Z:\...\write2stderr\write2stderr\bin\Debug>write2stderr.exe
Write to STDOUT.
Write to STDERR.

При выполнении BAT-файла экран останется пустым, но в директории с файлом появятся два файла stderr_.txt и stdout_.txt со следующим содержимым.

stderr_.txt:
	Write to STDERR.

stdout_.txt:
	Write to STDOUT.

Пример на GitHub

Исходник
Тестовый BAT-файл
BAT-файл и скомпилированный EXE

C#, ввод только цифр (чисел) в текстовое поле (TextBox).

Или окончательное решение цифирьного вопроса.

Преамбула

Ранее мы показывали простые способы, как обеспечить, чтобы в TextBox можно было ввести только цифры, т.е. целое число (копия), а потом расширили пример до ввода в TextBox отрицательных (копия) и дробных чисел (копия)

К сожалению, во всех этих примерах есть фатальный недостаток, текст в них все-таки вставить можно, если воспользоваться стандартным контекстным меню или комбинацией клавиш CTRL+V. На уровне простого взаимодействия с формой и контролами это перехватить невозможно, придется несколько извернуться, т.к. для перехвата события «вставить», придется перехватить сообщение Windows WM_PASTE, которое отправляется окну, или элементу управления окна при выполнении операции вставки. Для Windows, тащемта, однохуйственно, кому отправлять сообщение, форме (окну) или, например, текстовому полю. Т.к. для Windows, и текстовое поле на самом деле окно, просто дочернее, т.е. размещенное в другом окне (форме, в нашем случае). Но это я залез глубоко в бок. Нам нужно добраться до сообщений. А это можно сделать только изнутри самого контрола, но не из событий стандартных контролов, так что будем писать свой!

Постановка задачи: необходимо создать свой контрол на основе текстового поля, который позволяет вводить только числа определенного типа — целые, целые отрицательные, отрицательные и положительные/отрицательные с дробной частью.

Начало

Подключим необходимые пространства имен:

using System.Windows.Forms;
using System.ComponentModel;
using System.Globalization;

Начнем делать свой контрол, наследуемый от TextBox. Т.е. создадим новый класс:

public class InputDigitControl:TextBox
{
	//тут будет код :)
}

Для начала необходимо определить сообщения, которые будем перехватывать. Заводим в классе константы, определяющие коды нужных сообщений:

const int WM_PASTE = 0x0302; //Сообщение "Вставка" (через к.м. и комбинацию клавиш)
const int WM_CHAR = 0x0102; //Сообщение - нажатие алфавитно-цифровой клавиши

WM_CHAR будет отправлено форме системой только тогда, когда будет нажата алфавитно-цифровая клавиша, его будем перехватывать для отслеживания цифр (и прочего). Правда есть важный нюанс — WM_CHAR посылается и при нажатии комбинаций клавиш, например Ctrl+C и т.д., а также некоторых клавиш, которые не совсем подходят под понятие «алфавитно-цифровая», например BACKSPACE. Это надо будет учесть.

Анализ ввода с клавиатуры будем производить в функции PreProcessMessage(), которую переопределим. PreProcessMessage() вызывается для предварительной обработки входящих сообщений, и нужно будет вернуть true, если это сообщение было обработано.

Т.е. алгоритм действий таков — мы проверяем входящий символ на соответствие, и, либо пропускаем его дальше (возвращаем base.PreProcessMessage(ref msg) или false), либо что-то делаем с содержимым текстового поля, если символ нужен (это понадобится при вводе отрицательных и дробей) и возвращаем true. Или ничего не делаем, если символ нежелательный, и просто вызываем true. В последнем случае, символ просто не попадет в поле ввода, т.к. контрол будет думать, что он уже обработан.

Вставку, как я уже говорил выше, тоже нужно будет обработать, но, естественно, несколько иначе, это будем делать в переопределенной функции WndProc()
Под катом еще много

Исходники

Контрол

Тестовое приложение

Репозиторий

C#, перехват вывода консольной программы в реальном времени из собственного приложения.

Преамбула.

Итак, необходимо перехватить в реальном времени вывод консольной программы, которая запущена собственным приложением в фоновом режиме, например, как это делает GUI Openvpn для Windows.

На моделировании формы приложения особо останавливаться не буду, сделаем там элемент управления для вывода данных, организуем возможность выбора консольного приложения или BAT/CMD-файла, вывод которого будем перехватывать, кнопки для запуска, останова внешнего приложения, и кнопку для очистки лога.

Замечу только, что для вывода перехваченных сообщений использовал немного модифицированный ListView (копия)


Главное окно основной программы

Консольные тестовые приложения и командные файлы.

Теперь надо подготовить несколько приложений для теста, одно будет консольное приложение на C#, которое генерирует случайное число, выводит его на консоль, ожидает 250 мс, и так повторяет в бесконечном цикле:

Бинарник testapp.exe
Исходный код

И несколько вариантов BAT-файлов, реализующий тот же функционал, только время ожидания составляет 1 секунду.

Правда с BAT-файлами есть пока нерешенный вопрос, некоторые, а именно в которых содержатся команды timeout и choice, при перехвате работают нестабильно. Вариант с choice зависает, начиная нагружать процессор, timeout — не выполняет свои функции, т.е. никакого таймаута, а потом зависает. Пока не понял, с чем это связано, но включил эти файлы отдельно, в комплект к тестовым

Еще одна вводная

1. В более современных версиях .NET можно использовать оператор async, но я решил обойтись без него, т.к. все возможности для перехвата данных с консоли были еще в .NET Framework 2.0. были, а слова такого не было. Как в анекдоте про Вовочку.
2. Запускать вызываемую консольную программу следует в отдельном потоке, с которым мы будем общаться как обычно — с помощью событий. Иначе чуда не получится — все будет висеть и не работать.

Т.е алгоритм примерно понятен — надо запустить вызываемую консольную программу в отдельном потоке, а потом рулить основной программой, перехватывая данные при наличии события (т.е вывода запущенной программой новых данных).

Основной класс.

Создадим новый класс, например Runner:

public class Runner

Выше класса добавляем публичный делегат (он нам потом для события понадобится)

public delegate void LogMessage(string Data);

Подключаем нужные пространства имен:

using System.Threading;
using System.Diagnostics;

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

public string ProcessPath { get; set; }

Заводим событие, которое будем генерировать при перехвате сообщения с консоли.

public event LogMessage LogSend;

И приватные переменные типов ProcessStartInfo, Process, для управления вызываемым процессом и типа Thread, для управления потоком, в котором будет запущен дочерний процесс.

private ProcessStartInfo Info = null;
private Process Proc = null;
private Thread t = null;

В конструкторе инициализируем переменную Info:

public Runner(string processpath)
{
    ProcessPath = processpath; //устанавливаем значение свойства ProcessPath
    Info = new ProcessStartInfo(ProcessPath);
    Info.RedirectStandardError = true; //перехват STDERR
    Info.RedirectStandardOutput = true; //перехват STDOUT
    Info.UseShellExecute = false; //иначе перехват работать не будет, см. MSDN
    Info.CreateNoWindow = true; //не запускать процесс в новом окне
                                //это скроет консоль запускаемой программы
}

Примечание: Присвоение свойству ShellExecute значения false позволяет перенаправлять потоки ввода, вывода и ошибки.

Запуск процесса и перехват вывода консоли

В функции StartProcess() непосредственно выполняем запуск процесса и перехват.

try
{
    Proc = Process.Start(Info);
}
catch (Exception ex)
{
    LogSend("INTERNAL ERROR: "+ex.Message);
    return;
}

Запускаем процесс, если произошла ошибка, вызываем событие, ответственное за отправку сообщения и выходим.

Для перехвата создаем цикл, в котором вызываем Process.StandardOutput.ReadLine(), пока результат не будет равен null.

StandardOutput.ReadLine() будет ждать, пока программа не выведет на консоль строку, и тогда вернет ее, или же вернет null, когда программа завершится.

string ConOut = "";

do
{
    ConOut = Proc.StandardOutput.ReadLine();
    if (ConOut != null)
    {
        LogSend(ConOut);
    }                

} while (ConOut != null);

Поскольку запуск этой функции вызовет «зависание», если запустить ее в основном потоке, то вызывать ее нужно в отдельном:

public void Start()
{
    t = new Thread(StartProcess);
    t.Start();
}

Ну и функция для остановки вызванного процесса:

public void Stop()
{
    if (Proc != null)
    {
        Proc.Kill();
        Proc = null;
    }

    if (t != null)
    {
        t.Abort();
        t = null;
    }
}

Основная форма

В коде основной формы создаем объект Runner, регистрируем обработчик событий, запускаем перехват, не забывая про останов по нажатию нужной кнопки:

Runner runner = null;
//...
private void btnStart_Click(object sender, EventArgs e)
{
    runner = new Runner(txtPath.Text);
    runner.LogSend += new LogMessage(runner_LogSend);            
    runner.Start();
}

private void btnStop_Click(object sender, EventArgs e)
{
    if (runner != null)
    {
        runner.Stop();
    }
}

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

void runner_LogSend(string Data)
{
    Invoke((MethodInvoker)delegate
    {
        lvConsole.Items.Add(Data);
        lvConsole.EnsureVisible(lvConsole.Items.Count - 1);
    });
}

Результат

Перехват вывода из BAT-файла

Исходники

На GitHub

C#, тайм-аут (sleep) в консольной программе.

Проще всего это сделать с помощью остановки потока (thread):

System.Threading.Thread.Sleep(время_в_миллисекундах);

Например:

System.Threading.Thread.Sleep(250);

остановит выполнение основного потока программы (если поток один, соответственно, программу) на 250 миллисекунд:

static void Main(string[] args)
        {
            
           //какой-то код

            System.Threading.Thread.Sleep(250);
            
            //какой-то код
			
        }

C#, получение списка дисков, доступных в системе, и некоторых их параметров

Заметка от склероза.

Делается с помощью функции DriveInfo[] Drives = DriveInfo.GetDrives(); из пространства имен System.IO.

Ссылки

DriveInfo.GetDrives() на MSDN
Класс DriveInfo на MSDN
Пример кода на PasteBin

Менеджер отложенной автозагрузки / StartupDelayed v 0.0.2

Первая версия (копия)

+ Исправлен баг с сохранением конфигурации при удалении записей (спасибо SCPicker за тестирование и сообщение).

+ Убран режим выбора каталога конфигурации из командной строки, оставлен только обычный (файлы конфигурации в LocalApplicationData\StartupDelayed\) и портативный (файлы конфигурации в директории с программой).

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

+ Исправлены прочие мелкие недочеты.

+ Инсталлятор изменен на NSIS.

Скачать

Портабельная версия (ZIP)

Инсталлятор

Исходники на GitHub

Калькулятор пропорций v 0.2

Задолбало меня считать сумму внесения платежей с комиссией, через терминалы (или с международными переводами, один хрен, там с комиссией), так что запряг друга $EG’у, чтоб он разобрался в вопросе и допилил эту функцию в калькулятор пропорций. Он, ВНЕЗАПНО, допилил:

Формула

var paymentWithCommission = payment / (1 - 100 / proc);

где:
payment — необходимая сумма платежа
proc — процент комиссии

Скачать

Установщик
Бинарник без установки (ZIP)

Исходник

На GitHub

Пакетный конвертер (перекодировщик) текстовых файлов v 0.0.3b

Обновление пакетного конвертера текстовых файлов.

Благодарим всех, кто сообщил о багах.

Изменения в версии v 0.0.3b

+ Изменения в алгоритме поиска файлов
+ Поправлен баг с сохранением конфигурации (масок файлов)

Исходники

Репозиторий на GitHub

Скачать

Портативная версия

Постоянная страничка программы (копия)

C#, поиск файла по маске, более правильное решение.

Преамбула

Когда-то уже говорил (копия) что стандартная функция C# Directory.GetFiles(); неправильно ищет файлы по маске. И даже сделал на скорую руку кривофикс, но кривофикс действительно оказался именно что криво. Во-первых, срабатывал только для некоторых масок, а во-вторых, оказался чувствительным к регистру имен файлов. Делаем более прямое исправление.

Вспомогательные функции

Заведем вспомогательную функцию, которая будет добавлять конечный слэш (\) к имени директории. Оно не особо надо, но пусть будет для порядка.

private static string AddSlash(string st)
{
    if (st.EndsWith("\\"))
    {
        return st;
    }

    return st + "\\";
}

И функцию, получающую имя файла из полного пути. Конечно, можно было бы воспользоваться классом FileInfo из System.IO, но тут операция совсем уж простая, а FileInfo может сгенерировать ненужный Exception. Проще получить имя файла с помощью строковой операции:

private static string GetNameOnly(string FullName)
{
    int LastSlash = FullName.LastIndexOf("\\");
    
    if (LastSlash == -1) return FullName;

    return FullName.Substring(LastSlash + 1);
}

Преобразование маски файла в регулярное выражение.

Да, я таки решил воспользоваться нелюбимыми регекспами. Впрочем, маска файла и есть регулярное выражение, только с упрощенным синтаксисом.

1. В имени файла могут встретиться символы, считающиеся служебными в регулярном выражении (.,^,$,{,},[,],(,),+), их необходимо экранировать, чтоб они воспринимались обработчиком регулярных выражений, как обычные, а не служебные символы.

//точка в маске файла должна быть точкой в регулярном выражении
//экранируем
Mask = Mask.Replace(".", "\\.");
//^,$,{,},[,],(,),+ в regexp служебные, в именах файла допустимые
//экранируем
Mask = Mask.Replace("^", "\\^");
Mask = Mask.Replace("$", "\\$");
Mask = Mask.Replace("{", "\\{");
Mask = Mask.Replace("}", "\\}");
Mask = Mask.Replace("[", "\\[");
Mask = Mask.Replace("[", "\\[");
Mask = Mask.Replace("(", "\\(");
Mask = Mask.Replace(")", "\\(");
Mask = Mask.Replace("+", "\\+");

2. * — в маске файла это любой символ, или их отсутствие. В регулярном выражении этому соответствует комбинация .*, заменяем:

Mask = Mask.Replace("*", ".*");

3. ? в маске файла — любой существующий символ. В регулярном выражении это символ . (точка), заменяем:

Mask = Mask.Replace("?", ".");

4. Осталось ограничить работу регулярного выражения началом и концом строки, строкой будет являться имя (маска) файла. Начало строки обозначается символом ^, конец символом $. Добавляем:

Mask = "^" + Mask + "$";

Функция целиком:

private static string Mask2Reg(string Mask)
{
    //точка в маске файла должна быть точкой в регулярном выражении
    //экранируем
    Mask = Mask.Replace(".", "\\.");
    //^,$,{,},[,],(,),+ в regexp служебные, в именах файла допустимые
    //экранируем
    Mask = Mask.Replace("^", "\\^");
    Mask = Mask.Replace("$", "\\$");
    Mask = Mask.Replace("{", "\\{");
    Mask = Mask.Replace("}", "\\}");
    Mask = Mask.Replace("[", "\\[");
    Mask = Mask.Replace("[", "\\[");
    Mask = Mask.Replace("(", "\\(");
    Mask = Mask.Replace(")", "\\(");
    Mask = Mask.Replace("+", "\\+");
    //* - любое количество любого символа, 
    //в regexp любой символ - точка, любое количество *
    Mask = Mask.Replace("*", ".*");
    //? - любой символ, в regexp любой символ - точка.
    Mask = Mask.Replace("?", ".");

    //добавляем начало и конец строки к имени файла.
    Mask = "^" + Mask + "$";

    return Mask;
}

Модификация функции поиска

В модифицированную функцию поиска передаются такие же параметры, как и в функцию Directory.GetFiles(); т.е. маска файла, путь до каталога и перечисление SearchOption, которое может принимать два значения: SearchOption.AllDirectories — поиск с подкаталогами и SearchOption.TopDirectoryOnly — поиск только в текущем каталоге.

Внутри функции:

1. Преобразуем маску файла в регулярное выражение:

string MaskRegStr = Mask2Reg(sMask);

2. Добавляем слеш к пути поиска (на всякий случай):

sPath = AddSlash(sPath);

3. Заводим List<string>, куда будем складировать отфильтрованные файлы из найденных (на то, как криво работает Directory.GetFiles() есть ссылки в начале заметки).

List<string> FoundFiles = new List<string>();

4. Создаем обработчик регулярных выражений с опцией RegexOptions.IgnoreCase, чтобы игнорировать регистр входной строки (в нашем случае — имени файла).

Regex MaskReg = new Regex(MaskRegStr, RegexOptions.IgnoreCase);

5. Вызываем функцию поиска из System.IO:

string[] files = Directory.GetFiles(sPath, sMask, SO);

6. Фильтруем вывод на предмет лишних файлов (см. подробнее по ссылке в начале заметки). Фильтрация производится путем сравнения имени файла с ранее сгенерированным регулярным выражением. Если имя файла соответствует регулярке, оно добавляется в List:

foreach (string filename in files)
{                                
    if (MaskReg.IsMatch(GetNameOnly(filename)))
    {
        FoundFiles.Add(filename);
    }
}

7. Результат возвращается в виде строкового массива:

return FoundFiles.ToArray();

Функция целиком:

public static string[] Find(string sPath, string sMask, SearchOption SO)
{
    string MaskRegStr = Mask2Reg(sMask);
    sPath = AddSlash(sPath);
    List<string> FoundFiles = new List<string>();
    Regex MaskReg = new Regex(MaskRegStr, RegexOptions.IgnoreCase);

    string[] files = Directory.GetFiles(sPath, sMask, SO);

    foreach (string filename in files)
    {                                
        if (MaskReg.IsMatch(GetNameOnly(filename)))
        {
            FoundFiles.Add(filename);
        }
    }
    return FoundFiles.ToArray();
}

Пример на GitHub

Вроде бы в этот раз все предусмотрел, и глюкоопцию встроенной функции поправил, и в регулярке нигде не наебался.

Пример на GitHub

C#. Удаление значений (values) из ключа Реестра

Преамбула

Задача довольно простая, имеется ключ в разделе Реестра Windows, необходимо удалить оттуда все значения (values).

Термины и определения

Реестр — иерархическая база данных, хранящая основные настройки системы и программ.
Корневой ключ (в некоторых источниках просто ключ или раздел Реестра), это один из основных ключей Реестра. Обычно присутствуют следующие:

HKEY_CLASSES_ROOT (HKCR) — Зарегистрированные типы файлов, объекты COM и ActiveX
HKEY_CURRENT_USER (HKCU) — Настройки текущего пользователя, в данный момент вошедшего в систему.
HKEY_LOCAL_MACHINE (HKLM) — Общие настройки для всех пользователей данного компьютера.
HKEY_USERS (HKU) — Профили всех пользователей данной машины.
HKEY_CURRENT_CONFIG (HKCC) — Профили оборудования, информация о драйверах.

Подключ/Subkey (иногда Ключ, если корневой раздел называется корневым разделом или корневым ключом) Реестра — путь в Реестре, аналогичный пути к каталогу (папке) файловой системы. Обычно каждый подключ хранит или настройки для конкретного случая. Например, настройки конкретной программы или профиль конфигурации оборудования. Выглядит примерно так: Software\TestKey (в полном виде, вместе с корневым ключом, например: HKEY_CURRENT_USER\Software\TestKey).

Подключ может содержать как и другие подключи, так и значения (values), представляющие собой поименованные переменные, содержащие значения конкретного параметра

Обзорная статья про Реестр Windows на Википедии

Тестовый ключ

Для тестирования создадим тестовый ключ, который не будет влиять на какие либо программы и компоненты ОС. Это можно сделать, как вручную, с помощью программы Regedit, так и в автоматическом режиме, с помощью reg-файла, например, такого:

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\TestKey]
"value0"="text0"
"value1"="text1"
"value2"="text2"
"value3"="text3"
"value4"="text4"
"value5"="text5"

Файл на GitHub

Можно создать и reg-файл для удаления тестового ключа из Реестра, если что-то пойдет не так:

Windows Registry Editor Version 5.00

[-HKEY_CURRENT_USER\Software\TestKey]

Файл на GitHub

Решение

1. Подключаем пространство имен Microsoft.Win32:

using Microsoft.Win32;

2. Создаем переменную с именем подключа Реестра и переменную типа RegistryKey. Этот объект и будет рулить нужной веткой Реестра:

string subkey = "Software\\TestKey";
RegistryKey key = null;

3. Любые операции с Реестром могут привести к ошибкам, потому их как и операции ввода-вывода желательно оборачивать в try..catch:

try
{
    //тут будет код.
}
catch (Exception ex)
{
    Console.WriteLine("ERROR: " + ex.Message);
}

4. Открываем (под)ключ Реестра:

key = Registry.CurrentUser.OpenSubKey(subkey, true);

Внимание! Если необходимо писать в ключ Реестра, обязательно устанавливаем второй параметр функции OpenSubKey в true, т.к. в противном случае, мы получим ошибку UnauthorizedAccessException: Не удалось выполнить запись в раздел реестра.

Обсуждение на Cyberforum

5. Получить список значений в (под)ключе можно функцией GetValueNames(), которая выдает строковый массив с именами значений Реестра.

6. Получаем имена всех значений и в цикле удаляем их с помощью функции DeleteValue(имя_параметра);

foreach (string valname in key.GetValueNames())
{
    Console.WriteLine("Delete value: " + valname);
    key.DeleteValue(valname);
}

7. Закрываем (под)ключ Реестра.

key.Close();

Все получилось:

Код целиком

На PasteBin

Тестовый пример

Пример на GitHub

C#, генератор файлов (нужного количества и с определенным содержимым).

Описание задачи

Итак, требуется написать программу, которая генерирует заданное количество файлов с определенным содержимым. Содержимое задается или в качестве байтового паттерна (т.е. если программе указать 0xFF, то файл будет заполнен указанным количеством байт со значением 0xFF) или случайных байт. Файлам также можно задать расширение, или сделать и имя файла случайным.

Программа

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

Использование: filegen.exe <количество_файлов> <размер>[K|M|G] <расширение|/R> [байтовый_паттерн|/R]

<количество_файлов> - количество файлов
<размер> - размер:

   по умолчанию - байты
   K - килобайты
   M - мегабайты
   G - гигабайты

<расширение> - расширение файла или /R - случайное имя файла
[байтовый_паттерн] - байтовый паттерн (HEX): от 0x00 до 0xFF, по умолчанию 0x00 или /R случайные байты

Использованные алгоритмы

1. С#, Заполнение массива одним значением. (копия)
2. C#, заполнение массива случайными числами. (копия)
3. C#, Количество цифр (разрядов) числа. (копия)
4. C#. Перевод кило- мега- и гигабайтов в байты. (копия)

Пример программы

На GitHub

C#. Перевод кило- мега- и гигабайтов в байты

Преамбула

Простая учебная задача, но почему-то до моего решения долюбились, в конце объясню, почему долюбились, и почему, в случае C#, это зря было.

Решение

И так, задача состоит в том, чтобы всякие кратные единицы перевести в байты (кому надо обратную задачу, либо все во все — пишите в комментарии). Для этого достаточно сделать следующую функцию, исходя из того, что 1 Кб == 1024 Б, 1 Мб = 1024 Кб, 1 Гб = 1024 Мб. В функцию добавим некую переменную-модификатор, для консольного приложения было проще сделать строковую, в ином случае, может и есть сделать отдельный Enum для единиц, но я обошелся строковой.
Переменная-модификатор (modificator) принимает строковые значения G(g), M(m) и K(k), соответственно для гига- мега- и килобайта.

Функция получается такая:

public static ulong ToBytes(ulong bytes, string modificator)
{
    ulong Ret = 0;
    switch (modificator)
    {
        case "":
            {
                Ret = bytes;
            } break;
        case "K":
        case "k":
            {
                Ret = bytes * 1024;
            } break;
        case "M":
        case "m":
            {
                Ret = bytes * 1024 * 1024;
            } break;
        case "G":
        case "g":
            {
                Ret = bytes * 1024 * 1024 * 1024;
            } break;
        default:
            {
                throw new ArgumentException("Wrong modificator ["
                    + modificator + "]. Set K(k), M(m), G(g)");
            }                    
    }

    return Ret;
}

Почему не стал заморачиваться с Enum’ом

Задача была очень простая, чисто учебная. Консольная программа принимает в качестве одного параметра число с единицей без пробела, т.е. команда вида program.exe 10G, должна вывести количество байт. Надо ли Enum городить, ну красиво было бы, но до Enum‘ов они не добрались 🙂

В основной программе надо отрезать последний символ из параметра, проверить ли является он нужной буквой (K/M/G) и пересчитать. Если буквы нету, а только цифры — значит оно уже сразу байты.

В функции Main() отрезаем последний символ от первого параметра командной строки, если он K/M/G, считаем, что все остальное число, отрезаем его, а все остальное пытаемся сконвертировать в ulong (Int64) и передать функции ToBytes() с отрезанным символом, как модификатором для расчета. Если нет — просто считаем, что вся строка число, пытаемся конвертировать, а функции передаем пустой модификатор.

Ошибки конвертации ловим try/catch:

string size_s = args[1];

try
{
    string mdf = size_s.Substring(size_s.Length - 1, 1);
    if ((mdf != "K") && (mdf != "k") && (mdf != "M") && (mdf != "m") &&
        (mdf != "G") && (mdf != "g"))
    {
        mdf = "";
        //in parameter only numbers or param wrong...
    }
    else // remove modificator
    {
        size_s = size_s.Substring(0, size_s.Length - 1);
    }
    BytesCount = ToBytes(Convert.ToUInt64(size_s), mdf);
}
catch (Exception ex)
{
    Console.WriteLine("Wrong <size> parameter!");
    Console.WriteLine(ex.Message);
    Console.Write("Press Enter...");
    Console.ReadLine();
    return 2;
}

Пример целиком на GitHub

Почему долюбились

За вот эти вот конструкции:

Ret = bytes * 1024;
Ret = bytes * 1024 * 1024;
Ret = bytes * 1024 * 1024 * 1024;

Типа «ну тут числа, ты бы их сначала перемножил, результат записал , а потом на переменную помножил».

Почему зря.

Дебилы, бля, как говорил Лавров. В интерпретируемых языках или старом Трупопоскакале под DOS, может это и какой-то рояль играет, но в C# компилятор перемножает константы до сборки экзешника, а 1024*1024*1024 для компилятора константы, во время компиляции. Т.е. процессору скормят 1073741824. А код останется более понятен и читабелен.

C#, добавление лидирующих нолей к строковому представлению числа, более оптимальный способ.

Преамбула

Здесь (копия) я показал довольно неоптимальный способ решения данной задачи, но его можно оптимизировать с помощью стандартных функций. Напомню суть задачи: даны числа, скажем, от 1 до 1000, необходимо вывести их на экран или в файл последовательно, добавив лидирующие ноли, исходя из максимального числа. Если максимум 1000, то числа должны быть выведены так:
0001
0002
0003
...
0100
0101
0102
...
1000

Если максимум 100, то:

001
002
...
099
100

Подготовка

Как и в предыдущей версии решения, понадобится задать максимальное число (в тестовых примерах опять же, зададим его статически), например:

int maxnum = 150;

И опять же, понадобится функция, вычисляющая количество цифр (разрядов) числа (копия). Например, такая:

static int CountDigitsRec(int n)
{
    n = (int)Math.Abs(n);
    if (n <= 9)
    {
        return 1;
    }
    else
    {
        return CountDigitsRec(n / 10) + 1;
    }
}

Форматирование строки

C# автоматически умеет форматировать строки, используя специальные паттерны описания формата, задаваемые в виде строки. Основные паттерны перечислены в таблице ниже:

C / c Задает формат денежной единицы, указывает количество десятичных разрядов после запятой
D / d Целочисленный формат, указывает минимальное количество цифр
E / e Экспоненциальное представление числа, указывает количество десятичных разрядов после запятой
F / f Формат дробных чисел с фиксированной точкой, указывает количество десятичных разрядов после запятой
G / g Задает более короткий из двух форматов: F или E
N / n Также задает формат дробных чисел с фиксированной точкой, определяет количество разрядов после запятой
P / p Задает отображения знака процентов рядом с число, указывает количество десятичных разрядов после запятой
X / x Шестнадцатеричный формат числа

Для форматирования строки, содержащей целое число, необходимо использовать паттерн d.

Форматирование во время вывода строки на консоль.

Отформатировать строку можно сразу во время вывода на консоль, функцией Console.Write()/Console.WriteLine():

int num = 125
Console.Write ("0:d4", num) //вывод: 0125

или

int num = 25
Console.Write ("0:d4", num) //вывод: 0025

Пример:

static void Main(string[] args)
{
    int maxnum = 150;
    string FormatPattern = "{0:d" + 
        CountDigitsRec(maxnum).ToString() + "}";

    for (int i = 0; i <= maxnum; i++)
    {
        Console.WriteLine(FormatPattern, i);
    }

    Console.WriteLine("Press Enter...");
    Console.ReadLine();
}

Пример на GitHub

Использование String.Format()

Функция String.Format() может сделать аналогичную операцию, но отличается тем, что вывод функции можно сохранить в строковую переменную и использовать далее, а не просто вывести на консоль.

Пример:

static void Main(string[] args)
{
    int maxnum = 1150;
    string FormatPattern = "{0:d" +
        CountDigitsRec(maxnum).ToString() + "}";
    string TempFile = Path.GetTempFileName();
    string Result = "";
    List<string> WriteList = new List<string>();

    for (int i = 0; i <= maxnum; i++)
    {
        Result = String.Format(FormatPattern,i);
        Console.WriteLine(Result);
        WriteList.Add(Result);
    }

    File.WriteAllLines(TempFile, WriteList.ToArray());

    Console.WriteLine("Test file: " + TempFile);
    Console.WriteLine("Press Enter...");
    Console.ReadLine();
}

В цикле результат работы функции String.Format() возвращается в переменную Result, значение которой выводится на консоль и сохраняется в List<string> WriteList для дальнейшей записи в файл.

Пример на GitHub

Примечание: в источнике есть еще варианты форматирования строк, ознакомьтесь.

Источник

Форматирование и интерполяция строк (копия на mega.nz, PDF)

C#, добавление лидирующих нолей к строковому представлению числа.

Преамбула

Задача опять же учебная, но может пригодиться и в реальной программе. Итак, нам даны числа, скажем, от 1 до 1000, необходимо вывести их на экран или в файл последовательно, добавив лидирующие ноли, исходя из максимального числа. Если максимум 1000, то числа должны быть выведены так:
0001
0002
0003
...
0100
0101
0102
...
1000

Если максимум 100, то:

001
002
...
099
100

Решение

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

int maxnumber = 1998;

Далее, понадобится получить количество цифр числа. Это мы уже делали, причем разными способами (копия), выбираем подходящий.

Создаем отдельный класс (в примере AddLZ) и добавляем туда функцию:

public static int CountDigitsRec(int n)
{
    n = (int)Math.Abs(n);
    if (n <= 9)
    {
        return 1;
    }
    else
    {
        return CountDigitsRec(n / 10) + 1;
    }
}

В основной программе получаем количество цифр числа:

int maxdigits = AddLZ.CountDigitsRec(maxnumber);

Далее в цикле перебираем все числа, и скармливаем их функции, которая будет добавлять лидирующие ноли (AddLZ.AddLeaderZeroString(maxdigits, i));

for (int i = 0; i <= maxnumber ; i++)
{
    string Result = AddLZ.AddLeaderZeroString(maxdigits, i);
    //...
}

На вход функции подается два параметра — максимальное количество цифр и текущее число.

Внутри функции

Получаем строковое представление текущего числа:

string scurnum = ((int)Math.Abs(curnum)).ToString();

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

int LenLZ = maxnum - scurnum.Length;

Далее, надо сформировать строку нужной длины из одного символа ('0') (копия):

StringBuilder sb = new StringBuilder(maxnum);
for (int i = 0; i < LenLZ; i++)
{
    sb.Append('0');
}

Далее добавляем строковое представление текущего числа:

sb.Append(scurnum);

И возвращаем значение:

return sb.ToString();

Функция целиком

public static string AddLeaderZeroString(int maxnum, int curnum)
{
    string scurnum = ((int)Math.Abs(curnum)).ToString();
    int LenLZ = maxnum - scurnum.Length;

    StringBuilder sb = new StringBuilder(maxnum);
    for (int i = 0; i < LenLZ; i++)
    {
        sb.Append('0');
    }
    sb.Append(scurnum);

    return sb.ToString();
}

Исходники

Тестовый пример на GitHub
Класс AddLZ

C#, сгенерировать строку нужной длины из одного символа.

Преамбула

Продолжаем задачки по генерации всяких паттернов. Недавно мы генерировали массив, заполненный одним значением (копия). Теперь будем генерировать строку из одного символа, но нужной длины. Строку из случайных символов мы уже делали

Теперь будем делать строку-паттерн из одного заранее заданного символа.

Заведем три переменных:

int strlen = 100000; // длинна строки
string symbol = "a"; // символ заполнения
string Result = ""; // результирующая строка

«В лоб», с использованием стандартной конкатенации строк

Т.е. в цикле for, используя конструкцию s=s+"a":

for (int i = 0; i < strlen; i++)
{
    Result = Result + symbol;
}

С использованием StringBuilder

StringBuilder sb = new StringBuilder(strlen);
for (int i = 0; i < strlen; i++)
{
    sb.Append(symbol);
}

Result = sb.ToString();

Тесты

For + standart string concatenation: 00:00:05.7493288
Test For + StringBuilder.Append: 00:00:00.0030001

Даже на 10000 символов видна разница, здесь показана на 100000, а 500000 для for я вообще не дождался.

Примечание: Тут мы делали строку заданной длины из одного паттерна, поэтому StringBuilder был уместен, т.к. он напрямую работает с памятью, и не использует механизм .NET, которому нужно строку скопировать, а потом добавить символ. См. ссылку ниже.

Эффективная конкатенация строк в .NET

Тестовый пример на GitHub

C#, Количество цифр (разрядов) числа.

Пост из серии «спрашивали — отвечаем», довольно стандартная учебная задача, никакой особой военной хитрости нет, но раз уж, что б нет то.

Преобразование числа в строку и подсчет символов

Самый простой, дубовый, но самый медленный по времени выполнения способ. Обычно в школе за него по рукам бьют 🙂

static int CountDigitsStr(int n)
{
    return ((int)Math.Abs(n)).ToString().Length;
}

Получаем модуль числа [(int)Math.Abs(n)], чтоб исключить ситуацию, если в n окажется отрицательное число, преобразуем число в строку и получаем количество символов в строке, т.е. количество цифр числа.

Подсчет цифр делением нацело

Способ заключается в том, чтобы последовательно делить число на 10, оставляя от деления целую часть, пока целая часть не станет равна 0.

Можно сделать в двух вариантах.

В цикле:

static int CountDigitsCycle(int n)
{
    n = (int)Math.Abs(n); //берем модуль числа
    int count = 0; //заводим счетчик
    if (n == 0) count = 1; // n == 0? Цифра одна.
    while (n != 0) //пока n не равно 0
    {
        n = n / 10; //делим число на 10
        count++; //прибавляем счетчик
    }
    return count;
}

Какая-то специальная функция для деления нацело в C# не нужна, при делении переменной цельночисленного типа (byte, sbyte, short, ushort, int, uint, long, ulong) на целое число или цельночисленную переменную, результатом будет целое число, т.е. деление нацело произойдет автоматически.

Рекурсией:

static int CountDigitsRec(int n)
{
    n = (int)Math.Abs(n);
    if (n <= 9)
    {
        return 1;
    }
    else
    {
       return CountDigitsRec(n / 10) + 1;
    }
}

Использование десятичного логарифма.

static int CountDigitsLog10(int n)
{
    if (n == 0)
    {
        return 1;
    }
    return (int)Math.Log10(Math.Abs(n)) + 1;
}

С#, Заполнение массива одним значением.

Преамбула

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

Примечание: Создаваемый по умолчанию массив, автоматически заполняется нолями (0x00).
А нам, как раз, наоборот, надо создать массив с паттерном 0xFF или 0xAA.

Решение «в лоб»

Я думаю, оно всем понятно. Создать массив и заполнить его нужными значениями:

int items = 536870912;
byte[] tArr = new byte[items];

for (int i = 0; i < items; i++)
{
    tArr[i] = 0xFF;
}

Тут мы в цикле присваиваем каждому элементу массива значение 0xFF.

— Оно медленное.
+ Оно простое и с использованием безопасного кода.

В лоб + Array.Copy

Некто попытался оптимизировать прошлое решение, используя Array.Copy(), думая, что эта функция быстрее работает с памятью.

Что решение «в лоб», что «Array.Copy()» с распополамленным циклом работают одинаково, точнее, получилось даже медленнее (в конце статьи будет итоговая таблица):

int items = 536870912;
byte[] tArr = new byte[items];
tArr[0] = 0xFF;
for (int i = 1; i <= items / 2; i *= 2)
{
    Array.Copy(tArr, 0, tArr, i, i);
    Array.Copy(tArr, 0, tArr, i, items - i);
}

P/Invoke способ с использованием внешних библиотек

Прямая работа с памятью сильно обходит .NET по скорости, так что самый оптимальный способ для такой задачи — использовать небезопасный код.

public static class FillArray
{
    [DllImport("msvcrt.dll",
    EntryPoint = "memset",
    CallingConvention = CallingConvention.Cdecl,
    SetLastError = false)]

    private static extern IntPtr MemSet(IntPtr dest, int value, int count);

    public static byte[] FillBytes(int Length, byte Value)
    {
        byte[] Arr = new byte[Length];
        GCHandle GCH = GCHandle.Alloc(Arr, GCHandleType.Pinned);
        MemSet(GCH.AddrOfPinnedObject(), (int)Value, Length);
        return Arr;
    }

В свою программу мы импортировали функцию memset из библиотеки msvcrt.dll, т.е. смогли получить контроль над памятью. И заполнили нужным числом массив. Поскольку, за нас это делали библиотеки C++ и функции ОС, все оказалось быстро.

Сравнительная таблица скорости работы разных алгоритмов заполнения массива (массив 500 Мб).

For 00:00:04.4212528
For+Array.Copy() 00:00:04.6952685
MemSet (msvcrt.dll) 00:00:00.2360135
Random bytes (RNGCryptoServiceProvider) 00:00:06.1143497

Сравнение алгоритмов в источнике.

Тестовый пример (для этого и предыдущего [копия] поста) на GitHub

Источник

C#, заполнение массива случайными числами.

Преамбула

Можно сказать, учебная задача. Итак, дан массив типа byte[], необходимо заполнить его случайными числами. И случайные числа нужны понадежнее. Ну хорошо, аппаратный ГСЧ конструировать не будем 🙂 Хотя как-нибудь надо и этот вариант рассмотреть.

Решение

Все решается довольно стандартными методами:

1. Подключаем пространство имен System.Security.Cryptography:

using System.Security.Cryptography;

2. Создаем массив байт нужной длины:

byte[] Arr = new byte[666];

3. Создаем RNGCryptoServiceProvider, т.е. генератор случайных чисел системного криптопровайдера.

RngCsp = new RNGCryptoServiceProvider();

4. Заполняем массив:

RngCsp.GetBytes(Arr);

Примечание: Это довольно простое решение «в лоб», и хоть Майкрософт гарантирует нам, что числа будут прям случайные-случайные, в самом MSDN показан пример с выкрутасами (см. в источниках).

Примечание #2: Метод стандартный для C#, но довольно медленный. Даже медленнее, чем заполнение гигантского массива на 500 Мб одним числом, в примерном приближении и довольно неточно заполнение массива заняло почти 6 секунд:

Random bytes (RNGCryptoServiceProvider): 00:00:05.8453343

Источник

RNGCryptoServiceProvider Class

Код возврата в консольном приложении C#

Преамбула

Известно, что все приложения в Windows, после завершения передают ОС код возврата, который можно потом получить, например в BAT-файле в переменной %ERRORLEVEL% и проанализировать. Например, чтобы понять, успешно ли завершена работа программы, или же в процессе работы произошла ошибка.

А как вернуть нужный код возврата, если создаешь приложение в C#? Пока остановлюсь только на консольном приложении.

Код возврата в консольном приложении

Для возврата кода в консольном приложении достаточно изменить заголовок функции Main с
static void Main(string[] args)
на
static int Main(string[] args),
т.е. заставить главную функцию возвращать значение int (от -2147483648 до 2147483647).

Естественно, там, где функция завершается, необходимо добавить оператор
return <код_возврата>;

Пример

Программа выводит заданный в командной строке код завершения.

Исходник
EXE с тестовым BAT-файлом