【问题标题】:C# params apparent compiler bug (C# 5.0) [closed]C# 参数明显的编译器错误(C# 5.0)[关闭]
【发布时间】:2012-03-14 20:20:00
【问题描述】:

这是对thread 的跟进,我认为昨天已经解决。昨天我在以下情况下遇到了我的代码问题:

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

如果你运行它,你会发现最后一次调用 Foo 有问题。参数是向量的事实是“丢失”的。

所以....有人知道如何报告 C# 编译器错误吗?或者这会被认为是反射错误吗?

(真是如释重负:我很沮丧地认为我在自己的错误上浪费了时间。事实上,这毕竟是一个 C# 错误,我被证明是正确的!而且我们多久能看到一次实际的这些天 C# 编译器的错误?不常见...)

【问题讨论】:

  • 我认为 C# 5 处于 beta 版本中...
  • 能否请您发布您的测试的完整源代码?
  • 您的代码中有一个错误,new Foobar(3)) 的右括号太多了,不是吗?
  • 目前还不清楚您认为该错误是什么,但它在早期编译器版本中的工作方式没有任何不同。试试吧。如果您只想传递一个参数,那么您必须使用 Foo(new object[] { new FooBar[] {...} });
  • 这不是 C# 编译器错误。

标签: c# params c#-5.0


【解决方案1】:

C# 4.0 specification 非常明确地说明了这一切是如何发生的。 7.5.3.1 表示如果带有params 的函数可以以普通形式(忽略params 关键字)或扩展形式(使用params 关键字)应用,则普通形式获胜。

假设Foo 被声明为Foo(params object[] args),则调用Foo(new Foobar[] {new Foobar(1), new Foobar(2), new Foobar(3)) }); 以正常形式适用,因为Foobar[] 可隐式转换为object[](6.1.6 第5 条)。因此,使用普通形式,忽略扩展形式。

