Join the Stack Overflow Community
Stack Overflow is a community of 6.6 million programmers, just like you, helping each other.
Join them; it only takes a minute:
Sign up

I'm trying to write a function that populates strings with the contents of an array, or sets them to null. The number of strings is can vary and I don't want to add requirements like them all being part of the same array or class.

In C# you cannot combine param and out. Therefore the only way to do this seems to be to overload the method like this:

    public void ParseRemainders(string[] remainders, out string p1)
    {
        p1 = null;
        if ((remainders != null) && (remainders.Length > 0))
            p1 = remainders[0];
    }

    public void ParseRemainders(string[] remainders, out string p1, out string p2)
    {
        p1 = null;
        p2 = null;
        if (remainders != null)
        {
            ParseRemainders(remainders, out p1);
            if (remainders.Length > 1)
                p2 = remainders[1];
        }
    }

    public void ParseRemainders(string[] remainders, out string p1, out string p2, out string p3)
    {
        p1 = null;
        p2 = null;
        p3 = null;
        if (remainders != null)
        {
            ParseRemainders(remainders, out p1, out p2);
            if (remainders.Length > 2)
                p3 = remainders[2];
        }
    }

    .... and on forever ....

How can I avoid all this code duplication, ideally accepting an arbitrary number of parameters?


Edit: This is useful because you could do, say, ParseRemainders(remainders, out inputFileName, out outputFileName, out configFileName) and then avoid having to manually do

if (remainder.Length > 0) inputFileName = remainder[0];
if (remainder.Length > 1) outputFileName = remainder[1];
if (remainder.Length > 2) configFileName = remainder[2];
...

Sorry if this wasn't clear, I had a specific goal in mind which I why I didn't simply return a List<>.


Conclusion: Thanks to Botond Balázs for the answer, particularly the hint that this is called "array destructuring". As they point out, and as this question confirms, it is not possible in the current version of C#: Destructuring assignment - object properties to variables in C#

share|improve this question
4  
Whats wrong with public string[] ParseRemainders(string[] remainders) ? – AndyJ 3 hours ago
1  
Consider using a return type of IEnumerable<string> and don't use out. – EluciusFTW 3 hours ago
6  
I really don't understand why people aggressively downvote all beginner questions. What's wrong with this one? – Botond Balázs 3 hours ago
3  
@Botond: I agree, it's a perfectly fine question, imo. – EluciusFTW 3 hours ago
2  
@Slai I don't think "How can I emulate params combined with out" is a duplicate of "What's the difference between out and ref". – AndyJ 3 hours ago
up vote 7 down vote accepted

If I understand you correctly, your use case would look like this:

var remainders = new[] { "a", "b", "c" };
string a, b, c;
ParseRemainders(remainders, a, b, c); // after this, a == "a", b == "b" and c == "c"

The feature you want to have in C# is called array destructuring, like in JavaScript:

var remainders = ["a", "b", "c"];
var [a, b, c] = remainders; // after this, a == "a", b == "b" and c == "c"

Unfortunately, as far as I know, the answer to your question is that

no, this cannot be solved in a general way using C#.

C# 7 will have tuple destructuring though.

share|improve this answer
1  
Thanks, you hit the nail on the head there. Shame there is no way to do this in C# at the moment. – ゼーロ 3 hours ago
    
@ゼーロ I feel like this might be possible with unsafe code and pointers, but probably not worth the complications. – Slai 2 hours ago
    
C#7 wont help.. the new method Deconstruct has precisely the same problem the OP has: void Deconstruct(out first, out second, ....) which brings you right back to square 1 because there is no way to use out and params. You'd need to write as many overloads as you expect to use. – InBetween 2 hours ago
    
How would the javascript work if remainders is only two elements long when you do var [a, b, c] = remainders;? – Matthew Watson 1 hour ago

Well, you can change your method to something like

