Le moyen le plus simple de fractionner une chaîne sur des sauts de ligne dans .NET?

806

J'ai besoin de diviser une chaîne en sauts de ligne dans .NET et la seule façon que je sache de diviser les chaînes est avec la méthode Split . Cependant, cela ne me permettra pas (facilement) de diviser sur une nouvelle ligne, alors quelle est la meilleure façon de le faire?

RCIX
la source
2
Pourquoi pas? Juste partagé sur System.Environment.NewLine
aviraldg
16
Mais vous devez l'envelopper dans une chaîne [] et ajouter un argument supplémentaire et ... cela semble juste maladroit.
RCIX

Réponses:

1414

Pour diviser sur une chaîne, vous devez utiliser la surcharge qui prend un tableau de chaînes:

string[] lines = theText.Split(
    new[] { Environment.NewLine },
    StringSplitOptions.None
);

Modifier:
si vous souhaitez gérer différents types de sauts de ligne dans un texte, vous pouvez utiliser la possibilité de faire correspondre plusieurs chaînes. Cela se divisera correctement sur chaque type de saut de ligne et préservera les lignes vides et l'espacement dans le texte:

string[] lines = theText.Split(
    new[] { "\r\n", "\r", "\n" },
    StringSplitOptions.None
);
Guffa
la source
3
@RCIX: L'envoi des paramètres corrects à la méthode est un peu gênant car vous l'utilisez pour quelque chose de beaucoup plus simple que ce dont il est capable. Au moins, c'est là, avant le framework 2, vous deviez utiliser une expression régulière ou créer votre propre routine de fractionnement pour diviser sur une chaîne ...
Guffa
4
@Leandro: La Environment.NewLinepropriété contient la nouvelle ligne par défaut du système. Pour un système Windows par exemple ça le sera "\r\n".
Guffa
3
@Leandro: Une supposition serait que le programme se divise en \nlaissant un \rà la fin de chaque ligne, puis sort les lignes avec un \r\nentre elles.
Guffa
3
@Samuel: Les séquences d'échappement \ret \n(entre autres) ont une signification particulière pour le compilateur C #. VB n'a pas ces séquences d'échappement, donc ces constantes sont utilisées à la place.
Guffa
2
Si vous souhaitez accepter des fichiers provenant de nombreux OS différents, vous pouvez également ajouter "\ n \ r" au début et "\ r" à la fin de la liste des délimiteurs. Je ne suis pas sûr que cela en vaille la peine. ( en.wikipedia.org/wiki/Newline )
user420667
121

Et si vous utilisiez un StringReader?

using (System.IO.StringReader reader = new System.IO.StringReader(input)) {
    string line = reader.ReadLine();
}
Clément
la source
13
C'est mon préféré. J'ai enveloppé dans une méthode d'extension et produit la ligne de retour de rendement: gist.github.com/ronnieoverby/7916886
Ronnie Overby
3
Ceci est la seule solution non regex que j'ai trouvée pour .netcf 3.5
Carl
8
Particulièrement agréable lorsque l'entrée est volumineuse et que la copie dans un tableau devient lente / gourmande en mémoire.
Alejandro
1
Comme écrit, cette réponse ne lit que la première ligne. Voir la réponse de Steve Cooper pour la whileboucle qui devrait être ajoutée à cette réponse.
ToolmakerSteve
48

Vous devriez pouvoir diviser votre chaîne assez facilement, comme ceci:

aString.Split(Environment.NewLine.ToCharArray());
nikmd23
la source
46
Sur un système non * nix qui se divisera sur les caractères séparés dans la chaîne Newline, c'est-à-dire les caractères CR et LF. Cela provoquera une chaîne vide supplémentaire entre chaque ligne.
Guffa
Corrigez-moi si je me trompe, mais cela ne se divisera-t-il pas sur les caractères \ et n?
RCIX
7
@RCIX: Non, les codes \ r et \ n représentent des caractères uniques. La chaîne "\ r \ n" est composée de deux caractères, pas de quatre.
Guffa
10
si vous ajoutez le paramètre StringSplitOptions.RemoveEmptyEntries, cela fonctionnera parfaitement.
Ruben
18
@Ruben: Non, ce ne sera pas le cas. Serge l'a déjà suggéré dans sa réponse, et j'ai déjà expliqué que cela supprimera également les lignes vides du texte d'origine qui devraient être conservées.
Guffa
34

