¿Cómo se obtiene una cadena de un MemoryStream?

532

Si me dan un MemoryStreamque sé que se ha llenado con un String, ¿cómo obtengo una Stringcopia de seguridad?

Brian
fuente
1
Nunca estoy seguro de si siempre se requiere reader.close. He tenido problemas en el pasado, así que, por regla general, siempre lo hago solo para estar seguro.
Crujiente

Respuestas:

468

Este ejemplo muestra cómo leer y escribir una cadena en un MemoryStream.


Imports System.IO

Module Module1
  Sub Main()
    ' We don't need to dispose any of the MemoryStream 
    ' because it is a managed object. However, just for 
    ' good practice, we'll close the MemoryStream.
    Using ms As New MemoryStream
      Dim sw As New StreamWriter(ms)
      sw.WriteLine("Hello World")
      ' The string is currently stored in the 
      ' StreamWriters buffer. Flushing the stream will 
      ' force the string into the MemoryStream.
      sw.Flush()
      ' If we dispose the StreamWriter now, it will close 
      ' the BaseStream (which is our MemoryStream) which 
      ' will prevent us from reading from our MemoryStream
      'sw.Dispose()

      ' The StreamReader will read from the current 
      ' position of the MemoryStream which is currently 
      ' set at the end of the string we just wrote to it. 
      ' We need to set the position to 0 in order to read 
      ' from the beginning.
      ms.Position = 0
      Dim sr As New StreamReader(ms)
      Dim myStr = sr.ReadToEnd()
      Console.WriteLine(myStr)

      ' We can dispose our StreamWriter and StreamReader 
      ' now, though this isn't necessary (they don't hold 
      ' any resources open on their own).
      sw.Dispose()
      sr.Dispose()
    End Using

    Console.WriteLine("Press any key to continue.")
    Console.ReadKey()
  End Sub
End Module
Brian
fuente
3
¿No va a deshacerse de StreamWriter cuando la función esté fuera de alcance de todos modos?
Tim Keating
14
No se llama a Dispose cuando una variable está fuera de alcance. Finalize se llamará cuando el GC llegue a él, pero Dispose es algo que debe llamarse antes de que la variable salga del alcance. No lo llamo arriba porque sé que la implementación de StreamWriter y StreamReader no requieren que se llame a Dispose, solo pasa la llamada a la secuencia subyacente. Sin embargo, se puede hacer un argumento legítimo para llamar a Dipose por cualquier cosa que implemente IDisposable ya que no puede garantizar que una versión futura no requiera su eliminación.
Brian
12
@MichaelEakins ¿Por qué la respuesta debería estar incluso en C #, cuando la pregunta está etiquetada como VB.Net?
Rowland Shaw
1
Me alegro de haberme enterado de los "ayudantes" que pasan la llamada de disposición a sus flujos subyacentes, pero esto parece una mala decisión de diseño.
Gerard ONeill
2
Esta decisión fue mitigada más adelante: msdn.microsoft.com/en-us/library/…
Mark Sowul
310

También puedes usar

Encoding.ASCII.GetString(ms.ToArray());

No creo que esto sea menos eficiente, pero no podría jurarlo. También te permite elegir una codificación diferente, mientras que usando un StreamReader deberías especificarlo como parámetro.

Coderer
fuente
66
La codificación está en el espacio de nombres System.Text
northben
2
Estaba buscando el equivalente de PowerShell de esto y tuve que usar esto. ([System.Text.Encoding] :: ASCII) .GetString (ms.ToArray ())
Lewis
Esta solución es útil ya que se puede usar después de que MemoryStream se haya cerrado.
Jacob Horbulyk
2
FWIW, descubrí que esto no funcionaba con cadenas muy grandes, estaba obteniendo OutOfMemoryExceptions. El uso de en su StreamReaderlugar resolvió el problema.
Grant H.
Dado que esto puede ser una trampa: no es consciente de la marca de orden de bytes y puede incluir un Hex 00al comienzo de la cadena. 00 3C 3F-> .<?en Hex Editor pero en VS o Notepad ++: <?. Por lo tanto, no puede ver la diferencia incluso si compara las cadenas a simple vista, solo una herramienta de comparación o un editor hexadecimal mostrarán la diferencia. Si aún lo usa, piense en String.TrimStart. Ver: docs.microsoft.com/en-us/dotnet/api/…
Skalli
99

Usando un StreamReader para convertir el MemoryStream en una Cadena.

<Extension()> _
Public Function ReadAll(ByVal memStream As MemoryStream) As String
    ' Reset the stream otherwise you will just get an empty string.
    ' Remember the position so we can restore it later.
    Dim pos = memStream.Position
    memStream.Position = 0

    Dim reader As New StreamReader(memStream)
    Dim str = reader.ReadToEnd()

    ' Reset the position so that subsequent writes are correct.
    memStream.Position = pos

    Return str
End Function
Brian
fuente
3
Establecer la posición en 0 limita la capacidad de reutilización del método; es mejor dejar que la persona que llama se encargue de esto. ¿Qué pasa si la secuencia contiene datos antes de la cadena, que la persona que llama sabe cómo manejar?
Alex Lyman
1
La declaración de uso garantizará que su StreamReader se elimine, pero la documentación dice que StreamReader cierra la transmisión subyacente cuando se elimina. Por lo tanto, su método cierra el MemoryStream que pasa, lo cual es conceptualmente poco atractivo para las personas que llaman, incluso si dudo que MemoryStream.Dispose haga mucho.
Trillian
Estás en lo correcto. Por lo general, es una mala idea usar el método Dispose en las clases auxiliares de la secuencia, especialmente si la secuencia se pasa a un método como parámetro. Actualizaré esta respuesta. También tengo una respuesta más completa a continuación.
Brian
Si descompila esas clases, verá que el método de eliminación simplemente llama a Dispose () en cualquier transmisión que no sea nula en la instancia (TextWriter, MemoryStream, etc.)
Sinaesthetic
39

use un StreamReader , luego puede usar el método ReadToEnd que devuelve una cadena.

Darren Kopp
fuente
12
Solo quiero mencionar que el Basestreamdebería haber establecido su posición en 0. Me gusta memoryStream.Position = 0;.
Aykut Çevik
26
byte[] array = Encoding.ASCII.GetBytes("MyTest1 - MyTest2");
MemoryStream streamItem = new MemoryStream(array);

// convert to string
StreamReader reader = new StreamReader(streamItem);
string text = reader.ReadToEnd();
Sadjad Khazaie
fuente
22

Las soluciones anteriores no funcionarían en los casos en que está involucrada la codificación. Aquí hay, como una "vida real", un ejemplo de cómo hacer esto correctamente ...

using(var stream = new System.IO.MemoryStream())
{
  var serializer = new DataContractJsonSerializer(typeof(IEnumerable<ExportData>),  new[]{typeof(ExportData)}, Int32.MaxValue, true, null, false);               
  serializer.WriteObject(stream, model);  


  var jsonString = Encoding.Default.GetString((stream.ToArray()));
}
Arek Bal
fuente
15

En este caso, si realmente desea utilizar el ReadToEndmétodo de MemoryStreamuna manera fácil, puede utilizar este Método de extensión para lograr esto:

public static class SetExtensions
{
    public static string ReadToEnd(this MemoryStream BASE)
    {
        BASE.Position = 0;
        StreamReader R = new StreamReader(BASE);
        return R.ReadToEnd();
    }
}

Y puede usar este método de esta manera:

using (MemoryStream m = new MemoryStream())
{
    //for example i want to serialize an object into MemoryStream
    //I want to use XmlSeralizer
    XmlSerializer xs = new XmlSerializer(_yourVariable.GetType());
    xs.Serialize(m, _yourVariable);

    //the easy way to use ReadToEnd method in MemoryStream
    MessageBox.Show(m.ReadToEnd());
}
Mehdi Khademloo
fuente
11

Este ejemplo muestra cómo leer una cadena de un MemoryStream, en el que he usado una serialización (usando DataContractJsonSerializer), pasar la cadena de algún servidor al cliente y luego, cómo recuperar el MemoryStream de la cadena pasada como parámetro, luego , deserialice el MemoryStream.

He usado partes de diferentes publicaciones para realizar esta muestra.

Espero que esto ayude.

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Threading;

namespace JsonSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var phones = new List<Phone>
            {
                new Phone { Type = PhoneTypes.Home, Number = "28736127" },
                new Phone { Type = PhoneTypes.Movil, Number = "842736487" }
            };
            var p = new Person { Id = 1, Name = "Person 1", BirthDate = DateTime.Now, Phones = phones };

            Console.WriteLine("New object 'Person' in the server side:");
            Console.WriteLine(string.Format("Id: {0}, Name: {1}, Birthday: {2}.", p.Id, p.Name, p.BirthDate.ToShortDateString()));
            Console.WriteLine(string.Format("Phone: {0} {1}", p.Phones[0].Type.ToString(), p.Phones[0].Number));
            Console.WriteLine(string.Format("Phone: {0} {1}", p.Phones[1].Type.ToString(), p.Phones[1].Number));

            Console.Write(Environment.NewLine);
            Thread.Sleep(2000);

            var stream1 = new MemoryStream();
            var ser = new DataContractJsonSerializer(typeof(Person));

            ser.WriteObject(stream1, p);

            stream1.Position = 0;
            StreamReader sr = new StreamReader(stream1);
            Console.Write("JSON form of Person object: ");
            Console.WriteLine(sr.ReadToEnd());

            Console.Write(Environment.NewLine);
            Thread.Sleep(2000);

            var f = GetStringFromMemoryStream(stream1);

            Console.Write(Environment.NewLine);
            Thread.Sleep(2000);

            Console.WriteLine("Passing string parameter from server to client...");

            Console.Write(Environment.NewLine);
            Thread.Sleep(2000);

            var g = GetMemoryStreamFromString(f);
            g.Position = 0;
            var ser2 = new DataContractJsonSerializer(typeof(Person));
            var p2 = (Person)ser2.ReadObject(g);

            Console.Write(Environment.NewLine);
            Thread.Sleep(2000);

            Console.WriteLine("New object 'Person' arrived to the client:");
            Console.WriteLine(string.Format("Id: {0}, Name: {1}, Birthday: {2}.", p2.Id, p2.Name, p2.BirthDate.ToShortDateString()));
            Console.WriteLine(string.Format("Phone: {0} {1}", p2.Phones[0].Type.ToString(), p2.Phones[0].Number));
            Console.WriteLine(string.Format("Phone: {0} {1}", p2.Phones[1].Type.ToString(), p2.Phones[1].Number));

            Console.Read();
        }

        private static MemoryStream GetMemoryStreamFromString(string s)
        {
            var stream = new MemoryStream();
            var sw = new StreamWriter(stream);
            sw.Write(s);
            sw.Flush();
            stream.Position = 0;
            return stream;
        }

        private static string GetStringFromMemoryStream(MemoryStream ms)
        {
            ms.Position = 0;
            using (StreamReader sr = new StreamReader(ms))
            {
                return sr.ReadToEnd();
            }
        }
    }

    [DataContract]
    internal class Person
    {
        [DataMember]
        public int Id { get; set; }
        [DataMember]
        public string Name { get; set; }
        [DataMember]
        public DateTime BirthDate { get; set; }
        [DataMember]
        public List<Phone> Phones { get; set; }
    }

    [DataContract]
    internal class Phone
    {
        [DataMember]
        public PhoneTypes Type { get; set; }
        [DataMember]
        public string Number { get; set; }
    }

    internal enum PhoneTypes
    {
        Home = 1,
        Movil = 2
    }
}
Sebastian Ferrari
fuente
5

