2011-03-10 31 views
12

tengo una cadena con varios campos separados por un carácter específico, algo como esto:C#: Separar una cadena y el resultado de asignación de múltiples variables de cadena

A, B, C

quiero para dividir la cadena en las comas y asignar cada campo resultante a su propia variable de cadena. En Perl que puedo hacer eso elegantemente como esta:

my ($varA, $varB, $varC) = split (/,/, $string); 

¿Cuál es la forma más sencilla y elegante para lograr el mismo resultado en C#?

sé que puedo dividir en una matriz:

string[] results = string.Split(','); 

Pero entonces tendría que acceder a los campos a través de su índice, por ejemplo, resultados [2]. Eso es difícil de leer y propenso a errores; considere no tener 3 campos Buth 30. Por esa razón, prefiero tener cada valor de campo en su propia variable nombrada.

+3

No hay una respuesta sobre cómo hacerlo con C#, pero F # puede hacer exactamente lo que usted quiere: 'let [| un; segundo; c |] = "1,2,3" .Split (',') ' –

+2

F # es impresionante. Ojalá fuera más popular en entornos comerciales. –

Respuesta

8

Estoy de acuerdo. Ocultación de la escisión en una clase adaptador parece un buen enfoque y se comunica su intención bastante bien:

public class MySplitter 
{ 
    public MySplitter(string split) 
    { 
     var results = string.Split(','); 
     NamedPartA = results[0]; 
     NamedpartB = results[1]; 
    } 

    public string NamedPartA { get; private set; } 
    public string NamedPartB { get; private set; } 
} 
+0

¿Por qué no usar un Tuple? – Linkgoron

+1

Si entiendo su respuesta correctamente, se requiere una clase por escenario de uso de Split porque la cantidad de resultados se corrige así como los nombres. Quiero que los nombres sean flexibles. –

+0

Yo diría que requiere una instancia por tipo para el que desee tener nombres. Es una pequeña porción de código que hace que el código del consumidor sea más fácil de leer e interpretar. –

0

No hay está construido en forma de C# para hacer una asignación múltiple como se hace en Perl; sin embargo, existen múltiples soluciones para obtener los datos en cada variable a través de una ruta normal.

+0

Bueno, ¿cuál es la forma más elegante de obtener los datos en cada variable? –

+0

En mi opinión, Rich Melton (arriba) publicó una buena solución: abstrae la 'basura' (la división) detrás de un objeto complejo y luego nombra las variables de salida resultantes en propiedades. – Tejs

1

Recomiendo un diccionario.

List<string> names = new List<string>(){"varA","varB","varC"}; 
Dictionary<string, string> map = new Dictionary<string, string>(); 
string[] results = myLongString.Split(','); 
for (int i = 0; i < results.Length; i++) map.Add(names[i], results[i]); 
+0

Eso me parece exagerado y no es muy fácil de leer (5 líneas en lugar de 1). –

7

Puede usar Tuples (agregado en .Net 4). Tuples in MSDN

Este:

public class MySplitter 
{ 
    public MySplitter(string split) 
    { 
     var results = split.Split(','); 
     NamedPartA = results[0]; 
     NamedpartB = results[1]; 
    } 

    public string NamedPartA { get; private set; } 
    public string NamedPartB { get; private set; } 
} 

se podría lograr con algo como esto:

public Tuple<string,string> SplitIntoVars(string toSplit) 
{ 
    string[] split = toSplit.Split(','); 
    return Tuple.Create(split[0],split[1]); 
} 

Con una tupla que puede utilizar:

var x = SplitIntoVars(arr); 
// you can access the items like this: x.Item1 or x.Item2 (and x.Item3 etc.) 

También puede crear una tupla de usando Tuple<string,int> etc.

Además ... Realmente no me gustan los parámetros, por lo que emula la devolución de múltiples valores usando a Tuple (y obviamente, también de diferentes tipos). esto:

public void SplitIntoVariables(string input, out a, out b, out c) 
{ 
    string pieces[] = input.Split(','); 
    a = pieces[0]; 
    b = pieces[1]; 
    c = pieces[2]; 
} 

se convierte en esto:

public Tuple<string,string,string> SplitIntoVariables(string[] input) 
    { 
     string pieces[] = input.Split(','); 
     return Tuple.Create(pieces[0],pieces[1],pieces[2]); 
    } 