Essayez d'éviter d'utiliser string.Split pour une solution générale, car vous utiliserez plus de mémoire partout où vous utilisez la fonction - la chaîne d'origine et la copie fractionnée, toutes deux en mémoire. Croyez-moi, cela peut être un sacré problème lorsque vous commencez à évoluer - exécutez une application de traitement par lots 32 bits traitant des documents de 100 Mo, et vous vous retrouverez à huit threads simultanés. Non pas que j'y ai été avant ...

Utilisez plutôt un itérateur comme celui-ci;

    public static IEnumerable<string> SplitToLines(this string input)
    {
        if (input == null)
        {
            yield break;
        }

        using (System.IO.StringReader reader = new System.IO.StringReader(input))
        {
            string line;
            while( (line = reader.ReadLine()) != null)
            {
                yield return line;
            }
        }
    }

Cela vous permettra de faire une boucle plus efficace en mémoire autour de vos données;

foreach(var line in document.SplitToLines()) 
{
    // one line at a time...
}

Bien sûr, si vous voulez tout garder en mémoire, vous pouvez le faire;

var allTheLines = document.SplitToLines.ToArray();
Steve Cooper
la source
J'y suis allé (analyse de gros fichiers HTML et manque de mémoire). Oui, évitez les chaînes. L'utilisation de string.Split peut entraîner l'utilisation du tas d'objets volumineux (LOH) - mais je n'en suis pas sûr à 100%.
Peter Mortensen
Si vous avez fait de SplitToLines une méthode statique (ce que vous semblez dd), comment pouvez-vous faire blah.SplitToLines.. par exemple document.SplitToLines...?
barlop le
ah je vois que vous mettez thisdans les paramètres formels ce qui en fait une méthode d'extension.
barlop
26

Sur la base de la réponse de Guffa, dans une classe d'extension, utilisez:

public static string[] Lines(this string source) {
    return source.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None);
}
Erwin Mayer
la source
9

Pour une variable chaîne s:

s.Split(new string[]{Environment.NewLine},StringSplitOptions.None)

Cela utilise la définition des fins de ligne de votre environnement. Sous Windows, les fins de ligne sont CR-LF (retour chariot, saut de ligne) ou en caractères d'échappement C # \r\n.

C'est une solution fiable, car si vous recombinez les lignes avec String.Join, cela équivaut à votre chaîne d'origine:

var lines = s.Split(new string[]{Environment.NewLine},StringSplitOptions.None);
var reconstituted = String.Join(Environment.NewLine,lines);
Debug.Assert(s==reconstituted);

Ce qu'il ne faut pas faire:

  • Utilisez StringSplitOptions.RemoveEmptyEntries, car cela rompra le balisage tel que Markdown où les lignes vides ont un objectif syntaxique.
  • Fractionner sur le séparateur new char[]{Environment.NewLine}, car sous Windows, cela créera un élément de chaîne vide pour chaque nouvelle ligne.
Colonel Panic
la source
Fondamentalement, la même réponse ici que la meilleure note, acceptée, mais elle a un bon test unitaire et des mises en garde.
vapcguy
8

Regex est également une option:

    private string[] SplitStringByLineFeed(string inpString)
    {
        string[] locResult = Regex.Split(inpString, "[\r\n]+");
        return locResult;
    }
user1964822
la source
7
Si vous voulez faire correspondre les lignes exactement, en conservant des lignes vides, cette chaîne regex serait mieux: "\r?\n".
Rory O'Kane
7

Je pensais simplement que j'ajouterais mes deux bits, car les autres solutions sur cette question ne tombent pas dans la classification de code réutilisable et ne sont pas pratiques.

Le bloc de code suivant étend l' stringobjet afin qu'il soit disponible comme méthode naturelle lorsque vous travaillez avec des chaînes.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections;
using System.Collections.ObjectModel;

namespace System
{
    public static class StringExtensions
    {
        public static string[] Split(this string s, string delimiter, StringSplitOptions options = StringSplitOptions.None)
        {
            return s.Split(new string[] { delimiter }, options);
        }
    }
}

Vous pouvez maintenant utiliser la .Split()fonction à partir de n'importe quelle chaîne comme suit:

string[] result;

// Pass a string, and the delimiter
result = string.Split("My simple string", " ");

// Split an existing string by delimiter only
string foo = "my - string - i - want - split";
result = foo.Split("-");

// You can even pass the split options parameter. When omitted it is
// set to StringSplitOptions.None
result = foo.Split("-", StringSplitOptions.RemoveEmptyEntries);

Pour diviser un caractère de nouvelle ligne, passez simplement "\n"ou "\r\n"comme paramètre de délimiteur.