public IEnumerable<string> ParseRemainders(string[] remainders)
{
    var result = new List<string>();

    ///... your logic here, fill list with your strings according to your needs

    return result;
}
share|improve this answer
1  
well this is not solving the same thing. Its more like converting array to variables not returning the same array – Rafal 3 hours ago
2  
Who is up voting its seems quite weird :( – MMK 3 hours ago
1  
@JeremyThompson I suppose assignment of p1 = remainders[0] and so on in question was syntetic sample, not the real one, and essence of question was "how to return variable number of values from method" - just as it was stated in question header. – Andy Korneyev 3 hours ago
1  
@Botond Balázs answer is a correct one. – Rafal 3 hours ago
1  
I think the method name containing "parse" is confusing people. – ゼーロ 3 hours ago

Andys approach is fine but i'd return a string[] because it should have the same size as the input array and also return null if the input array was null:

public string[] ParseRemainders(string[] remainders)
{
    if(remainders == null) return null;
    var parsed = new string[remainders.Length];
    for(int i = 0; i < remainders.Length; i++)
        parsed[i] = ParseRemainder(remainders[i]);
    return parsed;
}

To clarify what ParseRemainder(different method for a single string) does:

public string ParseRemainder(string remainder)
{
    // parsing logic here...
    return "the parsing result of remainder";
}
share|improve this answer
    
Doesn't this just duplicate remainders? Edit: Ah, okay. – ゼーロ 3 hours ago
    
@ゼーロ: i have edited my answer to clarify it. ParseRemainder is another method that encapsulates the parsing logic for a single string. – Tim Schmelter 3 hours ago

Just use an index in the array, eg:

remainers[0];  //same as p1
remainers[1];  //same as p2  
remainers[2];  //same as p3
share|improve this answer
    
What happens if remainders only has two elements? That's why I include a null check. – ゼーロ 3 hours ago
1  
Just use remainders.Length to work out how many items & you're better off using string.IsNullOrEmpty for null checks – Jeremy Thompson 3 hours ago
    
Thanks for the PROTIP on string.IsNullOrEmpty. – ゼーロ 3 hours ago

For completeness, this is how you can do this kind of thing in C#7 (Visual Studio 2017):

string[] test = { "One", "Two", "Three", "Four", "Five" };

var (a, b, c) = (test[0], test[2], test[4]);

Debug.Assert(a == "One");
Debug.Assert(b == "Three");
Debug.Assert(c == "Five");

The important line here is var (a, b, c) = (test[0], test[2], test[4]); which shows you the shorthand way of assigning several different variables from some elements of an array.

However, this doesn't help with the assigning of null if the array isn't long enough. You could get around that problem by writing a helper class:

public sealed class ElementsOrNull<T> where T: class
{
    readonly IList<T> array;

    public ElementsOrNull(IList<T> array)
    {
        this.array = array;
    }

    public T this[int index]
    {
        get
        {
            if (index < array.Count)
                return array[index];

            return null;
        }
    }
}

And then:

string[] test = { "One", "Two", "Three", "Four", "Five" };

var t = new ElementsOrNull<string>(test);
var (a, b, c) = (t[0], t[2], t[6]);

Debug.Assert(a == "One");
Debug.Assert(b == "Three");
Debug.Assert(c == null);

But I'm sure most people (myself included) will think that's more trouble than it's worth.

share|improve this answer

From your description I'm guessing your use case would be something similar to:

public void SomeMethod( ... )
{
    string p1;
    string p2;

    ....
    ParseRemainders(string[] remainders, out string p1, out string p2);
    ...
}

public void SomeOtherMethod( ... )
{
    string p1;
    string p2;
    string p3;

    ....
    ParseRemainders(string[] remainders, out string p1, out string p2, out string p3);
    ...
}

You don't need to return strings this way. As already pointed out in other answers / comments, you can simply return an array of strings:

 string[] ParseRemainders(string[] remainders)
 {
     var result = new string[remainder.Length];
     result[0] = //whatever p1 would be
     result[1] = //whatever p2 would be
     //etc.
 }

And you would use it like this:

public void SomeMethod( ... )
{
    ....
    var parsed = ParseRemainders(string[] remainders);
    string p1 = parsed[0];
    string p2 = parsed[1];  
    ....
}

That looks a lot better.

share|improve this answer
    
Please see my edit, thanks. – ゼーロ 3 hours ago

I'm going to throw a possible workaround for this. OP motivation seems to be improving code readability, so this may help in array destructuring.

My solution involves using a class for the "variables" where you want to destructure your array, a extension method and some Reflection.

First, the class:

private class Reminders
{
    public string inputFileName { get; set; }
    public string outpuFileName { get; set; }
    public string configFileName { get; set; }
}

Then the extension method:

public static void arrayDesestructure<T>(this T obj, string[] remainders)
{
    int i = 0;
    Type t = obj.GetType();

    PropertyInfo[] properties=t.GetProperties();

    if (properties.Count()!=remainders.Count()) throw new Exception("Incorrect number of parameters");

    foreach(PropertyInfo prop in t.GetProperties())
    {
        prop.SetValue(obj, remainders[i]);
        i++;
    }
}

And this would be the usage:

string[] test = new string[] { "a", null ,"b" };
Reminders rem = new Reminders();            
rem.arrayDesestructure(test);
//after this, you'll have rem.inputFileName=="a",
//                        rem.outputFileName==null, 
//                        rem.configFileName=="b"

You just have to be careful with the number of elements in your array that must be the same as the number of public properties in your class, and the order is relevant.

Hope it helps.

Edit

If you don't want any exception to be thrown if the parameter numbers doesn't match, you can modify the extension method:

public static void arrayDesestructure<T>(this T obj, string[] remainders)
{
    int i = 0;
    Type t = obj.GetType();

    PropertyInfo[] properties=t.GetProperties();

    foreach(PropertyInfo prop in t.GetProperties())
    {
        if (i < remainders.Count())
        {
             prop.SetValue(obj, remainders[i]);
        }
        i++;
    }
}

With this second method, if there are more elements in the array, they would be ignored, and if there are less the rest of properties in the class would remain as they were.

share|improve this answer

It feels like you're trying to over-complicate a simple null check, just go back to basics and keep it simple:

public string GetRemainder(string[] remainders, int index)
{
    if ((remainders != null) && (remainders.Length > index))
        return remainders[index];
    return null;
}

Usage:

var inputFileName = GetRemainder(remainder, 0);
var outputFileName = GetRemainder(remainder, 1);
var configFileName = GetRemainder(remainder, 2);
share

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.