14

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.

2
  • 4
    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(',') Commented Mar 10, 2011 at 23:31
  • 2
    F# is awesome. I wish it were more popular in commercial settings. Commented Mar 11, 2011 at 2:02

8 Answers 8

9

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; }
}
Sign up to request clarification or add additional context in comments.

3 Comments

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.
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.
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
7

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)

4 Comments

Interesting. Did not know about tuples before.
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.
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).
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 ;)
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"]);

1 Comment

Sooo... Youve got a Dictionary[var+Index] of strings instead of an string array[Index]?
3

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

Comments

3

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;

Comments

2

Not a one line solution. But, how about an extension method with an additional out parameter?

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

Then, you could use the method like this.

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

Note that the Unpack method enumerates the sequence twice. So, I'd use it only if the data in the IEnumerable object is repeatable.

I didn't care to check the performance of the method because I thought that normally we would not unpack a large array.

Of course, you could use ref modifier instead of out, and the usage would be different.

Comments

0

There is no built in way in C# to do a multiple assignment like you can in Perl; however, there are multiple solutions to get the data into each variable via a normal path.

2 Comments

Well, what is the most elegant way to get the data into each variable?
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.
0

I couldn't resist adding to the ridiculousness :) Please don't use this "solution", at least not as-is.

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

Usage:

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.