Recuerdo que una de las primeras cosas que solíamos aprender con un lenguaje o framework era el cómo leer/escribir de o hacia un archivo (o por lo menos antes así era). En la .Net Framework tenemos todo un namespace dedicado a las operaciones de E/S de datos e información (System.IO). Una de las clases básicas en este namespace es la clase abstracta Stream. Cómo en la mayoría de lenguajes y frameworks modernas,Stream encapsula o abstrae las operaciones de E/S hacia diversos orígenes y/o destinos, de esa manera tenemos cosas como Network Streams, File Streams, Memory Streams, etc…
Algo de lo cual no muchos nos habíamos percatado es que las clases derivadas de Stream no solamente abstraen operaciones de E/S, sino que también pueden ser usadas para “transformar” datos sobre la marcha, de esa manera tenemos ya en la .Net framework clases Stream especiales como GzipStream(Compresión/Descompresión), CryptoStream (Encriptación) y me imagino que hay muchas más en la framework. Hace ya varios meses leí en la MSDN Magazine (una de mis magazines favoritas por cierto, les recomiendo a todos suscribirse
) un excelente artículo sobre Testing Transformation Streams (TS de aquí en adelante) y en general de que se trataba una TS y como crearla/utilizarla. Un ejemplo típico de una transformation Stream es una que elimine los espacios en una Stream básica, por ejemplo:
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
namespace FilterSpacesStream
{
public class NoSpaceStream : Stream
{
public NoSpaceStream(Stream sink)
{
Sink = sink;
}
private Stream Sink { get; set; }
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return Sink.CanSeek; }
}
public override bool CanWrite
{
get { return true; }
}
public override long Length
{
get { return Sink.Length; }
}
public override long Position
{
get { return Sink.Position; }
set { Sink.Position = value; }
}
public override void Flush()
{
Sink.Flush();
}
public override long Seek(long offset, SeekOrigin origin)
{
return Sink.Seek(offset, origin);
}
public override void SetLength(long value)
{
Sink.SetLength(value);
}
public override int Read(byte[] buffer, int offset, int count)
{
return Sink.Read(buffer, offset, count);
}
public override void Write(byte[] buffer, int offset, int count)
{
string text = Encoding.UTF8.GetString(buffer);
string result = Regex.Replace(text, @"s", string.Empty);
byte[] data = Encoding.UTF8.GetBytes(result);
Sink.Write(data, 0, data.Length);
}
}
}
Y su respectivo Fact Fixture:
using System.IO;
using System.Text;
using Xunit;
namespace FilterSpacesStream
{
public class NoSpaceStreamFacts
{
private readonly MemoryStream output = new MemoryStream();
private readonly NoSpaceStream sut;
public NoSpaceStreamFacts()
{
sut = new NoSpaceStream(output);
}
[Fact]
public void It_can_read()
{
Assert.Equal(output.CanRead, sut.CanRead);
}
[Fact]
public void It_can_write()
{
Assert.True(sut.CanWrite);
}
[Fact]
public void It_can_Seek()
{
Assert.Equal(output.CanSeek, sut.CanSeek);
}
// And many other operations I really don't want to test...
[Fact]
public void When_write_a_string_with_spaces_it_returns_a_string_without_spaces()
{
string text = "This is a test ";
byte[] buffer = Encoding.UTF8.GetBytes(text);
sut.Write(buffer, 0, buffer.Length);
sut.Flush();
buffer = output.ToArray();
string converted = Encoding.UTF8.GetString(buffer);
Assert.Equal("Thisisatest", converted);
}
}
}
Uno de los usos básicos que podríamos darle a las Transformation Streams es crear una que sobre la marcha me comprima/cambie/elimine datos en la Render Stream utilizada por la ASP.NET Framework, ven ahora hacia dónde estoy orientando este post? (Rayos, creo que pensé en “voz” alta
)
Bueno, hasta la siguiente entrega!