问题
This is a followup on a thread I thought was resolved yesterday. Yesterday I was having problems with my code in the following case:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication3
{
class Program
{
class Bar
{
int v;
public Bar(int v) { this.v = v; }
public override string ToString() { return v.ToString(); }
}
static void Main(string[] args)
{
Foo(1, 2, 3);
Foo(new int[] { 1, 2, 3 });
Foo(new Bar(1), new Bar(2), new Bar(3));
Foo(new Bar[] { new Bar(1), new Bar(2), new Bar(3) });
System.Threading.Thread.Sleep(20000);
}
static void Foo(params object[] objs)
{
Console.WriteLine("New call to Foo: ");
foreach(object o in objs)
Console.WriteLine("Type = " + o.GetType() + ", value = "+o.ToString());
}
}
}
If you run this you can see a problem with the last call to Foo. The fact that the argument is a vector is "lost".
So.... anyone know how to report a C# compiler bug? Or would this be considered a reflection bug?
(What a relief: I was bummed to think I had wasted time here with a bug of my own. In fact it is a C# bug after all, and I'm vindicated! And how often do we get to see actual C# compiler bugs these days? Not common...)
回答1:
I would expect these two calls to function identically- a params argument is an array in the called method. Jon Skeet's example in the previous question works because an array of int's is not covariant to an array of objects (and so is treated as new Object[] { new Int[] {1,2,3} }
), but in this example an array of FooBars is covariant to an array of objects, and so your parameter is expanded into the objs
argument.
Wikipedia of all things covers this exact case: Covariance and contravariance (computer science)
Sorry but I am sure that this is not a compiler bug.
EDIT:
You can achieve what you want thus:
Foo(new Object[] { new Bar[] { new Bar(1), new Bar(2), new Bar(3) } });
NEW EDIT (other author):
Or simply use:
Foo((Object)new Bar[] { new Bar(1), new Bar(2), new Bar(3) });
回答2:
The C# 4.0 specification is quite explicit in how this all plays out. 7.5.3.1 says that if a function with params
can be applied either in normal form (ignoring the params
keyword) or expanded form (using the params
keyword), then normal form wins.
Assuming Foo
was declared as Foo(params object[] args)
, then the call Foo(new Foobar[] {new Foobar(1), new Foobar(2), new Foobar(3)) });
is applicable in normal form, since Foobar[]
is implicitly convertible to object[]
(6.1.6 clause 5). Therefore, the normal form is used and the expanded form is ignored.
(I'm assuming that C# 5.0 did not change this part of the language.)
回答3:
See section 7.5.3.1 of the C# spec:
7.5.3.1 Applicable function member
A function member is said to be an applicable function member with respect to an argument list A when all of the following are true:
- Each argument in A corresponds to a parameter in the function member declaration as described in §7.5.1.1, and any parameter to which no argument corresponds is an optional parameter.
- For each argument in A, the parameter passing mode of the argument (i.e., value, ref, or out) is identical to the parameter passing mode of the corresponding parameter, and
- for a value parameter or a parameter array, an implicit conversion (§6.1) exists from the argument to the type of the corresponding parameter, or
- [... some irrelevant material concerning
ref
andout
parameters ...]For a function member that includes a parameter array, if the function member is applicable by the above rules, it is said to be applicable in its normal form. If a function member that includes a parameter array is not applicable in its normal form, the function member may instead be applicable in its expanded form[.]
Because the array you passed can be implicitly converted to object[]
, and because overload resolution prefers "normal" form over "expanded" form, the behavior you observe conforms to the specification, and there is no bug.
In addition to the workaround described by Chris Shain, you could also change Bar
from a class to a struct; that makes the array no longer implicitly convertible to object[]
, so you'll get the behavior you desire.
回答4:
I believe you are wrong Ken That's because int[] is not of object[] type, so the compiler assumes that the int[] is just one of arguments passed to the method.
here's how:
new Foobar[] { } is object[]; // true
new int[] { } is object[]; // false
update: you can make the method generic to let the compiler know struct/object type passes as params:
void Foo<T>(params T[] objs)
{
foreach (T o in objs)
Console.WriteLine(o.GetType());
}
回答5:
So I'm going to suggest that the best answer is that I'm right and that this really is a compiler bug. While I see the point perfectly clearly, your explanation ignores the "params" keyword. In fact to use covariance this way, one MUST ignore the params keyword, as if it was unimportant. I postulate that there simply is no plausible explanation for compiling the code this way with Params present as a keyword on the type signature of Foo: to invoke your explanation you need to convince me that myClass[] should be type-matched to object[], but we shouldn't even be asking that question given the params construct. In fact, the more you think about this, the more clear that it is actually a genuine C# 5.0 compiler bug: the compiler is neglecting to apply the params keyword. The language spec doesn't actually need any change at all. I should get some kind of wierd badge, imho!
来源:https://stackoverflow.com/questions/9709642/c-sharp-params-apparent-compiler-bug-c-5-0