C#: Split string and assign result to multiple string variables

20,781

Solution 1

I agree. Hiding the split in an Adapter class seems like a good approach and communicates your intent rather well:

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

Solution 2

You can use Tuples (added in .Net 4). Tuples in MSDN

This:

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

Could be achieved with something like this:

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

With a Tuple you can use:

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

You can also create a Tuple for using Tuple<string,int> etc.

Also... I don't really like out parameters, so you emulate returning multiple values using a Tuple (and obviously, also of varying types). this:

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

turns into this:

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

Other (more imaginative) options could be creating an ExpandoObject (dynamic) that holds your values (something akin to ViewBag in ASP.NET MVC)

Solution 3

And who can't resist some Linq insanity!

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"]);

Solution 4

@pnvn has a great idea with the Unpack pattern, but it could be improved to iterate over the enumeration in a single pass, provide defaults past the end of the enumerable and work on any type or size of enumerable in a predictable way.

Here is an example using C# 7 out variables feature.

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

This requires two extension methods, one for the IEnumerable to start, the second on the IEnumerator for each subsequent call.

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

Solution 5

Or if you just want to avoid the extra variable name for readability sake, you could do something like that (C#7+):

public static void Deconstruct<T>(this IList<T> self, out T v1, out T v2) {
    v1 = self.Count > 0 ? self[0] : default;
    v2 = self.Count > 1 ? self[1] : default;
}

On another static class where you write your extension methods. And then use it like:

var (a, b) = "1,2".Split(','); // a = "1"; b = "2";

Obviously, this can be extended for more than two variables, but unfortunately, you have to write another method as far as I know.

For example:

public static class Ex {
    public static void Deconstruct<T>(this IList<T> self, out T v1, out T v2) {
        v1 = self.Count > 0 ? self[0] : default;
        v2 = self.Count > 1 ? self[1] : default;
    }

    public static void Deconstruct<T>(this IList<T> self, out T v1, out T v2, out T v3) {
        v1 = self.Count > 0 ? self[0] : default;
        v2 = self.Count > 1 ? self[1] : default;
        v3 = self.Count > 2 ? self[2] : default;
    }

    public static void Deconstruct<T>(this IList<T> self, out T v1, out T v2, out T v3, out T v4) {
        v1 = self.Count > 0 ? self[0] : default;
        v2 = self.Count > 1 ? self[1] : default;
        v3 = self.Count > 2 ? self[2] : default;
        v4 = self.Count > 3 ? self[3] : default;
    }
}

And use it like:

var (a,_,_,d) = "1a,2b,3c,4d".Split(','); // a = "1a"; d = "4d";

As a side effect, now you can deconstruct any array.

var (first,second) = new [] { 1,2 }; // first = 1; second = 2;
Share:
20,781

Related videos on Youtube

Helge Klein
Author by

Helge Klein

Author of uberAgent for Splunk (user experience and application performance monitoring), Delprof2 (user profile deletion), SetACL and SetACL Studio (permissions management).

Updated on July 09, 2022

Comments

  • Helge Klein
    Helge Klein almost 2 years

    I have a string with several fields separated by a specific character, something like this:

    A,B,C

    I want to split the string at the commas and assign each resulting field to its own string variable. In Perl I can do that elegantly like this:

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

    What is the simplest and most elegant way to achieve the same result in C#?

    I know that I can split into an array:

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

    But then I would have to access the fields via their index, e.g. results[2]. That is difficult to read and error-prone - consider not having 3 buth 30 fields. For that reason I prefer having each field value in its own named variable.

    • Joel Mueller
      Joel Mueller about 13 years
      Not an answer on how to do it with C#, but F# can do pretty much exactly what you want: let [| a; b; c |] = "1,2,3".Split(',')
    • Ritch Melton
      Ritch Melton about 13 years
      F# is awesome. I wish it were more popular in commercial settings.
  • Helge Klein
    Helge Klein about 13 years
    If I understand your answer correctly, it requires one class per usage scenario of Split because the number of results are fixed as well as the names. I want the names to be flexible.
  • Helge Klein
    Helge Klein about 13 years
    Well, what is the most elegant way to get the data into each variable?
  • Tejs
    Tejs about 13 years
    In my opinion, Rich Melton (above) posted a good solution - it abstracts the 'crap' (the splitting) behind a complex object and then names the resulting output variables into properties.
  • Ritch Melton
    Ritch Melton about 13 years
    I'd say it requires one instance per type that you want to have names for. It's a minor bit of code that makes the consumer code easier to read and interpret.
  • Helge Klein
    Helge Klein about 13 years
    Interesting. Did not know about tuples before.
  • Linkgoron
    Linkgoron about 13 years
    Sooo... Youve got a Dictionary[var+Index] of strings instead of an string array[Index]?
  • Ritch Melton
    Ritch Melton about 13 years
    One of the reason for not suggesting a Tuple is the fact that result[0] isn't much clearer than tuple.Item1, Item2, etc.... The OP explicitly stated a desire for named values.
  • Linkgoron
    Linkgoron about 13 years
    I think it's still better than arrays, as it's not exactly using indexes, and you don't recreate structures that exist in the FW. Either way, I think the best solution would probably involve a solution that's closer to the actual problem, and not something so general as the current question. IMO the most elegant solution (if you cant create a good concrete class) would be something such as a Dynamic Dictionary (like the ViewBag).
  • Ritch Melton
    Ritch Melton about 13 years
    I agree. Other languages have much better answers to this sort of problem (Perl, OCaml, F#). For C#, my approach is the style I'd use. Obviously I'm biased ;)
  • Brian Genisio
    Brian Genisio over 12 years
    Except that I'd expect MySplitter to be something that stores the result named parts. A splitter should just split and return the split parts. Instead, I might consider something more semantic: Person that has First and Last and a static method called parse that returns a new instance of Person. person.First would make more sense to me than personSplitter.First

Related