2017-01-18 12 views
18

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

В C# вы не можете комбинировать 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 .... 

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


Edit: Это полезно, потому что вы могли бы сделать, скажем, 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 Балаш для ответа, в частности, намек, что это называется «массив деструктурирующий». Как они указывают, и, как утверждает этот вопрос, не представляется возможным в текущей версии C#: Destructuring assignment - object properties to variables in C#

+6

Что не так с общедоступной строкой [] ParseRemainders (string [] остатки)? – AndyJ

+3

Рассмотрите возможность использования возвращаемого типа IEnumerable и не используйте его. – EluciusFTW

+9

Я действительно не понимаю, почему люди агрессивно сбрасывают все начинающие вопросы. Что случилось с этим? –

ответ

10

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

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

Функция вы хотите иметь в C# называется массив уничтожения того, как в JavaScript:

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

К сожалению, насколько я знаю,

это не может быть решена общий способ с использованием C#.

Однако C# 7 будет иметь деструкцию кортежа.

+1

Спасибо, ты ударил ноготь по голове там. Позор, на данный момент нет способа сделать это на C#. –

+1

@ ゼ ー ロ Я чувствую, что это возможно с помощью «небезопасного» кода и указателей, но, вероятно, не стоит осложнений. – Slai

+1

C# 7 не поможет .. новый метод 'Deconstruct' имеет точно такую ​​же проблему, что и у OP:' void Deconstruct (out first, out second, ....) ', который возвращает вас обратно к квадрату 1, потому что есть нет возможности использовать 'out' и' params'. Вам нужно будет написать столько перегрузок, сколько вы ожидаете использовать. – InBetween

7

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

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

, но это не решение того же самого. Его больше похоже на преобразование массива в переменные, не возвращающие один и тот же массив. – Rafal

+0

@Rafal Не уверен, что вы имеете в виду, но в этом фрагменте нет конверсий ... – Rob

+0

Если вы посмотрите ближе к 'вашей логике здесь ', вы вернете параметр. Намерение, на мой взгляд, больше похоже на распаковку массива на переменные, подобные тому, что вы можете писать на python. – Rafal

2

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

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

Что произойдет, если 'остатки' имеют только два Элементы? Вот почему я включаю нулевую проверку. –

+1

Просто используйте 'остатки.Length', чтобы определить, сколько элементов & вам лучше использовать 'string.IsNullOrEmpty' для нулевых проверок. –

1

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

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

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

6

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

Разве это не просто дублирует' остатки'? Редактировать: Ах, ладно. –

3

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

Важным направлением здесь является 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); 

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

1

Он чувствует, как вы пытаетесь чрезмерно усложнять простую проверку нулевой, просто вернуться к основам и держать его просто:

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

OP утверждает, что цель всего этого метода заключается в том, чтобы избежать того, что вы защищаете: нужно снова и снова писать «переменное имя = значение в индексе массива i». –

+3

Как я уже сказал, кажется, слишком сложная ситуация. Все равно придется объявлять переменные где-то. – Jocie

+1

Довольно же тот же ответ, который я собирался дать, кроме того, что я добавлю метод расширения в массив, чтобы вы могли получить элемент из массива с чем-то вроде restder.getItemByIndexOrNull (3). Что касается -1 в этом ответе, это немного грубо. OP def пытается скомпрометировать что-то простое и, вероятно, ставит бандайу неправильный метод для начала (хранение разных частей данных в массиве вместо класса) – MikeKulls

12

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

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

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

+0

+ 1.Но при этом вам все равно придется писать строку для каждой переменной. (Представьте, что их 10). Что вы думаете об использовании класса и размышлений, как в моем решении? – Pikoh

+1

@Pikoh: Кажется ломким. Если вы хотите, чтобы переменные были свойствами класса, то почему бы просто не сделать класс конструктором, который принимает массив, и поставить туда логику? –

+0

Да, вы правы. Но имеет преимущество работать в любом классе, и если вы хотите добавить новое свойство, вам просто нужно добавить его, не меняя конструктора. Но я понимаю, что я, возможно, слишком усложнил его. – Pikoh

3

Я думаю, что это довольно близко к тому, что вы хотите.Он не нужен C# 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); 
    } 
}