Commentaire: Ce serait bien si Microsoft implémentait cette surcharge.

Kraang Prime
la source
Environment.Newlineest préférée pour coder en dur soit \nou \r\n.
Michael Blackburn
3
@MichaelBlackburn - C'est une déclaration invalide car il n'y a pas de contexte. Environment.Newlineest pour la compatibilité multiplateforme, pas pour travailler avec des fichiers utilisant des terminaisons de ligne différentes de celles du système d'exploitation actuel. Voir ici pour plus d'informations , donc cela dépend vraiment de ce avec quoi le développeur travaille. L'utilisation de Environment.Newlineassure qu'il n'y a pas de cohérence dans le type de retour de ligne entre les systèmes d'exploitation, où le «codage en dur» donne au développeur un contrôle total.
Kraang Prime
2
@MichaelBlackburn - Il n'est pas nécessaire que vous soyez impoli. Je fournissais simplement les informations. .Newlinen'est pas magique, sous le capot, ce sont juste les cordes comme indiqué ci-dessus, basées sur un commutateur si il fonctionne sur unix ou sur windows. Le pari le plus sûr est de faire d'abord un remplacement de chaîne pour tous les "\ r \ n", puis de diviser sur "\ n". Lorsque l'utilisation .Newlineéchoue, c'est lorsque vous travaillez avec des fichiers enregistrés par d'autres programmes qui utilisent une méthode différente pour les sauts de ligne. Cela fonctionne bien si vous savez que chaque fois que le fichier lu utilise toujours les sauts de ligne de votre système d'exploitation actuel.
Kraang Prime
Donc, ce que j'entends, c'est la façon la plus lisible (peut-être une utilisation de mémoire plus élevée) foo = foo.Replace("\r\n", "\n"); string[] result = foo.Split('\n');. Ai-je bien compris que cela fonctionne sur toutes les plateformes?
John Doe
4

J'utilise actuellement cette fonction (basée sur d'autres réponses) dans VB.NET:

Private Shared Function SplitLines(text As String) As String()
    Return text.Split({Environment.NewLine, vbCrLf, vbLf}, StringSplitOptions.None)
End Function

Il essaie d'abord de se diviser sur la nouvelle ligne locale de la plate-forme, puis revient à chaque nouvelle ligne possible.

Pour l'instant, je n'en ai eu besoin que dans une seule classe. Si cela change, je ferai probablement ceciPublic et le déplacer vers une classe utilitaire, et peut-être même en faire une méthode d'extension.

Voici comment joindre les lignes de sauvegarde, pour faire bonne mesure:

Private Shared Function JoinLines(lines As IEnumerable(Of String)) As String
    Return String.Join(Environment.NewLine, lines)
