为什么没有隐含的类型?

时间:2018-04-24 21:46:03

标签: 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);
        }
    }
}

编译器引发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));
        }
        ...

一切都按预期工作。

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

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

1 个答案:

答案 0 :(得分:85)

  

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

首先,你的建议是编译器推理内外两个"和"从外到内" 同时。也就是说,为了使您的建议功能发挥作用,编译器必须推断表达式System.Text.Encoding引用类型表示上下文 - 对WriteFullName的调用 - - 需要一种类型。 我们如何知道上下文需要类型WriteFullName的分辨率需要重载决策,因为可能有一百个,并且可能只有一个在该位置使用Type作为参数。

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

C#的设计使得在绝大多数情况下您不需要进行双向推理,因为双向推理昂贵且困难。我们使用双向推理的地方是 lambdas ,我花了一年多的时间来实现和测试它。获得对lambda的上下文敏感推理是使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"

c == Cc.FullName != C.FullName 奇怪会不会打击你?编程语言设计的基本原则是,您可以将表达式填充到变量中,并且变量的值的行为类似于表达式,但这在这里完全不正确。

您的提案基本上是引用某个类型的每个表达式都有不同的行为,具体取决于它是使用还是分配,而且超级混乱

现在,你可能会说,好吧,让我们制作一个特殊的语法来消除使用类型的情况< 的情况,并且有这样的语法。这是typeof(T)!如果我们要将T.FullName视为T Type我们说typeof(T).FullName,如果我们想将T视为查找中的限定符,我们会说{ {1}},现在我们已经干净地消除了这些的歧义,而无需进行任何双向推理

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

T.FullName

其中Type t = b ? C : D; List<t> l = new List<t>(); lList<C>,具体取决于List<D>的值。由于类型是非常特殊的表达式,特别是在运行时没有值的表达式,因此它们需要有一些特殊的语法,当它们被用作值时会调用它们。

最后,还有一个关于可能的正确性的争论。如果开发人员写了b并且Foo(Bar.Blah)是一种类型,那么他们犯了错误并认为Bar.Blah是一个解析为某个值的表达式。他们打算将Bar.Blah传递给Type

的可能性不大

跟进问题:

  

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

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

Foo

因为class C { public void M() {} } ... var x = C.M.Whatever; 根本没有任何成员。所以问题就消失了。我们永远不会说&#34;好吧,C.M可以转换为C.MAction有一个方法Action所以让我们允许Invoke。这不会发生。同样,方法组不是第一类值。只有将它们转换为代表后才能成为一流的价值观。

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

现在,如果您打算将方法组隐式转换为C.M.Invoke()并在任何预期MethodInfo的上下文中使用,那么我们就会考虑其优点。几十年来,有一个提议要建立MethodInfo运算符(发音为&#34; in-foof&#34;当然!),当给定方法组和{{infoof时,它将返回MethodInfo 1}}当给出一个属性等等时,该提议总是失败,因为太多的设计工作对于太少的好处。 PropertyInfo是完成的廉价工具。

一个你没有问过的问题,但这个问题似乎密不可分:

  

您说nameof可能含糊不清,因为不清楚C.FullNameC还是类型Type 。 C#中还有其他类似的含糊之处吗?

是的!考虑:

C

在这种情况下,聪明地调用了#34;颜色问题&#34;,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()); } } 中的Color表示类型< / em>,以及M的调用中,它表示N。在规范中搜索&#34;颜色颜色&#34;并且您将找到该规则,或查看博客文章 Color Color