处理可变数量的输出参数,减少 C# 中的代码重复
Handle variable number of out parameters with less code duplication in C#
我正在尝试编写一个函数,用数组的内容填充字符串,或将它们设置为 null。字符串的数量可能会有所不同,我不想添加像它们都属于同一个数组或 class.
这样的要求
在 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 ....
如何避免所有这些代码重复,理想情况下接受任意数量的参数?
编辑: 这很有用,因为您可以执行 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);
}
}
我正在尝试编写一个函数,用数组的内容填充字符串,或将它们设置为 null。字符串的数量可能会有所不同,我不想添加像它们都属于同一个数组或 class.
这样的要求在 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 ....
如何避免所有这些代码重复,理想情况下接受任意数量的参数?
编辑: 这很有用,因为您可以执行 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);
}
}