End Function
Rory O'Kane
la source
@Samuel - notez les citations. Ils ont en fait ce sens. "\r"= retour. "\r\n"= retour + nouvelle ligne. (veuillez consulter cet article et la solution acceptée ici
Kraang Prime
@Kraang Hmm .. Je n'ai pas travaillé avec .NET depuis longtemps. Je serais surpris si autant de personnes votaient une mauvaise réponse. Je vois que j'ai également commenté la réponse de Guffa et obtenu des éclaircissements. J'ai supprimé mon commentaire sur cette réponse. Merci pour l'information.
Samuel
2

Eh bien, en fait, le split devrait faire:

//Constructing string...
StringBuilder sb = new StringBuilder();
sb.AppendLine("first line");
sb.AppendLine("second line");
sb.AppendLine("third line");
string s = sb.ToString();
Console.WriteLine(s);

//Splitting multiline string into separate lines
string[] splitted = s.Split(new string[] {System.Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries);

// Output (separate lines)
for( int i = 0; i < splitted.Count(); i++ )
{
    Console.WriteLine("{0}: {1}", i, splitted[i]);
}
MaciekTalaska
la source
2
L'option RemoveEmptyEntries supprimera les lignes vides du texte. Cela peut être souhaitable dans certaines situations, mais un partage simple devrait conserver les lignes vides.
Guffa
oui, vous avez raison, je viens de faire cette supposition, que ... eh bien, les lignes vides ne sont pas intéressantes;)
MaciekTalaska
1
string[] lines = text.Split(
  Environment.NewLine.ToCharArray(), 
  StringSplitOptions.RemoveEmptyStrings);

L' option RemoveEmptyStrings s'assurera que vous n'avez pas d'entrées vides car \ n suit un \ r

(Modifier pour refléter les commentaires :) Notez qu'il supprimera également les véritables lignes vides dans le texte. C'est généralement ce que je veux, mais ce n'est peut-être pas votre exigence.

Serge Wautier
la source
Les options RemoveEmptyStrings suppriment également les lignes vides, donc cela ne fonctionne pas correctement si le texte contient des lignes vides.
Guffa
Vous voulez probablement conserver des lignes vides authentiques: \ r \ n \ r \ n
slim
0

Je ne connaissais pas Environment.Newline, mais je suppose que c'est une très bonne solution.

Mon essai aurait été:

        string str = "Test Me\r\nTest Me\nTest Me";
        var splitted = str.Split('\n').Select(s => s.Trim()).ToArray();

Le .Trim supplémentaire supprime tous les \ r ou \ n qui pourraient être encore présents (par exemple, sur Windows mais en divisant une chaîne avec des caractères de nouvelle ligne os x). Ce n'est probablement pas la méthode la plus rapide.

ÉDITER:

Comme les commentaires l'ont correctement souligné, cela supprime également tout espace au début de la ligne ou avant le nouveau saut de ligne. Si vous devez conserver cet espace, utilisez l'une des autres options.

Max
la source
Le Trim supprimera également tout espace blanc au début et à la fin des lignes, par exemple l'indentation.
Guffa
".Trim supprime tout \ r ou \ n qui pourrait être encore présent" - aïe. Pourquoi ne pas écrire du code robuste à la place?
bzlm
Peut-être que je me suis trompé de question, mais il n'était pas clair que cet espace devait être préservé. Bien sûr, vous avez raison, Trim () supprime également les espaces.
Max
1
@Max: Wow, attendez de dire à mon patron que le code est autorisé à faire tout ce qui n'est pas spécifiquement exclu dans les spécifications ...;)
Guffa
-2

Réponse idiote: écrivez dans un fichier temporaire pour pouvoir utiliser le vénérable File.ReadLines

var s = "Hello\r\nWorld";
var path = Path.GetTempFileName();
using (var writer = new StreamWriter(path))
{
    writer.Write(s);
}
var lines = File.ReadLines(path);
Colonel Panic
la source
1
Évitez var, car il ne définit pas le type de variable, de sorte que vous ne pouvez pas comprendre comment utiliser cet objet, ou ce que cet objet représente. De plus, cela montre l'écriture des lignes et ne spécifie même pas de nom de fichier, donc je doute que cela fonctionnerait. Ensuite, lors de la lecture, le chemin d'accès au fichier n'est à nouveau pas spécifié. En supposant que pathc'est le cas C:\Temp\test.txt, vous devriez alors l'avoir string[] lines = File.ReadLines(path);.
vapcguy
1
@vapcguy qu'est-ce que je viens de lire? - Je recommanderais de relire le post ou de le déboguer dans un programme console car tout ce que vous avez dit est tout simplement faux | chemin est défini sur Path.GetTempFileName | var est une définition courante et recommandée en C # - par la façon dont elle définit le type d'une variable ...... EDIT: Je ne dis pas que c'est une bonne solution
koanbock
@koanbock Ok, j'ai donc recherché Path.GetTempFileName msdn.microsoft.com/en-us/library/… et il dit qu'il crée un fichier de zéro octet et renvoie "le chemin complet de ce fichier". Je pourrais jurer que j'ai essayé avant et cela a donné une exception car il n'a pas trouvé de fichier, mais a été renvoyé à la place. Je connais les arguments pour l'utilisation var, mais je dirais que ce n'est PAS recommandé car il ne montre pas ce qu'est l'objet variable. Il l'obscurcit.
vapcguy
-3
using System.IO;

string textToSplit;

if (textToSplit != null)
{
    List<string> lines = new List<string>();
    using (StringReader reader = new StringReader(textToSplit))
    {
        for (string line = reader.ReadLine(); line != null; line = reader.ReadLine())
        {
            lines.Add(line);
        }
    }
}
maciej
la source
-5

Très facile, en fait.

VB.NET:

Private Function SplitOnNewLine(input as String) As String
    Return input.Split(Environment.NewLine)
End Function

C #:

string splitOnNewLine(string input)
{
    return input.split(environment.newline);
}
Skillaura13
la source
4
Totalement incorrect et ne fonctionne pas. De plus, en C #, c'est Environment.NewLinecomme en VB.
vapcguy
Voir Identifiant de fin de ligne dans VB.NET? pour les différentes options de nouvelle ligne.
Peter Mortensen