Известно, что в .NET для копирования файлов программистами используется метод Copy, класса File. В большинстве случаев вариант использования File.Copy оправдывает себя. Не нужно ни о чем беспокоиться, достаточно только передать путь к исходному и целевому файлу – система сама все проконтролирует: считает информацию из исходного файла, создаст новый и запишет его. При этом разработчик никак не может повлиять на процесс копирования – прервать его, поставить на паузу. Еще одна проблема возникает при копировании файлов большого размера. Если это выполнять через метод Copy и не в отдельном потоке, то ваше приложении будет в зависшем состоянии, пока процесс копирование не завершиться. Для решения этих проблем я написал класс, который побайтно копирует файл и в процессе передает данные приложению. Весь процесс работает исключительно на родных функциях библиотеки .NET, поэтому функций WIN API вы здесь не найдете.
Также, в этой статье мы разберем как работать с объектами класса FileStream.
Давайте разберем структуру класса.
Класс FileCopy содержит в себе всего два метода. И только один из них виден «снаружи». Для наглядности, в классе, я описал только самое необходимое, чтобы было понятно как происходит процесс чтения и записи.
Сперва я описал два делегата:
1 |
public delegate void Complet( bool ifComplete); |
2 |
public delegate void Progress( string message, int procent); |
Они нужны будут для описание следующих событий:
2 |
/// Событие на завершение копирования файла |
4 |
public event Complet OnComplete; |
6 |
/// Событие во время копирования |
8 |
public event Progress OnProgress; |
Далее в коде будут вызываться эти события, и все, кто на них подписался, получат сообщения.
Следующая переменная устанавливает размер порции или сколько за раз будет считано и записано информации. Размер в байтах. Значение этой переменной можно изменить во время работы программы.
1 |
public int BufferLenght { get ; set ; } |
Теперь перейдем к самому главному методу, в котором и будет проходить копирование данных. Вот полный код метода с описанием (не пугайтесь, на самом деле он короче, если отбросить комментарии =)):
4 |
/// <param name="sourceFile">Путь к исходному файлу</param> |
5 |
/// <param name="destinationFile">Путь к целевому файлу</param> |
6 |
public void CopyFile( string sourceFile, string destinationFile) |
12 |
Byte[] streamBuffer = new Byte[BufferLenght]; |
14 |
long totalBytesRead = 0; |
20 |
using (FileStream sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read)) |
23 |
long sLenght = sourceStream.Length; |
25 |
using (FileStream destinationStream = new FileStream(destinationFile, FileMode.Create, FileAccess.Write)) |
35 |
int bytesRead = sourceStream.Read(streamBuffer, 0, BufferLenght); |
41 |
getInfo(sLenght, sLenght); |
47 |
destinationStream.Write(streamBuffer, 0, bytesRead); |
49 |
totalBytesRead += bytesRead; |
52 |
if (numReads % 10 == 0) |
55 |
getInfo(totalBytesRead, sLenght); |
60 |
if (bytesRead < BufferLenght) |
63 |
getInfo(totalBytesRead, sLenght); |
71 |
if (OnComplete != null ) |
76 |
System.Windows.Forms.MessageBox.Show( "Возникла следующая ошибка при копировании:\n" + e.Message); |
78 |
if (OnComplete != null ) |
Как видите, я создал два объекта на основе класса FileStream. Один читает, а другой записывает из исходного файла в целевой. Таким образом, варьируя размером буфера, можно добиться довольно таки высокой скорости копирования больших файлов.
Метод getInfo просто готовит информацию и передает ее в событие.
1 |
private void getInfo( long totalBytesRead, long sLenght) |
4 |
string message = string .Empty; |
5 |
double pctDone = ( double )(( double )totalBytesRead / ( double )sLenght); |
6 |
message = string .Format( "Считано: {0} из {1}. Всего {2}%" , |
11 |
if (OnProgress != null ) |
12 |
OnProgress(message, ( int )(pctDone * 100)); |
Сам класс не идеален, но вы можете легко доработать его под себя. Например дописать проверку на существование файла или реализовать механизм паузы во время копирования файла.
Для примера работы с классом вы можете посмотреть вот этот проект. В этом проекте описан принцип работы в отдельном потоке через компонент BackgroundWorker.