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

Handle variable number of out parameters with less code duplication in C#

我正在尝试编写一个函数,用数组的内容填充字符串,或将它们设置为 null。字符串的数量可能会有所不同,我不想添加像它们都属于同一个数组或 class.

这样的要求

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

抱歉,如果不清楚,我有一个特定的目标,这就是为什么我不简单地 return List<>


结论:感谢 Botond Balázs 的回答,特别是提示这叫做 "array destructuring"。正如他们指出的那样,并且正如这个问题所证实的那样,在当前版本的 C# 中是不可能的:

好吧,您可以将方法更改为类似

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

只要在数组中使用索引即可,eg:

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

根据你的描述,我猜你的用例类似于:

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

您不需要 return 这样的字符串。正如其他答案/评论中已经指出的那样,您可以简单地 return 一个字符串数组:

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

看起来好多了。

Andys 的方法很好,但我会 return 一个 string[],因为它应该与输入数组具有相同的大小,并且 return 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";
}

如果我没理解错的话,你的用例应该是这样的:

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 将具有元组解构。

为了完整起见,这是在 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]);,它向您展示了 shorthand 从数组的某些元素分配几个不同变量的方法。

但是,如果数组不够长,这对分配 null 没有帮助。您可以通过编写帮助程序 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;
        }
    }
}

然后:

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

但我敢肯定,大多数人(包括我自己)会认为这带来的麻烦多于它的价值。

感觉你在尝试over-complicate一个简单的空值检查,回到基础并保持简单:

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

到目前为止,我会采用与任何答案不同的方法。

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

简单易行。您对数据类型的语义有疑问吗? 创建一个更好的数据类型来封装所需的语义

我认为这非常接近您想要的。它不需要 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);
    }
}