我想我明白为什么这个小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
具有从用户定义的隐式转换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"
Run Code Online (Sandbox Code Playgroud)
这岂不是让你觉得奇怪的是c == C
,但c.FullName != C.FullName
?编程语言设计的基本原则是,您可以将表达式填充到变量中,并且变量的值的行为类似于表达式,但这在此处根本不是真的.
您的提议基本上是每个引用类型的表达式都具有不同的行为,具体取决于它是使用还是分配,这是非常混乱的.
现在,你可能会说,好吧,让我们做一个特殊的语法歧义,其中类型的情况下使用从那里类型的情况下提及,并且有这样的语法.它是typeof(T)
!如果我们要处理T.FullName
的T
是Type
我们说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
是一个解析为某个值的表达式.赔率并不看好,他们打算通过一Type
对Foo
.
跟进问题:
为什么在传递给委托参数时可以使用方法组?是因为使用和提及方法更容易区分?
方法组没有成员; 你永远不会说:
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
.在"颜色颜色"的规范中搜索,您将找到规则,或参阅博客文章颜色颜色.