向所有人提出问题C#向导。我有一个方法,称之为myFunc,它需要可变长度/类型参数列表。 myFunc本身的参数签名是myFunc(params object[] args)
,我在列表上使用反射(例如,想想这有点像printf)。
我希望myFunc(1, 2, 3)
与myFunc(new int[] { 1, 2, 3 })
区别对待。也就是说,在myFunc的主体内,我想枚举我的参数的类型,并希望最终得到{int,int,int}而不是int []。现在我得到后者:实际上,我无法区分这两种情况,它们都以int []的形式出现。
我曾希望前者显示为obs []。长度= 3,有[0] = 1等等。
我曾预料到后者会显示为obs []。长度= 1,有[0] = {int [3]}
可以这样做,还是我问不可能?
答案 0 :(得分:9)
这样就可以了:
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("First call");
Foo(1, 2, 3);
Console.WriteLine("Second call");
Foo(new int[] { 1, 2, 3 });
}
static void Foo(params object[] values)
{
foreach (object x in values)
{
Console.WriteLine(x.GetType().Name);
}
}
}
或者,如果您使用DynamicObject
,则可以使用动态类型来获得类似的结果:
using System;
using System.Dynamic;
class Program
{
static void Main(string[] args)
{
dynamic d = new ArgumentDumper();
Console.WriteLine("First call");
d.Foo(1, 2, 3);
Console.WriteLine("Second call");
d.Bar(new int[] { 1, 2, 3 });
}
}
class ArgumentDumper : DynamicObject
{
public override bool TryInvokeMember
(InvokeMemberBinder binder,
Object[] args,
out Object result)
{
result = null;
foreach (object x in args)
{
Console.WriteLine(x.GetType().Name);
}
return true;
}
}
两个计划的输出:
First call
Int32
Int32
Int32
Second call
Int32[]
现在给出上面的输出,不清楚你的问题究竟来自哪里......虽然如果你给Foo("1", "2", "3")
vs Foo(new string[] { "1", "2", "3" })
那么那将是另一回事 - 因为{{ 1}}与string[]
兼容,但object[]
不兼容。如果这是真正的情况一直给你带来问题,那么请查看动态版本 - 这将在两种情况下都有效。
答案 1 :(得分:9)
好的,所以我们假设我们放弃了另一个问题,即你错误地认为这是一个编译器错误并且实际上解决了你的真实问题。
首先,让我们试着说出真正的问题。这是我的镜头:
“可变参数”方法是一种采用未指定的超前参数数量的方法。
在C#中实现可变方法的标准方法是:
void M(T1 t1, T2 t2, params P[] p)
即,零个或多个必需参数后跟一个标记为“params”的数组。
当调用这样的方法时,该方法适用于其普通形式(不带参数)或其扩展形式(带参数)。也就是说,打电话给
void M(params object[] x){}
表单
M(1, 2, 3)
生成为
M(new object[] { 1, 2, 3 });
因为它仅适用于其扩展形式。但是打电话
M(new object[] { 4, 5, 6 });
生成为
M(new object[] { 4, 5, 6 });
而不是
M(new object[] { new object[] { 4, 5, 6 } });
因为它适用于其正常形式。
C#支持引用类型元素数组的不安全数组协方差。也就是说,string[]
可能会隐式转换为object[]
,即使尝试将此类数组的第一个元素更改为非字符串也会产生运行时错误。
我希望打电话给表格:
M(new string[] { "hello" });
并且此方法仅适用于扩展形式:
M(new object[] { new string[] { "hello" }});
而不是正常形式:
M((object[])(new string[] { "hello" }));
C#中是否有办法实现可变方法,这些方法不会成为不安全数组协方差和优先以正常形式应用的方法的牺牲品?
是的,有办法,但你不会喜欢它。 如果您打算将单个数组传递给它,最好使该方法不可变。
C#的Microsoft实现支持未记录的扩展,它允许使用不使用params数组的C风格的可变方法。 此机制不适用于一般用途,仅包含在CLR团队和其他编写互操作库的人员中,以便他们可以编写桥接C#和期望C风格可变方法的语言的互操作代码。 I强烈建议反对自己尝试这样做。
这样做的机制涉及使用未记录的__arglist
关键字。基本草图是:
public static void M(__arglist)
{
var argumentIterator = new ArgIterator(__arglist);
object argument = TypedReference.ToObject(argumentIterator.GetNextArg());
您可以使用参数迭代器的方法遍历参数结构并获取所有参数。并且您可以使用超级魔法类型的引用对象来获取参数的类型。 甚至可以使用这种技术将变量的引用作为参数传递,但我不建议这样做。
这种技术的特别糟糕之处在于呼叫者需要说:
M(__arglist(new string[] { "hello" }));
在C#中坦率地看起来相当粗略。现在你明白为什么你最好完全放弃可变方法;只是让调用者传递一个数组并完成它。
同样,我的建议是(1)在任何情况下都不应该尝试使用C#语言的这些未记录的扩展,这些扩展旨在为CLR实现团队和互操作库作者提供便利,并且(2)您应该简单地放弃可变参数方法;它们似乎不适合您的问题空间。不要与工具作斗争;选择一个不同的工具。
答案 2 :(得分:3)
是的,您可以检查参数长度并检查参数类型,请参阅以下工作代码示例:
class Program
{
static void Main(string[] args)
{
myFunc(1, 2, 3);
myFunc(new int[] { 1, 2, 3 });
}
static void myFunc(params object[] args)
{
if (args.Length == 1 && (args[0] is int[]))
{
// called using myFunc(new int[] { 1, 2, 3 });
}
else
{
//called using myFunc(1, 2, 3), or other
}
}
}
答案 3 :(得分:2)
你可以通过打破列表中的第一个元素并提供额外的重载来实现这样的目的,例如:
class Foo
{
public int Sum()
{
// the trivial case
return 0;
}
public int Sum(int i)
{
// the singleton case
return i;
}
public int Sum(int i, params int[] others)
{
// e.g. Sum(1, 2, 3, 4)
return i + Sum(others);
}
public int Sum(int[] items)
{
// e.g. Sum(new int[] { 1, 2, 3, 4 });
int i = 0;
foreach(int q in items)
i += q;
return i;
}
}
答案 4 :(得分:0)
这在C#中是不可能的。 C#将在编译时通过第二次调用替换您的第一个调用。
一种可能性是创建一个没有params
的重载并将其设为ref
参数。这可能没有意义。如果你想根据输入改变行为,也许给第二个myFunc另一个名字。
<强>更新强>
我现在了解你的问题。你想要的是不可能的。如果唯一的论点是可以解析为object[]
的任何内容,则无法区分它。
您需要一个替代解决方案,可能有一个由调用者创建的字典或数组来构建参数。
答案 5 :(得分:0)
事实证明存在一个真正的问题,它归结为C#进行类型推断的方式。请参阅this other thread
上的讨论