Una versión ligeramente modificada de la respuesta de Brian permite la gestión opcional del inicio de lectura. Este parece ser el método más fácil. probablemente no sea el más eficiente, pero fácil de entender y usar.

Public Function ReadAll(ByVal memStream As MemoryStream, Optional ByVal startPos As Integer = 0) As String
    ' reset the stream or we'll get an empty string returned
    ' remember the position so we can restore it later
    Dim Pos = memStream.Position
    memStream.Position = startPos

    Dim reader As New StreamReader(memStream)
    Dim str = reader.ReadToEnd()

    ' reset the position so that subsequent writes are correct
    memStream.Position = Pos

    Return str
End Function
James
fuente
3
realmente no agrega nada nuevo a la respuesta de Brian
Luis Filipe
5

¿Por qué no hacer un buen método de extensión en el tipo MemoryStream?

public static class MemoryStreamExtensions
{

    static object streamLock = new object();

    public static void WriteLine(this MemoryStream stream, string text, bool flush)
    {
        byte[] bytes = Encoding.UTF8.GetBytes(text + Environment.NewLine);
        lock (streamLock)
        {
            stream.Write(bytes, 0, bytes.Length);
            if (flush)
            {
                stream.Flush();
            }
        }
    }

    public static void WriteLine(this MemoryStream stream, string formatString, bool flush, params string[] strings)
    {
        byte[] bytes = Encoding.UTF8.GetBytes(String.Format(formatString, strings) + Environment.NewLine);
        lock (streamLock)
        {
            stream.Write(bytes, 0, bytes.Length);
            if (flush)
            {
                stream.Flush();
            }
        }
    }

