17

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#

14
  • 6
    Whats wrong with public string[] ParseRemainders(string[] remainders) ? Commented Jan 18, 2017 at 10:02
  • 3
    Consider using a return type of IEnumerable<string> and don't use out. Commented Jan 18, 2017 at 10:04
  • 9
    I really don't understand why people aggressively downvote all beginner questions. What's wrong with this one? Commented Jan 18, 2017 at 10:06
  • 3
    @Botond: I agree, it's a perfectly fine question, imo. Commented Jan 18, 2017 at 10:07
  • 3
    @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". Commented Jan 18, 2017 at 10:12

9 Answers 9

12

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.

Sign up to request clarification or add additional context in comments.

7 Comments

+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: 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?
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: 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.
@Pikoh: What is this "definition order" you refer to? Is it the order they appeared in the source code? What if the properties were all defined in separate portions of a partial class, each in its own file? What if the properties were virtual in a base class and all overridden in a derived class; is it the order from the base class or the derived class? There is no such thing as a canonical "definition order" defined by the C# language, except for the order in which static initializers run. (And that is implementation-defined for partial classes.)
|
10

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,

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

C# 7 will have tuple destructuring though.

9 Comments

Thanks, you hit the nail on the head there. Shame there is no way to do this in C# at the moment.
@ゼーロ I feel like this might be possible with unsafe code and pointers, but probably not worth the complications.
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.
How would the javascript work if remainders is only two elements long when you do var [a, b, c] = remainders;?
@Dai No, you can't because sooner or later you have to define a method equivalent to Deconstruct<T>(out params T[] array) which is simply not possible in C#. Even with native tuple support C#7 offers you run into the same wall.
|
7

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

9 Comments

well this is not solving the same thing. Its more like converting array to variables not returning the same array
@Rafal Not sure what you mean but there's no conversions going on in this snippet...
Who is up voting its seems quite weird :(
@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.
I think the method name containing "parse" is confusing people.
|
6

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

1 Comment

Doesn't this just duplicate remainders? Edit: Ah, okay.
3

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.

Comments

3

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

Comments

2

Just use an index in the array, eg:

remainers[0];  //same as p1
remainers[1];  //same as p2  
remainers[2];  //same as p3

2 Comments

What happens if remainders only has two elements? That's why I include a null check.
Just use remainders.Length to work out how many items & you're better off using string.IsNullOrEmpty for null checks
1

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.

Comments

1

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

6 Comments

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.
Like I say, seems to be overcomplicating a simple situation. Still have to declare the variables somewhere.
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 Yeah, I originally had it as an extension method, then looking at OPs code again realised remainder might be null, so you'd have to do the null check outside of the method and thus kinda defeat half of its purpose.
@Jocie good point. I haven't used C# in a few years. I can't remember if it throws an exception or passes in null to the method. Maybe a static method then on some object?
|

Your Answer

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