nameof()在编译时计算吗?

132
在C# 6中,可以使用nameof()操作符获取包含变量或类型名称的字符串。
这是否在编译时评估,还是通过某些Roslyn API在运行时评估?

Roslyn是新的编译器平台,仅在编译时使用。 - Paulo Morgado
2
@PauloMorgado 这不是真的,你可以在运行时使用Roslyn来做一些事情。例如构建实时代码编辑器或使用Roslyn的解析功能来处理树或表达式等内容。 - Chris Marisic
@ChrisMarisic 这是我的印象,但由于我对这个主题的了解有限(因此我提出了问题),所以我没有回应。 我确实发现了这个网站:http://scriptcs.net/,它是 Roslyn 功能强大的很好的例子,我相信它可以做一些运行时的东西,但我可能错了,因为我对它不是很了解。 - Gigi
@ChrisMarisic,所以你的意思是可以使用Roslyn从源代码构建实时代码,而不是从正在运行的二进制文件中构建。您仍然使用Roslyn将源代码转换为二进制文件,这些二进制文件不会使用Roslyn来更改它们。如果您在运行时绝对不能使用Roslyn,则永远无法编译任何代码。 - Paulo Morgado
2个回答

142
是的。 nameof() 在编译时被评估。从最新版本的规范中可以看出:
“nameof”表达式是一个常量。在所有情况下,“nameof(...)”都会在编译时进行评估以生成字符串。它的参数在运行时不会被评估,并被视为无法到达的代码(但它不会发出“无法到达的代码”警告)。
来自 nameof operator - v5 您可以通过此TryRoslyn示例看到,其中包含以下内容:
public class Foo
{
    public void Bar()
    {
        Console.WriteLine(nameof(Foo));
    }
}

被编译和反编译后的结果如下:
public class Foo
{
    public void Bar()
    {
        Console.WriteLine("Foo");
    }
}

它的运行时等效物是:

public class Foo
{
    public void Bar()
    {
        Console.WriteLine(typeof(Foo).Name);
    }
}

正如评论中提到的那样,当你在一个泛型类型中使用nameof来引用类型参数时,不要期望能够获取实际动态类型的名称,而只能获取类型参数本身的名称。因此,以下代码:

public class Foo
{
    public void Bar<T>()
    {
        Console.WriteLine(nameof(T));
    }
}

将变成这样:

public class Foo
{
    public void Bar<T>()
    {
        Console.WriteLine("T");
    }
}

这里的“编译时”是什么意思?编译成MSIL还是编译成本机代码? - user541686
7
C#编译器会生成中间语言IL。 - i3arnon
3
可以在switch case语句中使用nameof吗? - Spell
2
@Spell - i3arnon
这适用于类型。如何在运行时获取类型成员的名称?当指示诸如 control.ValueMember = <member name> 的内容时,这对于 DataSource 非常有用,特别是在使用 DotFuscator 后。 - Leonardo Hernández

62
我希望您能够为 @I3arnon提供的答案 做出证明,证明它是在编译时进行评估的。
假设我想使用 nameof 运算符在控制台上打印变量的名称:
 var firstname = "Gigi";
 var varname = nameof(firstname);
 Console.WriteLine(varname); // Prints "firstname" to the console

当您检查生成的 MSIL 时,您会发现它等同于字符串声明,因为使用 ldstr 运算符将字符串的对象引用推送到堆栈上:

IL_0001: ldstr "Gigi"
IL_0006: stloc.0
IL_0007: ldstr "firstname"
IL_000c: stloc.1
IL_000d: ldloc.1
IL_000e: call void [mscorlib]System.Console::WriteLine(string)

你会注意到,声明firstname字符串和使用nameof运算符在MSIL中生成相同的代码,这意味着nameof与声明一个字符串变量一样高效。

5
如果将MSIL反编译为源代码,反编译器能否轻松识别它是一个nameof运算符而不是纯粹的硬编码字符串? - ADTC
11
这是个好问题!如果你想要得到详细的解释,你可以在 Stack Overflow 上发布一个新问题 :).. 但简短的回答是反编译器将无法确定这是一个 nameof 操作符,而会使用一个字符串字面量代替。我已经通过ILSpy和Reflector验证了这一点。 - Faris Zacina
3
由于"nameof"被完全替换为"load-a-string-onto-the-stack",反编译器又怎么能猜出那是一个"nameof"而不是一个简单的常量参数呢? - quetzalcoatl
3
如果你有一个反编译器,并且它能够主动假定字符串字面量来自nameof,那么你可以使用它来帮助自动化转换到这种范式。或者说,这是一个梦想。 - Caleth
3
回到以前并不能完全奏效,因为nameof()也适用于你拥有引用的任何东西(例如nameof(someObject.SomeProperty)将被翻译为"SomeProperty")。当反编译器只有一个字符串字面量时,它应该如何可靠地找出这个字符串来自哪个对象呢?请注意,不应更改原始含义。 - Adwaenyth
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接