    public static void WriteToConsole(this MemoryStream stream)
    {
        lock (streamLock)
        {
            long temporary = stream.Position;
            stream.Position = 0;
            using (StreamReader reader = new StreamReader(stream, Encoding.UTF8, false, 0x1000, true))
            {
                string text = reader.ReadToEnd();
                if (!String.IsNullOrEmpty(text))
                {
                    Console.WriteLine(text);
                }
            }
            stream.Position = temporary;
        }
    }
}

Por supuesto, tenga cuidado al usar estos métodos junto con los métodos estándar. :) ... necesitará usar ese práctico streamLock si lo hace, para concurrencia.

Alexandru
fuente
0

Necesito integrarme con una clase que necesita una secuencia para escribir en ella:

XmlSchema schema;
// ... Use "schema" ...

var ret = "";

using (var ms = new MemoryStream())
{
    schema.Write(ms);
    ret = Encoding.ASCII.GetString(ms.ToArray());
}
//here you can use "ret"
// 6 Lines of code

Creo una clase simple que puede ayudar a reducir líneas de código para uso múltiple:

public static class MemoryStreamStringWrapper
{
    public static string Write(Action<MemoryStream> action)
    {
        var ret = "";
        using (var ms = new MemoryStream())
        {
            action(ms);
            ret = Encoding.ASCII.GetString(ms.ToArray());
        }

        return ret;
    }
}

entonces puede reemplazar la muestra con una sola línea de código

var ret = MemoryStreamStringWrapper.Write(schema.Write);
Riccardo Bassilichi
fuente