Академический Документы
Профессиональный Документы
Культура Документы
CONCEPTOS BÁSICOS..........................................................................................................................2
RUTAS DE FICHEROS Y DIRECTORIOS....................................................................................................3
SINTAXIS EN WINDOWS........................................................................................................................3
RUTAS INDEPENDIENTES DEL SISTEMA OPERATIVO.....................................................................................4
OPERACIONES CON RUTAS.....................................................................................................................5
REPRESENTACIÓN DE FICHEROS Y DIRECTORIOS EN .NET......................................................................6
ELEMENTOS COMUNES..........................................................................................................................6
ELEMENTOS ESPECÍFICOS DE DIRECTORIOS...............................................................................................9
ELEMENTOS ESPECÍFICOS DE FICHEROS..................................................................................................10
FLUJOS DE ENTRADA-SALIDA.............................................................................................................11
LECTURA Y ESCRITURA.......................................................................................................................12
MOVIMIENTO POR EL FLUJO................................................................................................................12
VOLCADO DE DATOS EN FLUJOS...........................................................................................................14
CIERRE DE FLUJOS.............................................................................................................................14
FLUJOS DE ENTRADA-SALIDA EN FICHEROS..........................................................................................14
CREACIÓN DE OBJETOS FILESTREAM....................................................................................................14
MANIPULACIÓN DEL CONTENIDO DE LOS FICHEROS..................................................................................16
CONTROL DE CONCURRENCIA EN ACCESOS A FICHEROS............................................................................20
ACCESO NATIVO A FICHEROS...............................................................................................................20
FICHERO BINARIOS...........................................................................................................................21
FICHEROS DE TEXTO.........................................................................................................................22
LECTURA DE FICHEROS DE TEXTO.........................................................................................................22
ESCRITURA EN FICHEROS DE TEXTO.....................................................................................................25
MANIPULACIÓN DEL SISTEMA DE ARCHIVOS........................................................................................27
MANIPULACIÓN DE FICHEROS..............................................................................................................27
MANIPULACIÓN DE DIRECTORIOS..........................................................................................................29
MANIPULACIÓN DE RUTAS..................................................................................................................30
DETECCIÓN DE CAMBIOS EN EL SISTEMA DE ARCHIVOS.........................................................................30
SELECCIÓN DE FICHEROS A VIGILAR......................................................................................................31
SELECCIÓN DE CAMBIOS A VIGILAR.......................................................................................................32
DETECCIÓN SÍNCRONA DE CAMBIOS......................................................................................................32
DETECCIÓN ASÍNCRONA DE CAMBIOS....................................................................................................34
EJEMPLO: CIFRADOR DE DIRECTORIOS..................................................................................................35
PROBLEMAS DE DESBORDAMIENTOS DEL BUFFER DE CAMBIOS...................................................................36
FICHEROS TEMPORALES....................................................................................................................37
Tema 3: Ficheros
Conceptos básicos
Aunque cada sistema operativo puede tener su propio formato de nombres de fichero,
nosotros veremos el que se utiliza en la plataforma .NET, que básicamente puede
considerarse que consiste en nombrar a los ficheros usando hasta 259 caracteres
Unicode imprimibles e interpretándose el nombre que se les dé según este formato:
<nombre>.<extensión>
1. Permiten organizar el sistema de archivos del ordenador de manera que sea más
fácil localizar ficheros en él, pues evitan tener que buscarlos entre todos los ficheros
de la máquina y acotar las búsquedas tan sólo a los incluidos en ciertos directorios.
Como los nombres de ficheros, en .NET los nombres de directorios pueden ser
cualquier combinación de caracteres Unicode imprimibles excepto \. Sin embargo,
ahora el límite de caracteres que pueden tener está limitado a 248 y se ha reservado un
el nombre especial \ para el directorio raíz.
Sintaxis en Windows
Como cada fichero y directorio está a su vez contenido dentro de otro directorio, para
identificarlo unívocamente habrá que indicar el camino que lleva desde el directorio raíz
hasta dicho fichero o directorio. Esto se le suele denominar su ruta completa, y en cada
sistema operativo sigue su propia sintaxis. Por ahora nos centraremos en la usada en los
sistemas Windows, aunque luego veremos el diseño de la BCL es bastante flexible y
permite escribir aplicaciones independientes de sistema operativo en este aspecto.
En Windows la sintaxis que sigue para escribir una ruta completa es de la forma:
<nombreDirectorioPadre>\<nombreFicheroODirectorio>
Como cada directorio puede estar contenido dentro de otro directorio, lo que se indica
en <nombreDirectorioPadre> es a su vez la ruta completa del directorio donde se
encuentra el fichero indicado en <nombreFicheroODirectorio>, y obviamente el primer
carácter de cualquier ruta completa será el \ correspondiente al directorio raíz. Por
ejemplo, la ruta completa de un fichero datos1.dat incluido dentro de un directorio
llamado Datos es \Datos\datos.dat, pero si dicho directorio se encontrase a su vez
contenido dentro de Programa entonces sería \Programa\Datos\datos.dat
En caso de que se desee especificar un fichero que esté ubicado en una máquina remota,
entones para hacerle referencia hay que usar el formato UNC1, cuya sintaxis es:
\\<nombreServidor>\<nombreFicheroCompartido>
1
UNC = Universal Naming Convention o Convenio Universal de Nombrado
Operaciones con rutas
Aparte de la utilidad independizadora del sistema opeartivo vista, Path también es muy
útil porque incluye un conjunto de miembros estáticos específicamente diseñados para
realizar cómodamente las operaciones más frecuentes relacionadas con rutas.
La mayoría de estos métodos toman como parámetros cadenas de texto con las rutas
sobre la que se desean aplicar la operación que facilitan. Si recuerda, como en Windows
el carácter usado como separador de directorios (\) coincide con el que C# usa como
indicador de secuencias de escape, en este lenguaje es incorrecto representar rutas como
c:\datos con literales como “c:\datos”. En su lugar hay tres alternativas:
• Usar el campo independizador del sistema operativo, aunque ello tiene el problema
de que da lugar a códigos poco compacto. Por ejemplo, para la ruta anterior habría
que representarla con “c:”+Path.DirectorySeparatorChar+”\datos”.
• Duplicar los caracteres \ de los literales para que dejen de considerarse secuencias de
escape. Así, la ruta de ejemplo anterior quedaría como “c:\\datos”
Dicho esto, el primer grupo de métodos que veremos son los destinados a facilitarnos la
extracción de información sobre las diferentes partes de la ruta que se les pase, que son:
Aparte de estos métodos también se incluyen otros que permiten hacer tareas de
diversos tipo relacionadas con rutas como:
• Modificar su extensión: El método string ChangeExtension(string ruta, string
nuevaExtensión) puede usarse tanto para cambiar la extensión del directorio o
fichero cuya ruta se le indica como para añadirle una nueva si no tenía ninguna o
quitarle la que tuviese si se le pasa null como nuevaExtensión.
Al usar este método tenga cuidado, pues si el resultado de combinar las rutas no da
lugar a una ruta válida no se producirá ninguna excepción, sino que en su lugar se
devolverá el valor indicado como ruta2. Por ejemplo, el resultado de una llamada
como Path.Combine(@“c:\datos”, “@d:\patatas”) es “@d:\patatas”.
Cuando use cualquiera de estos métodos tenga siempre presente una cosa: permiten sólo
operar con cadenas de texto que representan rutas pero no están de en ninguna manera
relacionados con las ruta que representan. Su utilidad es permitir preparar nuevas rutas a
partir de otras ya existentes para luego poder, si se desea, podrán ser pasarse a otros
mecanismos para actuar físicamente sobre ellas. Por ejemplo, modificar una ruta con
ChangeExtension() no implica que la ruta física en el dispositivo no volátil al que esté
asociada quede también modificada, pero la cadena que se devuelva puede usarse luego
para modificarla mediante otros métodos que iremos viendo a lo largo del tema.
Elementos comunes
En la plataforma .NET el trabajo con ficheros y directorios se hace, como no, siguiendo
un enfoque orientado a objetos en el que cada se los encapsula dentro de un objetos de
ciertos tipos: los ficheros en objetos FileInfo y los directorios en objetos DirectoryInfo.
Según su funcionalidad los miembros comunes ofrecidos por esta clase se agrupan en:
• Determinación de existencia: Saber si existe físicamente o el fichero o directorio
representado por un objeto FileSystemInfo es tan sencillo como mirar el valor de su
propiedad bool Exists.
• Borrado: Como tanto los ficheros como los directorios pueden ser borrados, en
FileSystemInfo se proporciona un método Delete() mediante el que es posible borrar
ambos tipos de elementos del sistema de archivos. Sin embargo, al usarlo hay que
tener en cuenta que si el elemento a borrar es un directorio, éste ha de estar vacío
porque si no lanzará una IOException
• Por otro lado, los sistemas operativos también suelen asociar una serie de atributos a
cada uno de sus ficheros y directorios que describen cómo ha de interpretarse bajo
determinadas circunstancias. Mediante la propiedad Attributes podemos acceder a
ellos tanto para leerlos como para modificarlos. Esta propiedad es de un tipo
enumerado llamado FileAttributes que admite los siguientes literales:
Tenga en cuenta que la tabla anterior abarca atributos propios de muy diversos
sistemas operativos que no tienen porqué estar presentes en todos los sistemas
operativo. Además, aunque pueda darle la sensación de que cada fichero o directorio
sólo puede disponer de sólo uno de los atributos señalados porque su propiedad
Attributes devuelve un único objeto, esto no es así porque FileAttributes ha sido
definido como una enumeración de flags y sus objetos pueden almacenar
combinaciones OR de sus literales. Por ejemplo, un método que convierta ficheros o
directorios en ocultos y de sólo lectura puede escribirse así:
Aunque el trabajo con ficheros y con directorios es muy similar –de hecho, como ya
hemos visto, los tipos que los representan derivan de una clase común FileSystemInfo-,
también es cierto que cada uno de ellos dispone tiene una serie de características propias
que lo diferencian del otro. Por esta razón, en la BCL se ha definido como abstracta
FileSystemInfo y se han incluido subclases suyas no abstractas FileInfo y DirectoryInfo
que añaden a los miembros comunes heredados de su padre miembros relacionados con
funcionalidades particulares de cada tipo de elemento.
Para crear el objeto DirectoryInfo que represente a un determinado directorio basta pasar
como parámetro de su constructor una cadena con la ruta –relativa o completa- del
mismo. Por ejemplo, un objeto d que represente al directorio c:\Pruebas se crea así:
DirectoryInfo d = new DirectoryInfo(@”c:\Pruebas”);
Este directorio no tiene porqué existir, pues como se verá más adelante es posible crear
directorios a través de objetos DirectoryInfo que representen directorios no existentes.
Los objetos DirectoryInfo cuentan con métodos con los que pueden obtenerse tablas con
todos los ficheros (FileInfo[] GetFiles()), directorios (DirectoryInfo[] GetDirectories()), o
ficheros y directorios (FileSystemInfo[] GetFileSystemInfos()) contenidos en el directorio
que representan. Por ejemplo, todos los ficheros de c:\Windows pueden obtenerse así:
FileInfo[] ficheros = new DirectoryInfo(@”C:\Windows”).GetFiles();
Cada uno de estos métodos tiene una sobrecarga que admite como parámetro una
cadena con la que pueden filtrarse los nombres de los ficheros o directorios que se
desean obtener. Dicho filtrado consiste en usar los caracteres ? y * como comodines con
los que representar, respectivamente, cualquier carácter y cualquier número de
cualesquiera caracteres. Así por ejemplo, si en vez de todos los ficheros de c:\windows
queremos obtener tan sólo los ejecutables (extensión .exe) podemos hacer lo siguiente:
FileInfo[] ficheros = new DirectoryInfo(@”C:\Windows”).GetFiles(*.exe);
Todo sistema operativo proporciona mecanismos mediante los que sus usuarios pueden
realizar modificar la estructura de directorios del sistemas de archivos de las máquinas,
realizando tareas tales como crear nuevos directorios, eliminar directorios existentes,
etc. Pues bien, para poder realizar este tipo de tareas de una manera automatizada desde
nuestro programas, la clase DirectoryInfo incluye el siguiente conjunto de métodos que
permiten realizarlas programáticamente:
A través de este objeto se pueden hacer las operaciones típicas de creación (Create()),
borrado (Delete()) y movimiento (MoveTo()) de ficheros de manera análoga a como se ha
visto que se hacen con los directorios. Adicionalmente se ha añadido un método
CopyTo() que funciona de manera similar a MoveTo() sólo que en vez de cambiar al
ubicación del fichero lo que hace es crear una copia del mismo en la ubicación indicada.
De nuevo, para evitar sobreescrituras no deseadas ambos métodos no admiten que el
fichero de destino especificado ya exista lanzan IOExceptions si fuese así, pero ello
también puede cambiarse pasándoles true como segundo parámetro.
En lo referente al acceso a información adicional sobre el fichero hay que señalar que en
este caso la analogía con el caso de los directorios no es tan pura, pues para obtener el
directorio padre de un fichero la propiedad a usar del objeto FileInfo que lo representa
no se llama ahora Parent sino DirectoryName. Además, también se ofrece una segunda
propiedad llamada Directory que permite acceder a esa misma información pero en
forma de objeto DirectoryInfo en lugar de cadena de texto.
Como habrá adivinado, aparte de estos miembros los objetos FileInfo deben también de
ofrecer mecanismos que permitan acceder al contenido de los ficheros que representan,
ya sea para leerlo o para modificarlo. Pues bien, precisamente a explicar cómo se realiza
eso es a lo que esta destinado los siguientes epígrafes.
Flujos de entrada-salida
La forma con la que se trabaja con ficheros en la plataforma .NET está íntimamente
ligada al concepto de flujo de entrada-salida, que consiste en tratar su contenido como
si de una secuencia ordenada de bytes se tratase. Este concepto no está ligado en
exclusividad a los ficheros, sino que es un concepto abstracto aplicable a otros tipos de
almacenes de información tales como conexiones de red o buffers en memoria.
• Flujos base: Trabajan directamente con algún tipo de medio físico, como puede ser
una porción de memoria, de espacio en disco o una conexión a red.
• Flujos intermedios: No trabajan con medios físicos directamente sino envuelven a
otros flujos a los que proporcionan diferentes características, pudiéndose combinar
entre sí de manera que el flujo base que haya tras ellos pueda verse beneficiado por
todas las funcionalidades que ofrezcan todas ellos. Ejemplos son los flujos que
proporcionan encriptación de datos o buffers de almacenamiento intermedio.
En la BCL todos los tipos relativos al trabajo con flujos se encuentran agrupados dentro
del espacio de nombres System.IO Un tipo muy importante entre ellos es Stream, que es
la clase abstracta base de todos los flujos y les proporciona los miembros básicos que
permiten trabajar con ellos. Estos miembros se explicarán en los epígrafes que siguen.
Lectura y escritura
Como es lógico, todo flujo dispondrá de mecanismos mediante los que se le puedan
extraer y añadir bytes. No todos los flujos tienen porqué admitir ambas operaciones, por
lo que para saber qué operaciones admite cada flujo concreto Stream añade a todos ellos
dos propiedades bool CanRead y bool CanWrite que permite consultarlo.
Los métodos anteriores funcionan síncronamente, lo que significa que tras llamarlos el
código que los llamó se quedará en espera de que terminen de ejecutarse. Sin embargo,
como las operaciones de entrada-salida suelen ser lentas también se ofrecen parejas de
métodos BeginRead()-EndRead() y BeginWrite()-EndWrite() que permiten realizarlas
asíncronamente de manera que mientras se estén realizando el código llamador pueda
seguir ejecutándose y realizando otras tareas. Estos métodos funcionan análogamente a
como lo hacen las parejas de métodos BeginInvoke y EndInvoke de los delegados.
Tras cada lectura la siguiente llamada a estos métodos devolvería los bytes del flujo
siguientes a los últimos leídos, y para detectar cuando se alcance el final del flujo basta
mirar su valor de retorno, pues en ese caso ReadByte() devolverá un –1 y Read() un 0.
Respecto a la escritura, cada vez que se escriba se escribirá a continuación de los
últimos bytes escritos en el flujo.
Por defecto, la primera operación que se realice sobre un flujo se aplica a su comienzo y
las siguientes se aplican tras la posición resultante de la anterior. Es decir, en cada
lectura o escritura se lee o escribe a continuación de la última posición accedida, por lo
que antes de acceder a una determinada posición habrá que pasar antes por las previas.
A esto se le conoce como acceso secuencial.
Sin embargo, hay situaciones en las que puede resultar interesante poderse escribir o
leer de cualquier posición del flujo sin necesariamente tener que pasar antes por sus
anteriores. A esto se le conoce como acceso aleatorio, y como no todos los flujos tienen
porqué admitirlo, cada flujo dispone de una propiedad de sólo lectura bool CanSeek que
indica si lo admite.
Si un flujo admite acceso aleatorio le serán aplicables los miembros de Stream relativos
a dicho tipo de acceso que a continuación se muestran, mientras que si no lo admite la
utilización de los mismos provocará excepciones NotSupportedException:
flujo.Position = númeroByte;
Console.WriteLine(“Byte {0} = {1}”, númeroByte, flujo.ReadByte());
flujo.Position = posiciónAnterior;
}
Este método imprime en la consola el byte que se le indique del flujo que se le
pase como parámetro. Nótese que, para asegurar que su ejecución sea inocua y
no altere el flujo se controla a su comienzo cuál era la posición inicial en el flujo
y a su final se le restaura a dicha posición. Por otra parte, también se controla
que el númeroByte indicado se encuentre dentro del flujo, pues sino aunque la
asignación flujo.Position = númeroByte sería válida y no produciría excepciones, el
flujo habría quedado en un estado inconsistente y ReadByte() devolvería –1.
Sin embargo, hay ocasiones en puede interesar asegurarse de que en un cierto instante
se haya realizado el volcado físico de los bytes en un flujo, ya sea porque de ello
dependa el correcto funcionamiento de otras partes de la aplicación o se sepa que no se
volverá a escribir en él hasta que dentro de mucho. En esos casos puede forzarse el
volcado llamando al método Flush() del flujo, que vacía por completo su buffer interno.
Cierre de flujos
Además de memoria, muchos flujos acaparan recursos extra que interesa liberar una vez
dejen de ser útiles. Por ejemplo, las conexiones de red acaparan sockets y los ficheros
acaparan manejadores de ficheros del sistema operativo, que son recursos limitados y si
no liberarlos podría impedir la apertura de nuevos ficheros o conexiones de red.
Como podrá adivinar, en principio ello es tarea del método Dispose() con el que todo
flujo cuenta como resultado de implementar la interfaz IDisposable que se usa en .NET
como estándar recomendado con el que liberar recursos de manera determinista. Sin
embargo, por similitud con las APIs de manejo de flujos de otros lenguajes a las que
muchos programadores estarán acostumbrados, en .NET la clase Stream también
dispone de un método Close() que hace lo mismo.
En cualquier caso, lo que es importante saber es que una vez cerrado el flujo –ya sea
llamando a Dispose() o a Close()- ya no es posible hacer uso del mismo y cualquier
intento de acceder a sus miembros provocará excepciones ObjectDisposedException
Por ejemplo, para abrir un fichero preexistente llamado c:\datos.dat puede hacerse:
FileInfo fichero = new FileInfo(@“c:\datos.dat”);
FileStream contenidoFichero= fichero.Open(FileMode.Open);
Además del modo de apertura, también puede resultar interesante especificar qué es lo
que deseamos hacer con el contenido del fichero: si sólo leerlo, si sólo escribir en él o si
ambas cosas; pues muchos sistemas operativos permiten asociar a los ficheros permisos
de acceso con los que puede restringirse qué puede hacerse con cada uno. Como si no
indicamos nada se considerará que queremos tanto leer como escribir en el fichero, y
para especificar cualquier otra cosa basta pasar a Open() un parámetro adicional del tipo
enumerado FileAccess que indique como se desea abrirlo. Los literales de este tipo son:
Por otra parte, la mayoría de los sistemas operativos permiten controlar la forma en que
diferentes procesos puede acceder simultáneamente a sus ficheros. Por seguridad, para
evitar problemas de concurrencia no se permite que múltiples hilos puedan compartir
ficheros abiertos con cualquiera de las sobrecargas de Open() vistas. Sin embargo, ello
puede cambiarse especificando un tercer parámetro de tipo enumerado FileShare, cuyos
posibles literales son:
Hasta ahora todas las formas de crear objetos FileStream que hemos visto eran métodos
proporcionados por la clase FileInfo que encapsula ficheros, lo que nos obliga a siempre
tener que crear un objeto de este tipo para poder acceder al contenido de un fichero.
Como ello puede resultar ineficiente, la propia clase FileStream también proporciona
una familia de constructores que permiten crear objetos suyos mucho más directamente.
Así, los siguientes constructores son equivalentes a las tres sobrecargas de Open() vistas:
Por ejemplo, para leer el contenido del fichero c:\datos.dat podemos generar un
FileStream así:
Dada la relativa lentitud de las operaciones de acceso a ficheros, para mayor eficiencia
durante su realización se ha optado por asociar un buffer a cada FileStream en el que los
bytes que se soliciten escribir se irán almacenando hasta alcanzar un cierto número para
entonces escribirlos todos de una vez. Por defecto el tamaño de este buffer es de 8192
bytes, pero puede cambiarse usando el siguiente constructor para crear el FileStream:
Al especificar así un tamaño de buffer hay que tener en cuenta que no conviene darle un
tamaño inferior a 8 bytes, pues sería tan pequeño que prácticamente no se ganaría nada
de eficiencia. Es más, cualquier valor inferior a dicha cantidad que se le dé será
ignorado y se tomará en su lugar un tamaño de 8 bytes.
Lo que si es importante recordar es que debido a este buffer, cuando queramos estar
seguros de que en un momento concreto se haya volcado todo su contenido en el fichero
al que está asociado, hemos de llamar al método Flush() Por cierto, por si lo estaba
pensando: cuando hayamos terminado de usar un FileStream y vayamos a cerrarlo no
tenemos porqué preocuparnos de vaciar este buffer, pues el método Close() usado para
ello se encarga automáticamente de hacerlo.
Nótese que hasta ahora no se ha dicho nada acerca de las versiones asíncronas de los
métodos de lectura-escritura heredados de Stream. Esto se debe a que por defecto el
acceso al contenido de los ficheros se realiza siempre de manera síncrona, aunque se
usen los métodos anteriores. Esto se debe a que para ficheros pequeños es la forma de
acceso más eficiente, que son los más frecuentemente utilizados. Sin embargo, como al
trabajar con ficheros grandes puede resultar más eficiente leerlos asíncronamente para
poder realizar otras tareas mientras tanto, también se permite activar el acceso
asíncrono. Para ello basta crear el FileStream usando el siguiente constructor:
A modo de ejemplo que sintetice todo lo visto hasta ahora respecto al acceso a ficheros
y su contenido vamos a desarrollar una sencilla aplicación de consola que actúe como
un visor hexadecimal que muestre toda la información relativa al fichero que se le pase
como argumento al llamarla así como los bytes que contiene. Su código es el siguiente:
using System;
using System.IO;
while (nBytesLeidos!=0)
{
// Mostramos posición en el fichero del primer byte de la línea
Console.Write("\n{0:X8}:", contenido.Position-16);
Para mostrar el contenido del fichero se usa el método muestraContenido(), que lo abre
en modo de sólo lectura y lee su contenido byte a byte mostrando por pantalla los bytes
así leídos en líneas de 16. Cada una de estas líneas va precedida del número que su
primer byte ocupa en el fichero, para así facilitar la determinación de la ubicación en el
fichero de cada uno de sus bytes, y seguida de la representación de sus bytes en forma
de caracteres, para así facilitar la lectura de ficheros de texto. Por ejemplo, al pasar a
este programa como argumento el propio fichero de su código fuente se mostrará:
Fíjese que no cierra explícitamente el FileStream tras terminar de usarlo. Esto se debe a
que en su lugar se ha usado la novedosa instrucción using de C#, que lo hace
automáticamente (en realidad, el cuerpo de Close() es solo una llamada al método
Dipose() al que llama using) Sin embargo, usando otros lenguajes sí que podría ser
necesario llamar explícitamente a Close()
Control de concurrencia en accesos a ficheros
• Lock(long byteInicial, long nBytes): Bloquea el acceso a la sección del fichero que
comienza en el byteInicial indicado y cuyo tamaño en bytes es nBytes
• Unlock(long byteInicial, long nBytes): Desbloquea el acceso a la sección del fichero
indicada. Si dicha sección no estaba bloqueada no se preocupe, pues no pasará nada.
Para los casos en que la eficiencia en los accesos a archivos sea crítica, .NET también se
da la posibilidad de trabajar directamente con sus manejadores asociados y podérselos
pasar como parámetros a métodos nativos que trabajen con ellos más eficientemente.
Esto se consigue mediante la propiedad Handle de los objetos FileStream, que devuelve
un objeto System.IntPtr que representa al manejador que encapsula.
Hay que tener en cuenta que los manejadores así devueltos pueden ser utilizados por
ciertas funciones nativas del sistema operativo, como las funciones ReadFile() y
WriteFile() de la API Win32, pero ello no significa que también puedan usarse con
funciones que esperen descriptores de ficheros tales como la fread() de la librería de C.
Fichero binarios
La forma de acceder a ficheros que hasta ahora hemos visto tiene una seria limitación, y
es que trabaja con la información a nivel de bytes, lo cual no suele ser muy conveniente
en tanto que generalmente en las aplicaciones no suele trabajarse directamente con bytes
sino con objetos más complejos formado por múltiples bytes. Por ejemplo, dado que un
int ocupa 4 bytes, para almacenar su valor en un fichero usando un FileStream habría
que realizar una operación WriteByte() por cada uno de sus bytes o descomponer todos
los en una tabla byte[] y escribirlos de una vez con una llamada a Write()
Como esta no es ni por asomo una solución cómoda, en System.IO se han incluido un
par de tipos llamados BinaryReader y BinaryWriter que encapsulan FileStreams y les
proporcionan una serie de métodos con los que se simplifica la lectura y escritura de
objetos de cualquier tipo en ficheros. Aunque similares a flujos intermedios, no hay que
confundir estos tipos con tales, pues aunque encapsulan a otros flujos y disponen de
muchos de los métodos comunes a éstos, no derivan de Stream.
Para crear objetos de estos tipos simplemente basta con pasar como parámetro de sus
respectivos constructores el flujo a envolver, pues éstos son de la forma:
BinaryReader(Stream flujo)
BinaryWriter(Stream flujo)
Una vez encapsulado el flujo siempre es posible extraerlo mediante la propiedad Stream
BaseStream {get;} que ambos tipos de objetos proporcionan para ello.
Por defecto, se considera que la codificación de las cadenas de texto que se lean o
escriban del flujo es UTF-8, pero puede especificarse cualquier otra usando las
siguientes sobrecargas de sus respectivos constructores:
BinaryReader(Stream flujo, Encoding codificación)
BinaryWriter(Stream flujo, Encoding codificación)
2
De todas formas fíjese que puede seguir haciendo accesos aleatorios a través del método Seek() del
objeto que encapsula, pues éste puede recuperarse a través de la propiedad BaseStream antes comentada
Como se deduce de los prototipos anteriores, la codificación se indica usando objetos
System.Text.Encoding que representarán formatos codificación. Aunque estos objetos
permiten acceder a información sobre los diversos formatos de codificación (página de
código, bits de preámbulo, etc.) y realizar conversiones entre sus juegos de caracteres,
nosotros por ahora sólo los usaremos para indicar el tipo de codificación a usar y los
obtendremos de las siguientes propiedades estáticas de Encoding:
Por ejemplo, para escribir en un flujo que represente a un fichero llamado datos.dat
usando UTF8 al codificar los caracteres puede hacerse lo siguiente:
Del mismo modo, para leer correctamente dicho mensaje habría que hacer algo como:
Lo que es importante es tener en cuenta que la codificación a usar al leer los caracteres
de un fichero de texto debería ser siempre la misma que la que se usó para escribirlos en
él, pues si no podrían obtenerse resultados extraños al interpretarse mal los datos leídos.
Ficheros de texto
Para facilitar la lectura de flujos de texto TextReader ofrece una familia de métodos que
permiten leer sus caracteres de diferentes formas:
• De uno en uno: El método int Read() devuelve el próximo carácter del flujo como, o
un –1 si se ha llegado a su final. Tras cada lectura la posición actual en el flujo se
mueve un carácter hacia delante, y por si sólo quisiésemos consultar el carácter
actual pero no pasar al siguiente también se ha incluido un método int Peek() que
funciona como Read() pero no avanza la posición en el flujo tras la lectura.
• Por grupos: El método int Read(out char[] caracteres, int inicio, int nCaracteres) lee
un grupo de nCaracteres y los almacena a partir de la posición inicio en la tabla que se
le indica. El valor que devuelve es el número de caracteres que se hayan leído, que
puede ser inferior a nCaracteres si el flujo tenía menos caracteres de los indicados o
un –1 si se ha llegado al final del flujo. También se incluye un método ReadBlock()
que funciona de forma parecida al anterior pero que se queda a la espera de nuevos
caracteres si en el flujo no hay disponibles nCaracteres pero se espera que lleguen,
lo que puede ocurrir cuando se lee de flujos asociados a una conexiones de red.
• Por completo: Un método muy útil que se ha incluido en la BCL y que las librerías
de muchos otros lenguajes y plataformas no tienen es string ReadToEnd(), que nos
devuelve una cadena con todo el texto que hubiese desde la posición actual del flujo
sobre el que se aplica hasta el final del mismo (o null si ya estábamos en su final)
Como la mayoría de estos métodos modifican la posición actual en el flujo pueden darse
problemas de concurrencia si varios hilos usan a la vez un mismo TextReader. Para
evitarlos se ofrece un método estático TextReader Synchronized(TextReader textreader)
que devuelve una versión del TextReader que se le pasa como parámetro en la que se
asegura la exclusión mutua en el acceso a aquellos miembros suyos que podrían dar
problemas al usarlos en entornos concurrentes.
Aparte de estos métodos, los TextReaders también incluyen un método Close() mediante
el que pueden liberarse los recursos que hubiesen acaparado para poder leer de la fuente
que tuviesen asociada. Obviamente, tras llamar a este método no se podrá seguir usando
el TextReader para leer de dicha fuente, e intentar de hacerlo provocará IOExceptions.
Como se ve, estos constructores permiten crear el StreamReader a partir del flujo cuyo
lectura facilitan (puede luego recuperarse leyendo su propiedad Stream BaseStream
{get;}) y que pueden configurarse para usar un cierta codificación y tamaño de buffer
La codificación tomada por defecto es UTF-8, aunque puede cambiarse dándole el valor
apropiado al parámetro codificación. Además, también se incluye un parámetro llamado
autodetectarCodificación que indica si se desea que se deduzca la codificación del texto a
partir de los valores de sus primeros bytes. Si no pudiese autodetectarse se consideraría
que es la indicada en codificación ó UTF-8 en su defecto. En cualquier caso, siempre es
posible saber cuál es la codificación considerada a través de la propiedad Encoding
CurrentEncoding {get;} del StreamReader, pero al leerla hay que tener en cuenta que su
valor puede cambiar tras la primera lectura como consecuencia de una autodetección
El tamaño del buffer que internamente usarán los StreamReaders para alojar los
caracteres que lean es por defecto de 256 caracteres (4096 bytes) Sin embargo, dicho
tamaño puede cambiarse al crearlos indicando su tamaño–en número de caracteres- en
el parámetro tamañoBuffer de su constructor. Dicho valor ha de ser de menos 128 (2048
bytes), y si se le diese alguno menor sería ignorado y se tomaría el mínimo señalado.
La existencia de un buffer implica que pueda ocurrir que haya momentos en los que la
posición actual en el StreamReader sea distinta a la posición actual en su Stream interno
debido a que se hayan leído más caracteres de los solicitados con la idea de guardar
temporalmente los sobrantes en el buffer por si se solicita en breve su lectura. Además,
los StreamReaders suponen que a su flujo interno sólo se accederá a través de ellos, por
lo que si se le hiciesen cambios externamente podrían no verlos directamente por estar
leyendo de su buffer interno. Para resolver los problemas de inconsistencia que esto
podría provocar, todo StreamReader cuenta con un método DiscardBufferedData() que
vacía por completo dicho buffer.
Como un uso muy frecuente de los StreamReaders es extraer textos de ficheros, también
se han definido versiones de cada uno de los constructores anteriormente vistos que en
lugar de tomar como primer parámetro un Stream lo que toman es la cadena con la ruta
del fichero a leer y a partir de ella generan el Stream apropiado. Éstos son:
StreamReader(string rutaFichero)
StreamReader(string rutaFichero, Encoding codificacion)
StreamReader(string rutaFichero, bool autodetectarCodificación?)
StreamReader(string rutaFichero, Encoding codificacion,
bool autodetectarCodificación?)
StreamReader(string rutaFichero, Encoding codificacion,
bool autodetectarCodificación?, int tamañoBuffer)
Nótese cómo ahora la extracción del contenido del fichero es mucho más sencilla que en
el caso de trabajar directamente con el FileStream asociado al contenido del fichero,
habiéndose reducido el complejo método muestraContenido() del visor hexadecimal de la
anterior aplicación de ejemplo a una única llamada a ReadToEnd() en ésta.
Del mismo modo que los TextReaders facilita la lectura de textos procedentes de fuentes
genéricas, los TextWriters hace lo propio con la escritura en destinos genéricos. Para ello
ofrece métodos que permiten:
• Escribir cadenas de texto: Todo TextWriters cuenta con método Write() que permite
escribir cualquier cadena de texto en el destino que tengan asociado. Esta cadena se
les pasa como primer parámetro, pero no tiene porqué ser un string sino que puede
ser un objeto de cualquier tipo y lo que se haría sería aplicarle su método ToString()
para obtener su representación en forma de cadena de texto.
También puede pasársele como parámetro una tabla char[], en cuyo caso lo que se
escribiría serían todos sus caracteres en el mismo orden en que apareciesen en ella.
Si además se usa la sobrecarga Write(char[] caracteres, int posición, int n) se podría
delimitar los caracteres de la tabla para sólo escribir los n primeros incluidos a partir
de la posición de la tabla indicada.
Como pasa con el Write() de Console, también existe sobrecarga de este método que
toma como parámetros la cadena a mostrar y un número indefinido de objetos cuya
representación como cadenas se escribirá en el destino en el lugar correspondiente a
las subcadenas {i} de la cadena que se le pasó como primer parámetro.
Dado que el indicador de fin de línea depende de cada sistema operativo, todo
TextWriter dispone de una propiedad string NewLine mediante la que podemos
configurar cuál queremos que se use al escribir en su destino asociado. Su valor por
defecto es el “\r\n” correspondiente al indicador de fin de línea en Windows, pero
también puede dársele el valor “\n” correspondiente a Linux3.
Cuando se usen cualquiera de estos métodos hay que tener cuidado con los objetos con
valor null que se les pasen, pues no escriben nada en su lugar en el destino del
TextWriter pero tampoco lanzan ningún tipo de excepción que informe de ello.
Además, también hay que tener cuidado al usarlos en entornos concurrente ya que si
varios hilos usan a la vez un mismo TextWriter podrían presentarse problemas de
concurrencia. Para evitarlos, a partir del TextWriter puede generarse una versión suya
donde se asegure la exclusión mutua al acceder a sus miembros conflictivos llamando
para ello a su método estático TextWriter Synchronized(TextWriter textwriter)
Aparte de los métodos anteriores, todo TextWriter también incorpora el típico método
Close() mediante el que pueden liberarse los recursos que hubiese acaparado para ser
capaz de escribir en su destino asociado. Además, como para optimizar dichas escrituras
las realizan a través de un buffer intermedio, también cuentan con el método estándar
Flush() encargado de vaciarlo para cuando, sin necesidad de cerrar el TextWriter, se
necesite asegurar el volcado físico de todo lo escrito a través de él.
StreamReader(string rutaFichero)
StreamReader(string rutaFichero, bool concatenar)
StreamReader(string rutaFichero, bool concatenar, Encoding codificacion)
StreamReader(string rutaFichero, bool concatenar, Encoding codificacion,
int tamañoBuffer)
Como se ve, éstos constructores toman un parámetro adicional llamado concatenar. Este
parámetro permite indicar qué ha de hacerse si el fichero donde se desea escribir ya
existe. Si vale false será sobreescrito, pero si vale true se concatenarán a su final los
nuevos datos que se escriban en él. Por defecto vale true.
Ya hemos visto que mediante objetos FileInfo podemos realizar las tareas más comunes
de manipulación de archivos, acceso a información sobre ellos y a su contenido; y que
mediante objetos DirectoryInfo podemos hacer lo propio con directorios.
Sin embargo, la realización de estas tareas con ellos implica la creación de objetos de
los tipos citados y ello puede llegar a ser un incordio si sólo tenemos que crearlos para
hacer una operación concreta, como por ejemplo renombrar un fichero. Para evitar esto,
la BCL incluye una serie de tipos que sólo tiene métodos estáticos y que actúan como
utilidades que permiten realizar ese tipo de tareas a partir de las rutas de los ficheros en
una sola instrucción, sin tener que andar creando objetos intermedios. El tipo ofrecido
para manipular así ficheros es File, mientras que para los directorios es Directory.
Hay que tener en cuenta que cada vez que se llame a uno de los métodos de estos tipos
se harán las comprobaciones de seguridad necesarias para ver si se tienen los permisos
necesarios sobre los archivos que intervengan en la operación. Sin embargo, cuando se
usan objetos FileInfo o DirectoryInfo las comprobaciones sólo se hacen sobre el fichero o
directorio que ellos representen la primera que se solicite una operación sobre ellos. Por
tanto, para a hacer múltiples operaciones sobre un mismo fichero o directorio es más
eficiente usar objetos intermedios que los métodos estáticos de los tipos anteriores.
Aparte de versiones más cómodas de utilizar de los servicios ofrecidos por los objetos
FileInfo y DirectoryInfo, estos tipos también proporcionan nuevos servicios relativos a
ficheros y directorios que permiten realizar tareas tan útiles como navegar por el sistema
de archivos de una máquina u obtener la lista de unidades lógicas de las que dispone.
Manipulación de ficheros
Como ya se ha dicho, el tipo ofrecido en la BCL como utilidad mediante cuyos métodos
estáticos se facilita la realización de las operaciones con ficheros más comunes es File.
Los servicios ofrecidos por los métodos de este tipo pueden agruparse en:
Antes crear un fichero puede que nos interese ver si previamente ya existía, pues si
fuese así se borraría el existente. Para ello puede usarse el método estático bool
Exists(string rutaFichero) también incorporado en File.
Si en vez de cambiarlo de ubicación lo que queremos es crear una copia suya en otra
ruta, entonces el método a usar es Copy(string rutaFuente, string rutaCopia) Cuando
se use hay que tener cuidado con que el fichero rutaCopia no exista, pues para evitar
sobreescrituras no deseadas se lanzaría una IOException en caso de que existiese.
Otra posibilidad sería pasarle true como tercer parámetro a Copy() para así indicar
explícitamente que deseamos sobreescribirlo.
• Acceso a información sobre directorios: Igual que, como se ha visto, File cuenta
con miembros que permiten acceder a la información que cada sistema operativo
almacenan sobre los ficheros de su sistema de archivos, Directory hace lo propio con
la relativa a los directorios. Para ello, ofrece los siguientes métodos:
Los métodos anteriores devuelven listas completas con los nombres de todos los
elementos del directorio correspondientes a cada uno. Por ejemplo, la lista completa
de todos los ficheros contenidos en el directorio C:\Pruebas puede obtenerse así:
String[] contenidoPruebas = File.GetFiles(@”C:\Pruebas”);
Sin embargo, como en ocasiones puede que sólo nos interese obtener los contenidos
del directorio cuyos nombres sigan un cierto patrón, de los métodos anteriores
también se ofrece una sobrecarga que admite como parámetro una cadena que
indique el patrón que han de seguir los nombres a obtener. Este patrón puede usar el
carácter ? para indicar que en su lugar se admite cualquier carácter y el carácter *
para indicar que en su lugar se admite cualquier número (incluido 0) de cualesquiera
caracteres. Así, los nombres de los ficheros almacenados en C:\Pruebas que
empiecen por A y tengan extensión txt pueden obtenerse con:
String[] contenidoPruebas = File.GetFiles(@”C:\Pruebas”, “A*.txt”);
• Navegación por el sistema de archivos: A veces puede interesar escribir las rutas
de los ficheros y/o directorios a los que se quiera acceder de manera relativa a un
determinado directorio, para lo que es necesario ir cambiando de directorio actual.
Esto puede ser útil porque permite que las aplicaciones accedan a ficheros instalados
con ellas sin tener porqué conocer la ruta completa donde se instalaron: se accedería
a los ficheros con rutas relativas a la ubicación de su ejecutable.
Una segunda utilidad de escribir las rutas relativamente es que ello puede
simplificar mucho las rutas cuando se va a acceder a varios ficheros y/o directorios
ubicados dentro de uno determinado. Por ejemplo, para acceder a varios ficheros
ubicados en d:\pruebas\versión1 lo mejor puede ser establecer ese directorio
como el actual y así podremos referenciarlos sin tener que escribir el nombre de su
directorio padre.
Como se ve, para poder escribir las rutas de los ficheros de manera relativa a otros
directorios puede necesitarse poder cambiar el directorio considerado como actual.
Para ello, Directory proporciona el método estático SetCurrentDirectory(string
rutaNuevoDirectorioActual), y también incluye un string GetCurrentDirectory() que
permite consultar cuál es el en cada instante el directorio considerado como actual.
Hay sistemas operativos que trabajan con múltiples unidades de disco lógicas, que
se corresponden con los distintos dispositivos de almacenamiento de la máquina
sobre la que corren o con porciones lógicas de ellos y que tendrán su propio árbol de
directorios. Para facilitar la navegación en estos casos, en Directory se ha incluido
un método string GetDirectoryRoot(string rutaDirectorio) que devuelve el nombre del
directorio raíz del árbol asociado a la unidad de disco a la que pertenece al directorio
indicado en el formato <nombreUnidad>:\; y un método string[] GetLogicalDrives()
que devuelve una tabla con los nombres, en el formato anterior, de todas y cada una
de las unidades de disco lógicas de la máquina sobre la que se ejecuta.
• Modificación de los directorios del sistema de archivos: Para realizar las típicas
operaciones de creación, borrado y movimiento de directorios la clase Directory
incluye los métodos estáticos DirectoryInfo CreateDirectory(string rutaDirectorio),
Delete(string rutaDirectorio) y Move(string rutaFuente, string rutaDestino)
Tenga en cuenta que las operaciones que realice sobre un directorio afectan a todos
los ficheros y subdirectorios contenidos en el mismo. Por ello, si borra un directorio
éste ha de estar vacío o si no Delete() producirá una IOException para evitar borrados
accidentales, aunque de todas formas puede pasarle true como segundo parámetro
para indicarle explícitamente su deseo de borrar el contenido del directorio.
Manipulación de rutas
Ahora bien, al usar FileSystemWatcher tenga en cuenta que tiene ciertas limitaciones:
sólo funciona bajo Windows N 4.0, 2000 ó XP y la detección remota no funciona entre
dos máquinas que usen Windows NT 4.0. Tampoco admite la vigilancia del contenido
de unidades de CD-ROM o DVD, aunque por otra parte ello es lógico en tanto que este
tipo de unidades son de sólo lectura y por tanto nunca se producirán cambios en ellas.
En ambos casos tenga en cuenta que el observador vigilará el contenido del directorio
que se le ha especificado aunque posteriormente dicho directorio sea renombrado. Ello
lo consigue gracias a que lo que para él lo identifica no es su nombre sino el manejador
que le asocia el sistema operativo, y éste no cambia por mucho que sea renombrado.
Los observadores creados como se ha dicho detectarán los cambios que se produzcan en
cualquiera de los ficheros y subdirectorios del directorio que se les indique, pero si se
desea que sólo se centre en algunos de ellos puede configurarse su propiedad string
Filter para ello. A esta propiedad se le pasaría una cadena en la que se indicaría el patrón
que seguirán los ficheros a vigilar, representando en ella cualquier carácter mediante un
? y cualquier combinación de cualesquiera caracteres con un *. Por ejemplo, para sólo
vigilar los ficheros de extensión txt de c:\datos podría hacerse:
Nótese que con IncludeSubdirectories indica que desea vigilar no sólo el contenido del
directorio indicado y sus subdirectorios sino también el de los subdirectorios de éstos, y
el de los subdirectorios de los subdirectorios de estos, etc.
Una vez configurados los ficheros que se desean vigilar, los observadores por defecto
sólo vigilan aquellos cambios de tamaño, nombre o fecha de última modificación que se
produzcan en los mismos. Esto puede cambiarse modificando su propiedad NotifyFilters
NotifyFilter, en la que se indica cuáles son los cambios ha vigilar usando un objeto de un
tipo enumerado que admite los siguientes literales:
Estos literales puede combinarse con operaciones lógicas OR. Por ejemplo, los cambios
de nombre o atributos producidos en los ficheros de c:\datos pueden detectarse con:
Una vez configurados los ficheros a vigilar y los tipos de cambios a detectar, la forma
más sencilla de usar FileSystemWatcher para detectar cambios de ese tipo es usando su
método WaitForChangedResult WaitForChanged( WatcherChangeTypes tipoCambio), lo
que dejaría el código bloqueado en espera de que se produjese algún cambio del tipo
indicado en tipoCambio. Este parámetro es de una enumeración cuyos los literales son:
Por ejemplo, el siguiente programa se queda en espera de que se realice algún cambio
en c:\test y luego muestra un mensaje indicando el tipo de cambio producido:
using System;
using System.IO;
class EsperaCambio
{
static void Main()
{
if (!Directory.Exists(@"c:\test");
Directory.CreateDirectory(@"c:\test");
switch(cambio.ChangeType)
{
case WatcherChangeTypes.Changed:
Console.WriteLine(“Detectado cambio en información sobre {0}”, cambio.Name));
break;
case WatcherChangeTypes.Renamed:
Console.WriteLine(“Detectado renombrado de {0} por {1}”,
cambio.Name, cambio.OldName));
break;
case WatcherChangeTypes.Created:
Console.WriteLine(“Detectada creación de {0}”, cambio.Name,));
break;
case WatcherChangeTypes.Deleted:
Console.WriteLine(“Detectado borrado de {0}”, cambio.Name));
break;
}
}
}
Nótese que el problema de utilizar un mecanismo como este para detectar los cambios
es que deja bloqueado al hilo que llama a WaitForChanged() hasta que se produzca el
cambio, lo que puede reducir mucho el rendimiento de la aplicación si éste tarda en
producirse (o incluso bloquearla si nunca llega a producirse)
Para evitar esto y dar la posibilidad de que se puedan realizar otras tareas mientras llega
el evento puede especificarse un como segundo parámetro de tipo int al llamar a
WaitForChanged() que para indicar el tiempo máximo, en ms., que se de esperarse a que
ocurra el cambio, y si no ocurre pasado ese tiempo finalizará la ejecución del método y
se marcará a true la propiedad bool TimedOut del WaitForChangedResult para indicarlo.
Para asociar códigos a ejecutar ante cada tipo de cambio detectado FileSystemWatcher
proporciona los siguientes eventos:
Esto se debe a que aparte de la información proporcionada por el tipo delegado anterior,
ante un renombrado puede convenir disponer también de información sobre el fichero o
directorio afectado antes de ser renombrardo. Eso es precisamente lo que se aporta el
segundo parámetro de este tipo de delegado, que es de un tipo RenameEventArgs hijo de
FileSystemEventArgs, pues añade propiedades destinadas a almacenar el nombre (string
OldValue {get;}) y ruta completa (string OldFullPath {get;}) del fichero o directorio antes
de ser renombrado.
Un ejemplo de cómo mostrar un mensaje cada vez que se detecte la creación de ficheros
en c:\pruebas con la ruta completa del fichero creado es el siguiente trozo de código:
FileSystemWatcher observador = new FileSystemWatcher(@”c:\pruebas”);
observador.Created += new FileSystemEventHandler(muestraMensaje);
donde el método muestraMensaje() estaría definido como sigue en alguna parte dentro
de la misma definición de tipo a la que perteneciese el código anterior:
void muestraMensaje(object emisor, FileSystemEventArgs args)
{
Console.WriteLine(“Creado fichero {0}”, args.FullPath);
}
Hay que tener en cuenta que una vez asociado los códigos de respuesta a los diferentes
eventos del FileSystemWatcher que queramos controlar hay que poner true su propiedad
bool EnableRaisingEvents para lazar el hilo que hará las detecciones de cambios y
llamadas a los códigos asociados a los eventos. Además, en cualquier momento es
posible parar dicho hilo sin más que volver a poner a false esta propiedad.
using System;
using System.IO;
class Cifrador
{
static void Main(string[] args)
{
try
{
FileSystemWatcher observador = new FileSystemWatcher(args[0]);
observador.Created+=new FileSystemEventHandler(ficheroCreado);
observador.EnableRaisingEvents=true;
4
Un componente es cualquier tipo que derive de Component. Sin embargo, no es el objetivo de este
tema estudiar cómo se crean componentes y su explicación se posterga a temas posteriores.
Console.WriteLine("Cifrado de {0} desactivado", args[0]);
}
catch (DirectoryNotFoundException)
{ Console.WriteLine("{0} no es un directorio válido"); }
catch (IndexOutOfRangeException)
{
Console.WriteLine("Error: Llamada incorrecta. Formato correcto de uso:");
Console.WriteLine("Cifrador <rutaDirectorio>");
}
}
lectorFichero.Close();
escritorFichero.Close();
}
}
}
Como puede observar el código del programa es muy sencillo, simplemente comprueba
que se le haya pasado como argumento la ruta de algún directorio; y si es así crea un
observador que vigila dicho directorio y que cada vez que se crea algún fichero sobre el
mismo lo cifra incrementado en tres unidades el valor cada uno de sus bytes (el clásico
criptsosistema de César)
Otra forma para evitarlos es modificar la propiedad NotifyFilter para restringir el tipo de
cambios a detectar y por tanto reducir así su número. Lo que no se le ocurra nunca es
intentar reducirlos usando Filter para filtrar sólo ciertos archivos de la ruta indicada en
Path, pues el buffer en realidad almacena todos los cambios producidos en dicha ruta,
incluidos los que ocurran sobre los ficheros que no entren en el filtro (sólo que cuando
toque procesar estos serán descartados en lugar de procesados)
Ficheros temporales
Muchos sistemas operativos suelen incluir un directorio que ofrecen a las aplicaciones
como almacén temporal de ficheros que sólo son útiles mientras la aplicación se esté
ejecutando pero que una vez ejecutada carecen de sentido y puede eliminarse.
Como según el tipo de sistema operativo que tenga instalado el usuario o según cómo lo
tenga configurado la ruta de éste puede variar, la BCL incluye un método string
GetTempPath() mediante el que puede obtenerse esta ruta para cada máquina concreta.
Por otra parte, como este directorio es compartido entre múltiples aplicaciones, no es
muy recomendable que creemos en él ficheros con nombres elegidos por nosotros
mismos ya que podrían entrar en conflicto con los ya instalados por otras aplicaciones.
En su lugar, es mejor usar el método string GetTempFileName () de Path, que devuelve
un nombre de fichero temporal que seguro que está libre. Por ejemplo, dado el código:
Console.WriteLine(Path.GetTempFileName());
----------------------------------------------------------------------------------------------------------
FileShare.Inheritable no especificado y no válido al usarlo (provoca excepción de
argumento fuera de rango válido)
----------------------------------------------------------------------------------------------------------
Añadir lo del paso por valor y paso por referencia
----------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------
Añadir consejos sobre cómo crear nuevos tipos de excepciones
----------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------
Añadir que la clase padre ha de ir antes que los interfaces
----------------------------------------------------------------------------------------------------------
Uso de this para resolver acceder a campos que coincidan con el nombre de variables
locales.
----------------------------------------------------------------------------------------------------------
Uso de get/set para tratar datos en formato diferente al de su representación interna
Desde el código de una clase puede llamarase a métodos privados de otros objetos de su
misma clase. Útil para acceder a campos privados en copy-constructors