(我假设 C# 5.0 没有改变这部分语言。)

【讨论】:

    【解决方案2】:

    我会期望这两个调用功能相同 - params 参数是被调用方法中的一个数组。 Jon Skeet 在上一个问题中的示例有效,因为 int 数组与对象数组不协变(因此被视为 new Object[] { new Int[] {1,2,3} }),但在此示例中,FooBars 数组 与对象数组协变,因此您的参数扩展为 objs 参数。

    所有事物的维基百科都涵盖了这个确切的情况:Covariance and contravariance (computer science)

    抱歉,我确信这不是编译器错误。

    编辑:

    你可以实现你想要的:

    Foo(new Object[] { new Bar[] { new Bar(1), new Bar(2), new Bar(3) } });
    

    新编辑(其他作者):

    或者干脆使用:

    Foo((Object)new Bar[] { new Bar(1), new Bar(2), new Bar(3) });
    

    【讨论】:

    • 我爱 PL 人。但在这种特殊情况下,他们显然是在说服自己犯了一个错误!
    • 对不起-我不明白最后的评论
    • 显然应该有一种方法可以使用参数来准确确定方法的参数是什么。通过采用这个定义,C# 5.0 的人创建了一个无法使用 params 正确处理的案例。因此,虽然您显然是对的(只需阅读维基百科页面),但使用的定义显然是错误的!无论如何,有没有办法做我真正想做的事情?
    • @KenBirman - 我认为这真的归结为为什么你需要确切地知道它被称为什么?我想不出很多实际情况是这样的。对于需要它的情况,您可以像 Jon Skeet 指出的那样通过 Dynamic 命名空间来做到这一点
    • 在 Isis2 中,人们构建并行应用程序。假设有人查询一组服务器,每个服务器都返回匹配某种模式的 URL(每个都搜索一个单独的语料库)。有些找到 2 个匹配项,有些找到 4 个。我无法提取类型签名,因此无法为调用者返回合理的响应列表。实际上,我无法区分 g.Reply("url1", "url2") 和 g.Reply(vector-of-urls);我当然支持一般情况,而不仅仅是字符串。
    【解决方案3】:

    请参阅 C# 规范的第 7.5.3.1 节:

    7.5.3.1 适用的函数成员

    当以下所有条件都为真时,就称函数成员是关于参数列表 A 的适用函数成员:

    • A 中的每个参数对应于第 7.5.1.1 节中所述的函数成员声明中的一个参数,任何没有参数对应的参数都是可选参数。
    • 对于 A 中的每个实参,实参的传参方式(即 value、ref 或 out)与对应参数的传参方式相同,并且
      • 对于值形参或形参数组,存在从实参到相应形参类型的隐式转换(第 6.1 节),或者
      • [...一些与refout参数无关的材料...]

    对于包含参数数组的函数成员,如果该函数成员按上述规则适用,则称其正常形式适用。如果包含参数数组的函数成员不适用于其正常形式,则该函数成员可能改为适用于其扩展形式[.]

    由于您传递的数组可以隐式转换为object[],并且由于重载解析更喜欢“普通”形式而不是“扩展”形式,因此您观察到的行为符合规范,并且没有错误。

    除了 Chris Shain 描述的解决方法之外,您还可以将 Bar 从类更改为结构;这使得数组不再隐式转换为object[],所以你会得到你想要的行为。

    【讨论】:

    • 我接受编译器实现规范。但是该规范显然是错误的,因为这个例子越来越清楚地说明了。怎么可能 g.Reply(vector-of-Foos) 与 g.Reply(Foo,Foo'...) 无法区分,而 g.Reply(3, vector-of-foos) 却能按预期工作?是的,我明白为什么会这样,我们在这个线程上的任何人都看到了。但是,为什么在深刻的哲学意义上这是关于如何推断类型的正确决定?他们在树林里有两条路……走错了一条。然后将其作为规范的一部分。还是错了!
    • @KenBirman 在我看来,您的问题的解决方案是放弃使用 params 而只使用一个 object[] 参数;无论如何,都要求您的用户将参数打包到一个数组中。那么就没有歧义了。另外,我会在这里质疑“越野车”的使用;如果语言设计者做出了糟糕的选择,那么无论如何都称其为“糟糕的选择”。当然,现在运行的系统有几个有用的属性。
    • @KenBirman:设计总是在众多不相容的目标之间进行妥协的过程。在您看来是“错误”肯定是该设计过程的不幸后果,但您没有考虑到设计师必须考虑的所有因素。从根本上说,问题源于不安全的数组协方差仅适用于引用类型。这是不幸的,是的,但这不是错误或错误;这是明智的人故意在语言中设计的,他们不得不做出艰难的妥协。
    • @EricLippert - 好吧,我想说,事后看来,数组协方差是 CLR 中的一个错误。从 C# 的角度来看,它更合理,因为底层平台支持它(并且匹配平台有一些好处)。
    • @phoog:不,你倒退了。 CLR 支持它是因为 CLR 希望能够成为任何可能的类 Java 语言的运行时,因此它支持 Java 功能的超集。鉴于 CLR 的通用类型系统支持不安全的数组协变,那么 C# 也很自然地支持它;否则会有点奇怪。 (但是,C# 不支持通用类型系统的所有协变特性;例如,从 int[] 到 uint[] 的强制转换在 C# 中是非法的,即使 CLR 允许这种转换。)
    【解决方案4】:

    我相信你错了肯 这是因为 int[] 不是 object[] 类型,所以编译器假定 int[] 只是传递给方法的参数之一。

    方法如下:

    new Foobar[] { } is object[]; // true
    new int[] { } is object[]; // false
    

    更新: 您可以使方法通用,让编译器知道结构/对象类型作为参数传递:

    void Foo<T>(params T[] objs)
    {
        foreach (T o in objs)
            Console.WriteLine(o.GetType());
    }
    

    【讨论】:

    • Params 正在以相反的方式进行类型推断,Kamar!如果您的解释成立,那么第 3 次而不是第 4 次调用将是打印不需要的输出的调用
    • 这是怎么回事:在 C# 中,结构的实例是一个对象,但是结构的数组不是 object[] 类型的
    • 最好的办法是使方法通用,在这种情况下编译器将决定方法调用的参数类型(结构/对象)!查看更新
    • 假设(错误地)回复都是同一类型。例如,我经常看到 g.Reply(quality, URL-list, ...)。但这会正常工作。事实上,这个问题只出现在一个参数是用户定义对象的向量上,对我来说,这凸显了声称 C# 使用这个定义是正确的荒谬。
    【解决方案5】:

    所以我建议最好的答案是我是对的,这确实是一个编译器错误。虽然我非常清楚地看到了这一点,但您的解释忽略了“params”关键字。事实上,要以这种方式使用协方差,必须忽略 params 关键字,就好像它不重要一样。我假设根本没有合理的解释来编译代码,其中 Params 作为 Foo 类型签名上的关键字出现:要调用您的解释,您需要说服我 myClass[] 应该与 object[] 类型匹配,但考虑到 params 构造,我们甚至不应该问这个问题。事实上,你越想越清楚,这实际上是一个真正的 C# 5.0 编译器错误:编译器忽略了应用 params 关键字。语言规范实际上根本不需要任何更改。我应该得到某种奇怪的徽章,恕我直言!

    【讨论】:

    • 你的分析不正确;这不是编译器错误。所有其他分析都是正确的; Raymond Chen 的回答在我看来是最清楚的。
    • @KenBirman:它与不使用 params 关键字的方法调用相同。正如 Raymond 所说,如果一个方法既适用于它的 normal(忽略参数)形式,也适用于它的 expanded(使用参数)形式,那么 normal 形式获胜我>。 (另外,要小心。我怀疑“类型参数”并不意味着你认为它的意思。)
    • @KenBirman:也许一个理由会有所帮助。 int M(bool b, params string[] p) { return b ? N(p) : O(p); } int N(params object[] q) {...} int O(params object[] r) {...}。对 N 和 O 的调用以其正常形式适用。显然,在这里将new object[] { p } 作为参数传递给 N 或 O 将是一个错误。如果我们不允许您以正常形式调用 N 或 O,那么 就无法编写方法 M.
    • Ken,在@EricLippert 的示例中,N 和O 可能是类的公共表面的一部分,因此可能需要params,或者可能是程序员无法控制的库方法。出路是考虑参数列表(params object[] x)(object[] x) 不同,以解决重载问题。我怀疑这会带来更多的成本而不是收益。一方面,每个此类方法的 params 版本看起来都一样:T M(params object[] a) { return M(a); } T M(object[] a) { // ... real logic here ...
    • @KenBirman,您可以不同意 C# 的行为方式。但这并不会使当前行为成为错误。
    猜你喜欢
    • 2017-04-10
    • 1970-01-01
    • 1970-01-01
    • 2021-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多