¿Cómo genero una secuencia a partir de una cadena?

759

Necesito escribir una prueba unitaria para un método que toma una secuencia que proviene de un archivo de texto. Me gustaría hacer algo como esto:

Stream s = GenerateStreamFromString("a,b \n c,d");
Omu
fuente
Para la solución de ahorro de memoria, consulte StringReaderStreamen stackoverflow.com/a/55170901/254109
xmedeko

Respuestas:

956
public static Stream GenerateStreamFromString(string s)
{
    var stream = new MemoryStream();
    var writer = new StreamWriter(stream);
    writer.Write(s);
    writer.Flush();
    stream.Position = 0;
    return stream;
}

No olvides usar Usando:

using (var stream = GenerateStreamFromString("a,b \n c,d"))
{
    // ... Do stuff to stream
}

Sobre la StreamWriterno disposición. StreamWriteres solo un contenedor alrededor del flujo base y no utiliza ningún recurso que deba eliminarse. El Disposemétodo cerrará el subyacente al Streamque se StreamWriterestá escribiendo. En este caso ese es el MemoryStreamque queremos volver.

En .NET 4.5 ahora hay una sobrecarga StreamWriterque mantiene abierta la secuencia subyacente una vez que se elimina el escritor, pero este código hace lo mismo y también funciona con otras versiones de .NET.

Consulte ¿Hay alguna forma de cerrar un StreamWriter sin cerrar su BaseStream?

Cameron MacFarland
fuente
134
Un concepto importante para señalar es que una secuencia está compuesta de bytes, mientras que una cadena está compuesta de caracteres. Es crucial comprender que la conversión de un carácter a uno o más bytes (o a una secuencia como en este caso) siempre usa (o supone) una codificación particular. Esta respuesta, aunque es correcta en algunos casos, usa la codificación predeterminada y puede no ser adecuada en general. Pasar explícitamente una codificación al constructor StreamWriter haría más evidente que el autor debe considerar las implicaciones de la codificación.
drwatsoncode
66
Dices "No olvides usar el Uso" para usar el flujo, pero en tu GenerateStreamFromStringmétodo no estás usando el Uso con el StreamWriter. ¿Hay alguna razón para esto?
Ben
12
@Ben Sí. Si dispone de StreamWriter, la secuencia subyacente también se cerrará. No queremos eso. La única razón por la que el escritor es desechable es para limpiar el flujo, por lo que es seguro ignorarlo.
Cameron MacFarland
2
También se debe tener en cuenta que la cadena completa se copia en una memoria que puede ser importante para cadenas grandes porque ahora tenemos una copia adicional en la memoria.
UGEEN
1
@ahong No realmente. StreamWriterprobablemente esté haciendo lo que dijiste internamente de todos modos. La ventaja es la encapsulación y el código más simple, pero a costa de abstraer cosas como la codificación. Depende de lo que intentes lograr.
Cameron MacFarland el
724

Otra solución:

