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#

Community
  • 1
  • 1
  • 6
    Whats wrong with public string[] ParseRemainders(string[] remainders) ? –  Jan 18 '17 at 10:02
  • 3
    Consider using a return type of IEnumerable and don't use out. – EluciusFTW Jan 18 '17 at 10:04
  • 9
    I really don't understand why people aggressively downvote all beginner questions. What's wrong with this one? – Botond Balázs Jan 18 '17 at 10:06
  • 3
    @Botond: I agree, it's a perfectly fine question, imo. – EluciusFTW Jan 18 '17 at 10:07
  • @Slai it isn't. – Botond Balázs Jan 18 '17 at 10:12
  • 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". –  Jan 18 '17 at 10:12
  • @Slai I might not have understood the question but I don't think so. Basically I think OP wants to have array destructuring in the C# language. – Botond Balázs Jan 18 '17 at 10:15
  • Is there any correspondence between the number of elements in `remainders[]` and the number of strings you want to return? – Matthew Watson Jan 18 '17 at 10:33
  • I really don't see the point in this question. Why do you want the array elements in individual variables when you can just access them by index? If you really want to have them named, why not using a dictionary? – Pikoh Jan 18 '17 at 10:40
  • 1
    @Pikoh, because `inputFileName` is more readable in code than `remainder[0]`. –  Jan 18 '17 at 10:44
  • @ゼーロ ok,see if my answer solves the code readability problem. I think it may be a valid workaround. – Pikoh Jan 18 '17 at 12:08
  • Another solution could be to generate the duplicated C# code using T4 templates or similar, obviously generating up to, say, 6 out parameters. – Arturo Torres Sánchez Jan 18 '17 at 13:17
  • 1
    The code seems very strange. You have an arbitrary-size collection of variables -- an array -- and you wish to populate a fixed-size number of variables -- your out parameters. Why not simply take two arrays -- one of arbitrary size and one of a given size -- and copy the first to the second? – Eric Lippert Jan 18 '17 at 15:02
  • @EricLippert that's more or less what i asked OP. But he seems to want to use variable with a significant name looking for better code readability – Pikoh Jan 18 '17 at 15:06

9 Answers9

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.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • +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 Jan 18 '17 at 15:24
  • 1
    @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 Jan 18 '17 at 18:21
  • 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 Jan 18 '17 at 18:29
  • 4
    @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 Jan 18 '17 at 19:03
  • 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 Jan 18 '17 at 20:11
  • 1
    @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.) – Eric Lippert Jan 18 '17 at 21:11
  • Yes,i know. I meant "order in the source code",and in my simple test worked. As i told,documentation states this is not valid. But is always a pleasure and informative your explanation,once again i learned something today. I'll delete my answer as it is obvious it won't work as expected in many cases – Pikoh Jan 18 '17 at 21:44
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.

Botond Balázs
  • 2,512
  • 1
  • 24
  • 34
  • 1
    Thanks, you hit the nail on the head there. Shame there is no way to do this in C# at the moment. –  Jan 18 '17 at 10:39
  • 1
    @ゼーロ I feel like this might be possible with `unsafe` code and pointers, but probably not worth the complications. – Slai Jan 18 '17 at 11:18
  • 1
    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 Jan 18 '17 at 11:40
  • How would the javascript work if `remainders` is only two elements long when you do `var [a, b, c] = remainders;`? – Matthew Watson Jan 18 '17 at 12:18
  • @MatthewWatson then `c` would be `undefined`. – Botond Balázs Jan 18 '17 at 14:15
  • This is also known as "unpacking" in Python. – jpmc26 Jan 18 '17 at 15:02
  • You can’t do it using only library code, like in C÷÷ (Boost tuple `map`, now standard)? – JDługosz Jan 18 '17 at 18:16
  • I think you could hack this using an implicit conversion and a helper function. – Dai Jan 18 '17 at 18:42
  • 1
    @Dai No, you can't because sooner or later you have to define a method equivalent to `Deconstruct(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. – InBetween Jan 19 '17 at 10:48
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;
}
Andrey Korneyev
  • 26,353
  • 15
  • 70
  • 71
  • 1
    well this is not solving the same thing. Its more like converting array to variables not returning the same array – Rafal Jan 18 '17 at 10:07
  • @Rafal Not sure what you mean but there's no conversions going on in this snippet... – Rob Jan 18 '17 at 10:09
  • If you would look closer into `your logic here` you will return the parameter. The intention in my opinion is more like unpackaging array into variables similar as you can write in python. – Rafal Jan 18 '17 at 10:12
  • But why, why wouldn't you just use an Index in the Array? All this is doing is converting an string array to an `IEnumerable`, like @Rafal says the intention to unpacking array into variables... but why not just use an index for each variable? – Jeremy Thompson Jan 18 '17 at 10:16
  • 2
    Who is up voting its seems quite weird :( – MMK Jan 18 '17 at 10:17
  • 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. – Andrey Korneyev Jan 18 '17 at 10:20
  • See my edit, I'm trying to avoid endlessly re-writing application specific logic as I describe. –  Jan 18 '17 at 10:34
  • I would return a `string[]` or `List` for this to make it easier to access the values by index. – Matthew Watson Jan 18 '17 at 10:36
  • 2
    I think the method name containing "parse" is confusing people. –  Jan 18 '17 at 10:45
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";
}
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
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.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
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);
    }
}
Taudris
  • 1,413
  • 1
  • 15
  • 23
2

Just use an index in the array, eg:

remainers[0];  //same as p1
remainers[1];  //same as p2  
remainers[2];  //same as p3
Jeremy Thompson
  • 61,933
  • 36
  • 195
  • 321
  • What happens if `remainders` only has two elements? That's why I include a null check. –  Jan 18 '17 at 10:39
  • 1
    Just use `remainders.Length` to work out how many items & you're better off using `string.IsNullOrEmpty` for null checks – Jeremy Thompson Jan 18 '17 at 10:42
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.

InBetween
  • 32,319
  • 3
  • 50
  • 90
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);
Vitani
  • 1,594
  • 1
  • 14
  • 28
  • 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 Jan 18 '17 at 16:20
  • 3
    Like I say, seems to be overcomplicating a simple situation. Still have to declare the variables somewhere. – Vitani Jan 18 '17 at 19:20
  • 1
    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 Jan 18 '17 at 23:00
  • 1
    @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. – Vitani Jan 19 '17 at 08:42
  • @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? – MikeKulls Jan 19 '17 at 21:36
  • Indeed, or an instance method in the containing class if it's only going to be used here (that's unlikely though IMHO). – Vitani Jan 20 '17 at 10:53