Comment générer un flux à partir d'une chaîne?

759

J'ai besoin d'écrire un test unitaire pour une méthode qui prend un flux provenant d'un fichier texte. Je voudrais faire quelque chose comme ça:

Stream s = GenerateStreamFromString("a,b \n c,d");
Omu
la source
Pour une solution d'économie de mémoire, voir StringReaderStreamdans stackoverflow.com/a/55170901/254109
xmedeko

Réponses:

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;
}

N'oubliez pas d'utiliser Using:

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

À propos de StreamWriterne pas être disposé. StreamWriterest juste un wrapper autour du flux de base et n'utilise aucune ressource devant être supprimée. La Disposeméthode fermera le sous Stream- jacent qui StreamWriterécrit. Dans ce cas, c'est le que MemoryStreamnous voulons retourner.

Dans .NET 4.5, il existe maintenant une surcharge StreamWriterqui maintient le flux sous-jacent ouvert après la suppression du programme d'écriture, mais ce code fait la même chose et fonctionne également avec d'autres versions de .NET.

Voir Existe - t-il un moyen de fermer un StreamWriter sans fermer son BaseStream?

Cameron MacFarland
la source
134
Un concept de point important à souligner est qu'un flux est composé d'octets, tandis qu'une chaîne est composée de caractères. Il est crucial de comprendre que la conversion d'un caractère en un ou plusieurs octets (ou en un Stream comme dans ce cas) utilise toujours (ou suppose) un encodage particulier. Cette réponse, bien que correcte dans certains cas, utilise l'encodage par défaut et peut ne pas convenir en général. Passer explicitement un codage au constructeur StreamWriter rendrait plus évident que l'auteur doit prendre en compte les implications du codage.
drwatsoncode
6
Vous dites "N'oubliez pas d'utiliser l'Utilisation" pour utiliser le flux, mais dans votre GenerateStreamFromStringméthode, vous n'utilisez pas l'Utilisation avec le StreamWriter. Y a-t-il une raison à cela?
Ben
12
@Ben Oui. Si vous disposez du StreamWriter, le flux sous-jacent sera également fermé. Nous n'en voulons pas. La seule raison pour laquelle Writer est jetable est de nettoyer le flux, il est donc sûr de l'ignorer.
Cameron MacFarland
2
Il convient également de noter que la chaîne entière est copiée dans une mémoire qui peut être importante pour les chaînes de grande taille car nous avons maintenant une copie supplémentaire dans la mémoire.
UGEEN
1
@ahong Pas vraiment. StreamWriterfait probablement de toute façon ce que vous avez dit en interne. L'avantage est l'encapsulation et le code plus simple, mais au prix d'abstraction de choses comme l'encodage. Cela dépend de ce que vous essayez de réaliser.
Cameron MacFarland
724

Une autre solution:

public static MemoryStream GenerateStreamFromString(string value)
{
    return new MemoryStream(Encoding.UTF8.GetBytes(value ?? ""));
}
joelnet
la source
31
Juste au cas où quelqu'un l'utilise avec une désérialisation de chaîne XML, j'ai dû basculer UTF8 vers Unicode pour que cela fonctionne sans indicateur. Super article!!!
Gaspa79
2
J'aime celui-ci (avec le tweak de Rhyous et le sucre supplémentaire trivial à utiliser comme méthode d'extension) mieux que la réponse acceptée; plus flexible, moins de LOC et moins d'objets impliqués (pas besoin explicite de StreamWriter)
KeithS
2
new MemoryStream(Encoding.UTF8.GetBytes("\ufeff" + (value ?? ""))si vous devez inclure la nomenclature au début du flux
robert4
5
C'est une syntaxe très compacte mais cela va provoquer beaucoup d'allocations d'octets [] donc méfiez-vous du code haute performance.
michael.aird
1
Cette solution laissait encore la possibilité de rendre le flux en lecture seule. new MemoryStream( value, false ). Vous ne pouvez pas créer un flux en lecture seule si vous devez l'écrire avec un rédacteur de flux.
codekandis
106

Ajoutez ceci à une classe utilitaire de chaîne statique:

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;
}

