代码之家  ›  专栏  ›  技术社区  ›  Carvo Loco

为什么没有隐式类型的?

c#
  •  43
  • Carvo Loco  · 技术社区  · 8 年前

    我想我理解为什么这个小型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() 操作员:

            ...
            static void Main(string[] args)
            {
                WriteFullName(typeof(System.Text.Encoding));
            }
            ...
    

    一切正常。

    但对我来说 typeof() 总是显得有些多余。如果编译器知道某个标记是对给定类型的引用(如错误CS0119所示),并且它知道某个赋值的目标(无论是函数参数、变量还是其他)需要对给定类型的引用,那么为什么编译器不能将其视为隐式 typeof() 呼叫

    或者,编译器完全有能力执行这一步骤,但由于可能产生的问题,选择不执行这一步骤。这会不会导致我现在无法想到的任何歧义/易读性问题?

    1 回复  |  直到 8 年前
        1
  •  84
  •   Peter Mortensen Pieter Jan Bonestroo    8 年前

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

    首先,您的建议是编译器同时考虑“从内到外”和“从外到内” 同时 。也就是说,为了使您提出的功能正常工作,编译器必须同时推断表达式 System.Text.Encoding 引用类型 和 这是上下文--调用 WriteFullName --需要类型。 我们如何知道上下文需要一个类型 ?决议 可写名称 需要 过载分辨率 因为可能有一百个,也许只有一个需要 Type 作为这种立场的论据。

    因此,现在我们必须设计重载解决方案来识别这种特定情况。过载解决已经够难了。现在考虑一下类型推断的含义。

    C#的设计使您在绝大多数情况下不需要进行双向推理,因为双向推理是 昂贵且困难 .我们 做 使用双向推理是 兰巴斯 ,我花了整整一年的时间来实现和测试它。在lambdas上获取上下文相关推理是 使LINQ工作所必需的 因此,获得双向推理正确的极高负担是值得的。

    此外:为什么是 类型 特殊的这样说完全合法 object x = typeof(T); 所以不应该 object x = int; 你的提案合法吗?假设一个类型 C 具有用户定义的隐式转换 类型 到 C ;不应该 C c = string; 合法吗?

    但是,让我们暂时搁置一下,考虑一下你的建议的其他优点。例如,您打算对此做些什么?

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

    你不觉得 奇怪的 那个 c == C 但是 c.FullName != C.FullName ?编程语言设计的一个基本原则是,可以将表达式填入变量和变量的值中 行为类似于表达式 ,但这里根本不是这样。

    你的建议基本上是 每个引用类型的表达式都有不同的行为,这取决于它是被使用还是被赋值 ,也就是说 超级混乱 。

    现在,您可能会说,好吧,让我们用一种特殊的语法来消除类型的歧义 习惯于 从类型为 提及 和 有这样一种语法 .是的 typeof(T) 哦!如果我们想治疗 T.FullName 像 T 存在 类型 我们说 typeof(T).FullName 如果我们想治疗 T 作为查找中的限定符,我们说 T、 全名 ,现在我们清楚地消除了这些案例的歧义 无需进行任何双向推理 。

    基本上,根本问题是 类型不是C中的第一类# .你可以用类型做一些事情 只有 在编译时执行。没有:

    Type t = b ? C : D;
    List<t> l = new List<t>();
    

    哪里 l 要么是 List<C> 或 List<D> 取决于 b 。因为类型是 非常特殊的表达式 ,特别是 运行时没有值的表达式 它们需要有一些特殊的语法,当它们被用作值时调用。

    最后,还有一个关于可能的正确性的论点。如果开发人员编写 Foo(Bar.Blah) 和 Bar.Blah 是一种类型,可能性很大他们犯了个错误 酒吧胡说八道 是解析为值的表达式。他们打算通过 类型 到 Foo 。


    后续问题:

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

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

    class C { public void M() {} }
    ...
    var x = C.M.Whatever;
    

    因为 C.M 根本没有任何成员。这样问题就消失了。我们从不说“好吧, C、 M级 可转换为 Action 和 行动 有一个方法 Invoke 所以让我们允许 C.M.Invoke() .那根本不会发生。再一次 方法组不是第一类值 。只有将其转换为委托后,它们才成为第一类值。

    基本上,方法组被视为具有 价值 但是没有 类型 ,然后 可兑换性 规则确定哪些方法组可转换为哪些委托类型。

    现在,如果您要论证一个方法组应该隐式地转换为 MethodInfo 并用于 MethodInfo方法信息 这是意料之中的,那么我们就必须考虑它的优点。几十年来,有人提议 infoof 运算符(当然发音为“in foof”)这将返回 MethodInfo方法信息 当给定方法组和 PropertyInfo 当你得到一处房产等东西时,这项提案总是失败,因为设计工作太多,效益太少。 nameof 是实现已完成的版本的廉价工具。


    一个你没有问但似乎有密切关系的问题:

    你说过的 C.FullName 可能模棱两可,因为不清楚 C 是一个 类型 或 类型 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());
      }
    }
    

    在这个被巧妙地称为“颜色问题”的场景中,C编译器设法解决了这个问题 Color 在呼叫中 M 指 类型 ,在调用 N ,这意味着 this.Color 。在“颜色颜色”规范中搜索,您将找到规则,或查看博客帖子 Color Color 。