开发者

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

开发者 https://www.devze.com 2023-02-16 06:42 出处:网络
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 variabl

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 spli开发者_C百科t 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.


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


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)


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


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


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;


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.


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.


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"}) ;
0

精彩评论

暂无评论...
验证码 换一张
取 消