public static MemoryStream GenerateStreamFromString(string value)
{
    return new MemoryStream(Encoding.UTF8.GetBytes(value ?? ""));
}
joelnet
fuente
31
En caso de que alguien use esto con una deserialización de cadenas XML, tuve que cambiar UTF8 a Unicode para que funcione sin una bandera. ¡¡¡Buena publicación!!!
Gaspa79
2
Me gusta este (con el ajuste de Rhyous y el azúcar extra trivial para usar como método de extensión) mejor que la respuesta aceptada; más flexible, menos LOC y menos objetos involucrados (sin necesidad explícita de un StreamWriter)
KeithS
2
new MemoryStream(Encoding.UTF8.GetBytes("\ufeff" + (value ?? ""))si necesita que se incluya la lista de materiales al comienzo de la transmisión
robert4
55
Esta es una sintaxis muy compacta pero causará muchas asignaciones de bytes [], así que tenga cuidado con el código de alto rendimiento.
michael.aird
1
Esta solución aún dejó la oportunidad de hacer que la transmisión sea de solo lectura. new MemoryStream( value, false ). No puede hacer una secuencia de solo lectura si tiene que escribirla con un escritor de secuencias.
codekandis el
106

Agregue esto a una clase de utilidad de cadena estática:

public static Stream ToStream(this string str)
{
    MemoryStream stream = new MemoryStream();
    StreamWriter writer = new StreamWriter(stream);
    writer.Write(str);
    writer.Flush();
    stream.Position = 0;
    return stream;
}

Esto agrega una función de extensión para que pueda simplemente:

using (var stringStream = "My string".ToStream())
{
    // use stringStream
}
Josh G
fuente
55
Descubrí que la secuencia devuelta se cierra (causando excepciones semi aleatorias) cuando el recolector de basura limpia el StreamWriter. La solución fue usar un constructor diferente, uno que me permitiera especificar leaveOpen .
Bevan
45
public Stream GenerateStreamFromString(string s)
{
    return new MemoryStream(Encoding.UTF8.GetBytes(s));
}
Brujo
fuente
24

Use la MemoryStreamclase, llamando Encoding.GetBytespara convertir su cadena en una matriz de bytes primero.

¿Posteriormente necesita un TextReaderen la corriente? Si es así, puede suministrar un StringReaderdirectamente y omitir los pasos MemoryStreamy Encoding.

Tim Robinson
fuente
23

Usé una mezcla de respuestas como esta:

public static Stream ToStream(this string str, Encoding enc = null)
{
    enc = enc ?? Encoding.UTF8;
    return new MemoryStream(enc.GetBytes(str ?? ""));
}

Y luego lo uso así:

String someStr="This is a Test";
Encoding enc = getEncodingFromSomeWhere();
using (Stream stream = someStr.ToStream(enc))
{
    // Do something with the stream....
}
Robocida
fuente
Thomas, ¿por qué rechazar el voto? enc = enc ?? Encoding.UTF8 me permite preguntar específicamente stream con codificación específica, o un valor predeterminado de UTF8, y porque en .net (hasta donde lo uso .net 4.0) no puede dar a un tipo de referencia que no sea string un valor predeterminado en la función firma esta línea es necesaria, ¿tiene sentido?
Robocide
mencionar que necesita poner esto en una clase separada (¿clase estática no genérica?) también es útil y reduce los votos negativos.
Ali
13

Utilizamos los métodos de extensión que se enumeran a continuación. Creo que debería hacer que el desarrollador tome una decisión sobre la codificación, por lo que hay menos magia involucrada.

public static class StringExtensions {

    public static Stream ToStream(this string s) {
        return s.ToStream(Encoding.UTF8);
    }

    public static Stream ToStream(this string s, Encoding encoding) {
        return new MemoryStream(encoding.GetBytes(s ?? ""));
    }
}
Shaun Bowe
fuente
1
Preferiría implementar el primer método como return ToStream(s, Encoding.UTF8);. En la implementación actual ( return s.ToStream(Encoding.UTF8);, el desarrollador se ve obligado a pensar más para comprender el código y parece que el caso s == nullno se maneja y se tira NullReferenceException.)
Palec
10

Aqui tienes:

private Stream GenerateStreamFromString(String p)
{
    Byte[] bytes = UTF8Encoding.GetBytes(p);
    MemoryStream strm = new MemoryStream();
    strm.Write(bytes, 0, bytes.Length);
    return strm;
}
cjk
fuente
1
La posición debe restablecerse después de escribir. Es mejor usar el constructor, como en la respuesta de joelnet.
Jim Balter
10

Versión modernizada y ligeramente modificada de los métodos de extensión para ToStream:

public static Stream ToStream(this string value) => ToStream(value, Encoding.UTF8);

public static Stream ToStream(this string value, Encoding encoding) 
                          => new MemoryStream(encoding.GetBytes(value ?? string.Empty));

Modificación como se sugiere en el comentario de @ Palec de la respuesta de @Shaun Bowe.

Nick N.
fuente
4

Si necesita cambiar la codificación, voto por la solución de @ShaunBowe . Pero cada respuesta aquí copia toda la cadena en la memoria al menos una vez. Las respuestas con ToCharArray+ BlockCopycombo lo hacen dos veces.

Si eso importa aquí, hay un Streamcontenedor simple para la cadena UTF-16 sin procesar. Si se usa con una StreamReaderselección Encoding.Unicodepara ello:

public class StringStream : Stream
{
    private readonly string str;

    public override bool CanRead => true;
    public override bool CanSeek => true;
    public override bool CanWrite => false;
    public override long Length => str.Length * 2;
    public override long Position { get; set; } // TODO: bounds check

    public StringStream(string s) => str = s ?? throw new ArgumentNullException(nameof(s));

    public override long Seek(long offset, SeekOrigin origin)
    {
        switch (origin)
        {
            case SeekOrigin.Begin:
                Position = offset;
                break;
            case SeekOrigin.Current:
                Position += offset;
                break;
            case SeekOrigin.End:
                Position = Length - offset;
                break;
        }

        return Position;
    }

    private byte this[int i] => (i & 1) == 0 ? (byte)(str[i / 2] & 0xFF) : (byte)(str[i / 2] >> 8);

    public override int Read(byte[] buffer, int offset, int count)
    {
        // TODO: bounds check
        var len = Math.Min(count, Length - Position);
        for (int i = 0; i < len; i++)
            buffer[offset++] = this[(int)(Position++)];
        return (int)len;
    }

    public override int ReadByte() => Position >= Length ? -1 : this[(int)Position++];
    public override void Flush() { }
    public override void SetLength(long value) => throw new NotSupportedException();
    public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
    public override string ToString() => str; // ;)     
}

Y aquí hay una solución más completa con las comprobaciones obligatorias necesarias (derivadas de MemoryStreamlo que tiene ToArrayy también los WriteTométodos).

György Kőszeg
fuente
-2

Una buena combinación de extensiones de cadena:

public static byte[] GetBytes(this string str)
{
    byte[] bytes = new byte[str.Length * sizeof(char)];
    System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
    return bytes;
}

public static Stream ToStream(this string str)
{
    Stream StringStream = new MemoryStream();
    StringStream.Read(str.GetBytes(), 0, str.Length);
    return StringStream;
}
MarkWalls
fuente