为什么没有隐含的类型?

Car*_*oco 42 c#

我想我明白为什么这个小C#控制台应用程序不会编译:

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void WriteFullName(Type t)
        {
            Console.WriteLine(t.FullName);
        }

        static void Main(string[] args)
        {
            WriteFullName(System.Text.Encoding);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

编译器引发CS0119错误:'Encoding' is a type which is not valid in the given context.我知道我可以通过使用typeof()运算符从其名称生成一个Type对象:

        ...
        static void Main(string[] args)
        {
            WriteFullName(typeof(System.Text.Encoding));
        }
        ...
Run Code Online (Sandbox Code Playgroud)

一切都按预期工作.

但对我来说,这种使用typeof()似乎总是多余的.如果编译器知道某个令牌是对给定类型的引用(如CS0119建议的那样)并且它知道某个赋值的目的地(无论是函数参数,变量还是其他)需要引用给定类型,为什么编译器不能将其作为隐式typeof()调用吗?

或者编译器完全有能力采取这一步骤,但是由于可能产生的问题而被选中.这会导致我现在无法想到的任何歧义/易读性问题吗?

Eri*_*ert 84

如果编译器知道某个令牌是对给定类型的引用(如CS0119建议的那样)并且它知道某个赋值的目的地(无论是函数参数,变量还是其他)需要引用给定类型,为什么编译器不能将其作为隐式typeof()调用吗?

首先,你的建议是,无论是"内至外"和"外到内"的编译器的原因在同一时间.也就是说,为了使你的建议功能工作的编译器都必须推断,表达System.Text.Encoding是指一种该内文-以呼叫WriteFullName-需要一个类型.我们怎么知道上下文需要一个类型?分辨率WriteFullName需要重载分辨率,因为它们可能有一百个,并且可能只有一个Type在该位置中作为参数.

所以现在我们必须设计重载决策来识别这个特定情况.过载分辨率已经足够困难了.现在考虑对类型推断的影响.

C#的设计使得在绝大多数情况下您不需要进行双向推理,因为双向推理既昂贵又困难.我们这个地方使用双向推理是lambda表达式,它花了我的大部分时间一年的实施和测试.获得对lambdas的上下文敏感推理是使LINQ工作必需的一个关键特性,因此值得承担双向推理的极高负担.

而且:为什么Type特别?在你的提案中说object x = typeof(T);这不object x = int;合法是完全合法的吗?假设一个类型C具有从用户定义的隐式转换TypeC; 不C c = string;合法吗?

但是,让我们暂时搁置一下,考虑一下你的建议的其他优点.例如,您对此有何建议?

class C {
  public static string FullName = "Hello";
}
...
Type c = C;
Console.WriteLine(c.FullName); // "C"
Console.WriteLine(C.FullName); // "Hello"
Run Code Online (Sandbox Code Playgroud)

这岂不是让你觉得奇怪的c == C,但c.FullName != C.FullName?编程语言设计的基本原则是,您可以将表达式填充到变量中,并且变量的值的行为类似于表达式,但这在此处根本不是真的.

您的提议基本上是每个引用类型的表达式都具有不同的行为,具体取决于它是使用还是分配,这是非常混乱的.

现在,你可能会说,好吧,让我们做一个特殊的语法歧义,其中类型的情况下使用从那里类型的情况下提及,并且有这样的语法.它是typeof(T)!如果我们要处理T.FullNameTType我们说typeof(T).FullName,如果我们想治疗T为在我们说查找一个限定词T.FullName,现在我们已经清晰地消除歧义,这种情况下,无需做任何双向推理.

基本上,基本问题是类型不是C#中的第一类.您可以使用类型执行的操作只能在编译时执行.没有:

Type t = b ? C : D;
List<t> l = new List<t>();
Run Code Online (Sandbox Code Playgroud)

其中l或者是List<C>List<D>取决于的值b.由于类型是非常特殊的表达式,并且特别是在运行时没有值的表达式,因此它们需要具有一些特殊的语法,当它们被用作值时会调用它们.

最后,还有一个关于可能的正确性的争论.如果一个开发人员写的Foo(Bar.Blah)并且Bar.Blah是一个类型,那么他们就犯了错误并认为这Bar.Blah是一个解析为某个值的表达式.赔率并不看好,他们打算通过一TypeFoo.


跟进问题:

为什么在传递给委托参数时可以使用方法组?是因为使用和提及方法更容易区分?

方法组没有成员; 你永远不会说:

class C { public void M() {} }
...
var x = C.M.Whatever;
Run Code Online (Sandbox Code Playgroud)

因为C.M根本没有任何成员.所以问题就消失了.我们永远不会说"好,C.M可以转换为Action并且Action有一个方法Invoke让我们允许C.M.Invoke().这不会发生.再次,方法组不是一流的值.只有在它们被转换为委托之后才会成为第一类值.

基本上,方法组被视为具有但没有类型的表达式,然后可转换性规则确定哪些方法组可转换为代理类型.

现在,如果你要提出一个论点,即一个方法组应该可以隐式转换为MethodInfo并在任何MethodInfo预期的上下文中使用,那么我们必须考虑它的优点.几十年来,有一个提议建立一个infoof运营商(当然,发音为"inofofof!"),MethodInfo当给定一个方法组和PropertyInfo给定一个属性等时,它将返回一个运算符,并且该提议总是因为太多而失败设计工作效益太小.nameof是完成的廉价实施版本.


你没有问过但是看起来密切相关的问题:

你说的C.FullName可能是不明确的,因为如果它是不明确C是一个Type类型C.C#中还有其他类似的含糊之处吗?

是! 考虑:

enum Color { Red }
class C {
  public Color Color { get; private set; }
  public void M(Color c) { }
  public void N(String s) { }
  public void O() {
    M(Color.Red);
    N(Color.ToString());
  }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,巧妙地称为"颜色问题",C#编译器设法弄清楚Color在调用中M意味着类型,而在调用中N意味着this.Color.在"颜色颜色"的规范中搜索,您将找到规则,或参阅博客文章颜色颜色.

  • @ pm100:"弄清楚开发人员的意图并做到这一点"是Visual Basic的设计原则.它不是*C#的设计原则!C#的设计原则是"如果开发人员的意图不明显,或者看起来可能存在错误,请将其引入开发人员的注意".这两个原则都是合理的,但它们是对立的. (24认同)
  • 海报就像是,"该死!埃里克只是回答." 大声笑 (11认同)
  • @ jpmc26:特异性和迂腐相辅相成(实际上只是对同一个概念的不同).C#为您提供更高的特异性(以更多的迂腐为代价),而VB.Net(以及您提到的其他语言)可以减少您的迂腐(以较低的特​​异性为代价).两种语言都试图最大限度地发挥积极的特质,但每种语言都有自己的负面后果.仅仅因为两件事是不同的并不意味着一件客观地高于另一件事.仅仅因为法拉利比面包车更快并不意味着UPS使用法拉利来提供包裹. (4认同)
  • @EricLippert和其他人.我的评论是100%误解.我的意思是你的解释是正确的.我的意思是"埃里克的解释是非常复杂的解释,为什么猜测是坏的,一个更简单的例子,为什么它的不好只是说'我们不妨允许int x ="42"'.我绝不建议那样做我们允许'int x ="42"' (3认同)
  • @EricLippert当你在那里调用VB方法时,我不得不对该注释进行unupvote.这也是JavaScript和PHP的原理,而且......好吧,让我们把它留在,不,这根本不合理.大声笑. (2认同)