Otros (más imaginativas) opciones podrían ser la creación de un ExpandoObject (dinámico) que mantiene sus valores (algo parecido a ViewBag en ASP.NET MVC)

+1

Interesante. No sabía sobre las tuplas antes. –

+0

Una de las razones para no sugerir un Tuple es el hecho de que el resultado [0] no es mucho más claro que tuple.Item1, Item2, etc. .... El OP explícitamente declaró un deseo por los valores con nombre. –

+0

Creo que sigue siendo mejor que las matrices, ya que no utiliza exactamente los índices, y no se recrean las estructuras que existen en el FW. De cualquier manera, creo que la mejor solución probablemente involucre una solución más cercana al problema real, y no algo tan general como la pregunta actual.IMO, la solución más elegante (si no puede crear una buena clase concreta) sería algo así como un diccionario dinámico (como el ViewBag). – Linkgoron

3

¡Y quién no puede resistirse a la locura de Linq!

string names = "Joe,Bob,Lucy"; 
var myVars = names.Split(',').Select((x, index) => Tuple.Create(index,x)).ToDictionary(x => "var" + x.Item1, y => y.Item2); 
Debug.WriteLine(myVars["var0"]); 
+1

Sooo ... ¿Tienes un Diccionario [var + Índice] de cadenas en lugar de una matriz de cadenas [Índice]? – Linkgoron

+3

Bueno, es una locura ... – gt124

2

No es una solución de una línea.Pero, ¿qué tal un método de extensión con un parámetro adicional out?

public static IEnumerable<T> Unpack<T>(this IEnumerable<T> source, out T target) 
{ 
    target = source.First(); 
    return source.Skip(1); 
} 

Entonces, podría usar el método de esta manera.

string values = "aaa,bbb,ccc"; 
string x, y, z; 
values.Split(',') 
    .Unpack(out x) 
    .Unpack(out y) 
    .Unpack(out z); 

Tenga en cuenta que el método Unpack enumera la secuencia dos veces. Entonces, lo usaría solo si los datos en el objeto IEnumerable son repetibles.

No me preocupé por comprobar el rendimiento del método porque pensé que normalmente no desempacaríamos una gran matriz.

Por supuesto, podría usar el modificador ref en lugar de out, y el uso sería diferente.

0

No pude resistir la ridiculez :) No use esta "solución", al menos no como está.

static class StringExtensions 
{ 
    private static readonly Func<object, string, Action<object, object>> GetSetter = 
     (o1, n) => (o2, v) => o1.GetType().GetProperty(n).SetValue(o2, v, null); 

    public static T AssignFromCSV<T>(this string csv, T obj, string[] propertyNames) 
    { 
     var a = csv.Split(new[] {','}); 
     for (var i = 0 ; i < propertyNames.Length; i++) 
     { 
      GetSetter(obj, propertyNames[i])(obj, a[i]); 
     } 
     return obj ; 
    } 
} 

class TargetClass 
{ 
    public string A { get; set; } 

    public string B { get; set; } 

    public string C { get; set; } 
} 

Uso:

var target = "1,2,3".AssignFromCSV(new TargetClass(), new[] {"A", "B", "C"}) ; 
1

@pnvn tiene una gran idea con el patrón Desempaquetar, pero podría ser mejorado para repetir la enumeración de una sola pasada, proporcionan valores por defecto más allá del final de la enumerables y trabajar en cualquier tipo o tamaño de enumerable de una manera predecible.

Aquí hay un ejemplo usando C# 7 out variables feature.

"A,B,C".Split(',') 
    .Unpack(out string varA) 
    .Unpack(out string varB); 

Esto requiere dos métodos de extensión, uno para el IEnumerable para empezar, el segundo en el IEnumerator para cada llamada posterior.

public static IEnumerator<T> Unpack<T>(this IEnumerable<T> source, out T item) 
{ 
    var enumerator = source.GetEnumerator(); 
    if (enumerator.MoveNext()) 
    { 
     item = enumerator.Current; 
     return enumerator; 
    } 
    item = default(T); 
    return null; 
} 

public static IEnumerator<T> Unpack<T>(this IEnumerator<T> enumerator, out T item) 
{ 
    if (enumerator != null && enumerator.MoveNext()) 
    { 
     item = enumerator.Current; 
     return enumerator; 
    }    
    item = default(T); 
    return null; 
} 
Cuestiones relacionadas