在C#中处理可变数量的输出参数,减少代码重复

时间:2017-01-18 10:01:19

标签: c# arrays destructuring

我正在尝试编写一个用数组内容填充字符串的函数,或者将它们设置为null。字符串的数量可能会有所不同,我不想添加像这些一样的要求都是同一个数组或类的一部分。

在C#中,您无法合并paramout。因此,执行此操作的唯一方法似乎是重载此方法:

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

结论:感谢BotondBalázs的回答,特别是暗示这被称为“阵列解构”。正如他们所指出的那样,并且正如这个问题所证实的那样,在当前版本的C#中是不可能的:Destructuring assignment - object properties to variables in C#

9 个答案:

答案 0 :(得分: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];

容易腻。您对数据类型的语义有问题吗? 制作一个更好的数据类型,封装所需的语义

答案 1 :(得分: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虽然会有元组解构。

答案 2 :(得分: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;
}

答案 3 :(得分: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";
}

答案 4 :(得分: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);

但我相信大多数人(包括我自己)会认为这比它的价值更麻烦。

答案 5 :(得分: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);
    }
}

答案 6 :(得分:2)

只需在数组中使用索引,例如:

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

答案 7 :(得分: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];  
    ....
}

看起来好多了。

答案 8 :(得分: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);