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
5  
Whats wrong with public string[] ParseRemainders(string[] remainders) ? – AndyJ 13 hours ago
2  
Consider using a return type of IEnumerable<string> and don't use out. – EluciusFTW 13 hours ago
8  
I really don't understand why people aggressively downvote all beginner questions. What's wrong with this one? – Botond Balázs 13 hours ago
3  
@Botond: I agree, it's a perfectly fine question, imo. – EluciusFTW 13 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 13 hours ago

10 Answers 10

up vote 10 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

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. – ゼーロ 12 hours ago
    
@ゼーロ I feel like this might be possible with unsafe code and pointers, but probably not worth the complications. – Slai 12 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 11 hours ago
    
How would the javascript work if remainders is only two elements long when you do var [a, b, c] = remainders;? – Matthew Watson 11 hours ago
    
@MatthewWatson then c would be undefined. – Botond Balázs 9 hours 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 13 hours ago
2  
Who is up voting its seems quite weird :( – MMK 13 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 13 hours ago
1  
@Botond Balázs answer is a correct one. – Rafal 13 hours ago
1  
I think the method name containing "parse" is confusing people. – ゼーロ 12 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. – ゼーロ 12 hours ago

I would take a different approach than any of the answers so far.

static class Extensions {
  public static SafeArrayReader<T> MakeSafe<T>(this T[] items)
  {
    return new SafeArrayReader<T>(items);
  }
}
struct SafeArrayReader<T> 
{
  private T[] items;
  public SafeArrayReader(T[] items) { this.items = items; }
  public T this[int index] 
  {
    get
    {
      if (items == null || index < 0 || index >= items.Length)
        return default(T);
      return items[index];
    }
  }
}

There, now you have an array that gives you a default value instead of throwing:

var remainder = GetRemainders().MakeSafe();
var input = remainder[0];
var output = remainder[1];
var config = remainder[2];

Easy peasy. You have a problem with the semantics of a data type? Make a better data type that encapsulates the desired semantics.

share|improve this answer
    
+1.But with this, you'll still have to write a line for each variable.(imagine there are 10 of them). What do you think of using a class and reflection as in my solution ? – Pikoh 7 hours ago
    
@Pikoh: Seems brittle. If you want the variables to be properties of a class then why not simply make the class have a constructor that takes an array, and put the logic in there? – Eric Lippert 5 hours ago
    
Yes,you are right. But has the advantage of working in any class, and if you want to add a new property,you just have to add it,without changing the constructor. But i get your point,i overcomplicated it probably. – Pikoh 4 hours ago
1  
@Pikoh: The negative consequence is: if you add a new property without thinking about it carefully then values get assigned to completely unexpected variables but the program continues to compile and run. Also, what about unusual but legal cases like protected properties and private properties? What if you refactor your class into two classes, base and derived, and one has half the properties, and the other has the other half; does the solution continue to work? And so on. Remember, code changes. Don't just design code to be right now; make it robust in the face of change. – Eric Lippert 4 hours ago
    
As usual,you are right again. But let me defend this a bit. If i'm not wrong,private properties won't get enumerated by GetProperties,and protected could be filtered. Regarding inheritance,it would be poinless in this case,as it is only a workaround to "destructure" an array into "named" variables in a search for code readability. The problem i see now,reading GetProperties documentation,is that is not assured that properties would be returned in any particular order,what,although in my tests were returned in definition order, would definetly invalidate my solution. :) – Pikoh 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

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. – ゼーロ 12 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 12 hours ago
    
Thanks for the PROTIP on string.IsNullOrEmpty. – ゼーロ 12 hours ago

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. – ゼーロ 12 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|improve this answer
    
OP states the whole point of trying to write this method is to avoid exactly the pattern you advocate: having to write "variable name = value at array index i" over and over. – Esoteric Screen Name 7 hours ago
    
Like I say, seems to be overcomplicating a simple situation. Still have to declare the variables somewhere. – Jocie 4 hours ago
    
Pretty much the same answer I was going to give except I would add an extension method to the array, so you could get an item from the array with something like remainder.getItemByIndexOrNull(3). As for the -1 on this answer that is a bit harsh. The OP is def trying to overcomplicate something simple and is probably putting a bandaid an incorrect method to start with (storing different pieces of data in an array instead of a class) – MikeKulls 22 mins ago

I think this gets pretty close to what you want. It doesn't need C# 7, works with any data element type, and isn't limited to arrays. You may want to pick better names than ValueReader/ReadValue, though.

static class Extensions
{
    public static ValueReader<T> ReadValue<T>(this IEnumerable<T> source, out T value)
    {
        var result = new ValueReader<T>(source);
        result.ReadValue(out value);
        return result;
    }
}

class ValueReader<T>
{
    IEnumerator<T> _enumerator;

    public ValueReader(IEnumerable<T> source)
    {
        if (source == null) source = new T[0];
        _enumerator = source.GetEnumerator();
    }

    public ValueReader<T> ReadValue(out T value)
    {
        bool hasNext = _enumerator.MoveNext();
        value = hasNext ? _enumerator.Current : default(T);
        return this;
    }
}

static class TestApp
{
    public static void Main()
    {
        var remainders = new string[] { "test1", "test2", "test3" };

        string inputFileName, outputFileName, configFileName, willBeSetToNull;

        remainders
            .ReadValue(out inputFileName)
            .ReadValue(out outputFileName)
            .ReadValue(out configFileName)
            .ReadValue(out willBeSetToNull);
    }
}
share|improve this answer

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.