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"}) ;
精彩评论