Обрабатывать переменное количество выходных параметров с меньшим дублированием кода в С#

Я пытаюсь написать функцию, которая заполняет строки содержимым массива или устанавливает их в null. Количество строк может меняться, и я не хочу добавлять такие требования, как все они являются частью одного и того же массива или класса.

В С# вы не можете объединить param и out. Поэтому единственный способ сделать это - перегрузить метод следующим образом:

    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 ....

Как я могу избежать всего этого дублирования кода, в идеале принимающего произвольное количество параметров?


Изменить: Это полезно, потому что вы могли бы сделать, скажем, ParseRemainders(remainders, out inputFileName, out outputFileName, out configFileName), а затем не нужно вручную делать

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

Извините, если это было неясно, у меня была конкретная цель в виду, почему я не просто вернул List<>.


Заключение: Благодаря Botond Balázs для ответа, в частности, намек на то, что это называется "разрушение массива". Как они отмечают, и по мере того, как этот вопрос подтверждает, в текущей версии С# невозможно: Назначение разрушения - свойства объекта для переменных в С#

Ответы

Ответ 1

Если вы правильно поняли, ваш пример использования будет выглядеть так:

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

Функция, которую вы хотите иметь в С#, называется destructuring массива, например, в JavaScript:

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

К сожалению, насколько мне известно,

это невозможно решить обычным способом с помощью С#.

С# 7 будет иметь деструктуризацию кортежа.

Ответ 2

Я бы выбрал другой подход, чем любой из ответов.

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

Теперь у вас есть массив, который дает вам значение по умолчанию, а не бросание:

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

Легкий peasy. У вас проблема с семантикой типа данных? Создайте лучший тип данных, который инкапсулирует желаемую семантику.

Ответ 3

Ну, вы можете изменить свой метод на что-то вроде

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

Ответ 4

Andys подход прекрасен, но я бы вернул string[], потому что он должен иметь тот же размер, что и входной массив, а также возвращать null, если входной массив был 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;
}

Чтобы уточнить, что ParseRemainder (другой метод для одного string):

public string ParseRemainder(string remainder)
{
    // parsing logic here...
    return "the parsing result of remainder";
}

Ответ 5

Для полноты, вот как это можно сделать в С# 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");

Важная строка здесь var (a, b, c) = (test[0], test[2], test[4]);, которая показывает сокращенный способ назначения нескольких разных переменных из некоторых элементов массива.

Однако это не помогает при назначении null, если массив недостаточно длинный. Вы можете обойти эту проблему, написав вспомогательный класс:

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

И затем:

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

Но я уверен, что большинство людей (включая меня) будут думать, что больше проблем, чем это стоит.

Ответ 6

Я думаю, что это довольно близко к тому, что вы хотите. Он не нужен С# 7, работает с любым типом элемента данных и не ограничивается массивами. Однако вы можете выбрать лучшие имена, чем ValueReader/ReadValue.

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

Ответ 7

Просто используйте индекс в массиве, например:

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

Ответ 8

Из вашего описания я предполагаю, что ваш прецедент будет похож на:

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

Вам не нужно возвращать строки таким образом. Как уже указывалось в других ответах/комментариях, вы можете просто вернуть массив строк:

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

И вы будете использовать его следующим образом:

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

Это выглядит намного лучше.

Ответ 9

Похоже, вы пытаетесь чрезмерно усложнить простой нулевой чек, просто вернитесь к основам и сохраните его просто:

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

Использование:

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