我想我明白为什么这个小型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()
调用吗?
或许编译器完全有能力采取这一步骤,但是由于可能产生的问题而被选中。这会导致我现在无法想到的任何歧义/易读性问题吗?
答案 0 :(得分:85)
如果编译器知道某个标记是对给定类型的引用(如CS0119建议的那样)并且它知道某个赋值的目标(无论是函数参数,变量还是其他)都需要对给定的引用类型,为什么编译器不能将其作为隐式typeof()调用?
首先,你的建议是编译器推理内外两个"和"从外到内" 同时。也就是说,为了使您的建议功能发挥作用,编译器必须推断表达式System.Text.Encoding
引用类型和表示上下文 - 对WriteFullName
的调用 - - 需要一种类型。 我们如何知道上下文需要类型? WriteFullName
的分辨率需要重载决策,因为可能有一百个,并且可能只有一个在该位置使用Type
作为参数。
所以现在我们必须设计重载决策来识别这个特定情况。过载分辨率已经足够困难了。现在考虑对类型推断的影响。
C#的设计使得在绝大多数情况下您不需要进行双向推理,因为双向推理昂贵且困难。我们做使用双向推理的地方是 lambdas ,我花了一年多的时间来实现和测试它。获得对lambda的上下文敏感推理是使LINQ工作所必需的一个关键特性,因此值得承担双向推理的极高负担。
此外:为什么Type
特别?说object x = typeof(T);
是完全合法的,所以不应该object x = int;
在你的提案中合法吗?假设类型C
具有从Type
到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
Type
我们说typeof(T).FullName
,如果我们想将T
视为查找中的限定符,我们会说{ {1}},现在我们已经干净地消除了这些的歧义,而无需进行任何双向推理。
基本上,基本问题是类型不是C#中的第一类。您可以使用仅在编译时执行的类型执行某些操作。没有:
T.FullName
其中Type t = b ? C : D;
List<t> l = new List<t>();
为l
或List<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.M
而Action
有一个方法Action
所以让我们允许Invoke
。这不会发生。同样,方法组不是第一类值。只有将它们转换为代表后才能成为一流的价值观。
基本上,方法组被视为具有值但没有类型的表达式,然后 convertibility 规则确定哪些方法组是可转换为代理类型。
现在,如果您打算将方法组隐式转换为C.M.Invoke()
并在任何预期MethodInfo
的上下文中使用,那么我们就会考虑其优点。几十年来,有一个提议要建立MethodInfo
运算符(发音为&#34; in-foof&#34;当然!),当给定方法组和{{infoof
时,它将返回MethodInfo
1}}当给出一个属性等等时,该提议总是失败,因为太多的设计工作对于太少的好处。 PropertyInfo
是完成的廉价工具。
一个你没有问过的问题,但这个问题似乎密不可分:
您说
nameof
可能含糊不清,因为不清楚C.FullName
是C
还是类型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 。