Cela ajoute une fonction d'extension pour que vous puissiez simplement:

using (var stringStream = "My string".ToStream())
{
    // use stringStream
}
Josh G
la source
5
J'ai découvert que le flux renvoyé est fermé (provoquant des exceptions semi-aléatoires) lorsque le garbage collector nettoie le StreamWriter. Le correctif consistait à utiliser un constructeur différent - celui qui me permettait de spécifier LeaveOpen .
Bevan
45
public Stream GenerateStreamFromString(string s)
{
    return new MemoryStream(Encoding.UTF8.GetBytes(s));
}
démoniste
la source
24

Utilisez la MemoryStreamclasse en appelant Encoding.GetBytespour transformer d'abord votre chaîne en un tableau d'octets.

Avez-vous besoin par la suite d'un TextReadersur le flux? Si tel est le cas, vous pouvez fournir StringReaderdirectement un et contourner les étapes MemoryStreamet Encoding.

Tim Robinson
la source
23

J'ai utilisé un mélange de réponses comme celle-ci:

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

Et puis je l'utilise comme ceci:

String someStr="This is a Test";
Encoding enc = getEncodingFromSomeWhere();
using (Stream stream = someStr.ToStream(enc))
{
    // Do something with the stream....
}
Robocide
la source
Thomas, pourquoi voter contre? enc = enc ?? Encoding.UTF8 me permet de demander spécifiquement un flux avec un encodage spécifique, ou une valeur par défaut UTF8, et parce que dans .net (pour autant que je l'utilise .net 4.0), vous ne pouvez pas donner à un type de référence autre que chaîne une valeur par défaut dans la fonction la signature de cette ligne est nécessaire, est-ce que cela a du sens?
Robocide du
mentionner que vous devez mettre cela dans une classe séparée (classe statique non générique?) est également utile et réduire les votes négatifs.
Ali
13

Nous utilisons les méthodes d'extension listées ci-dessous. Je pense que vous devriez obliger le développeur à prendre une décision concernant l'encodage, donc il y a moins de magie impliquée.

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
la source
1
Je préférerais implémenter la première méthode comme return ToStream(s, Encoding.UTF8);. Dans l'implémentation actuelle ( return s.ToStream(Encoding.UTF8);, le développeur est obligé de réfléchir davantage pour saisir le code et il semble que le cas de s == nullsoit non géré et lance NullReferenceException.
Palec
10

Voici:

private Stream GenerateStreamFromString(String p)
{
    Byte[] bytes = UTF8Encoding.GetBytes(p);
    MemoryStream strm = new MemoryStream();
    strm.Write(bytes, 0, bytes.Length);
    return strm;
}
cjk
la source
1
La position doit être réinitialisée après l'écriture. Mieux vaut utiliser le constructeur, comme dans la réponse de joelnet.
Jim Balter
10

Version modernisée et légèrement modifiée des méthodes d'extension pour 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));

Modification suggérée dans le commentaire @ Palec de la réponse @Shaun Bowe.

Nick N.
la source
8

Je pense que vous pouvez bénéficier de l'utilisation d'un MemoryStream . Vous pouvez le remplir avec les octets de chaîne que vous obtenez en utilisant la méthode GetBytes de la classe Encoding .

Konamiman
la source
4

Si vous devez changer l'encodage, je vote pour la solution de @ShaunBowe . Mais chaque réponse ici copie la chaîne entière en mémoire au moins une fois. Les réponses avec ToCharArray+ BlockCopycombo le font deux fois.

Si cela importe, voici un simple Streamwrapper pour la chaîne UTF-16 brute. Si utilisé avec une StreamReadersélection Encoding.Unicodepour cela:

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; // ;)     
}

Et voici une solution plus complète avec les vérifications liées nécessaires (dérivées de MemoryStreamce qu'il a ToArrayet des WriteTométhodes également).

György Kőszeg
la source
-2

Une bonne combinaison d'extensions de chaînes:

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
la source