【问题标题】:Why do the C# Caller Info Attributes need a default value?为什么 C# 调用者信息属性需要默认值?
【发布时间】:2014-08-13 00:38:09
【问题描述】:

我刚刚发现了 C# 5 调用者信息属性 (http://msdn.microsoft.com/en-us/library/hh534540.aspx)。

这似乎是一个非常有用的功能,我已经阅读了一些文档 (http://www.codeproject.com/Tips/606379/Caller-Info-Attributes-in-Csharp)。

但是,我只是想知道:为什么必须传入默认值?它们是如何使用的?

以下示例代码显示了如何使用调用者信息属性:

public static void ShowCallerInfo([CallerMemberName] 
  string callerName = null, [CallerFilePath] string 
  callerFilePath = null, [CallerLineNumber] int callerLine=-1)
{
    Console.WriteLine("Caller Name: {0}", callerName);
    Console.WriteLine("Caller FilePath: {0}", callerFilePath);
    Console.WriteLine("Caller Line number: {0}", callerLine);
}

我的问题是:nullnull-1 的默认值是什么?上面的代码有什么不同:

public static void ShowCallerInfo([CallerMemberName] 
  string callerName = "hello", [CallerFilePath] string 
  callerFilePath = "world", [CallerLineNumber] int callerLine=-42)
{
    Console.WriteLine("Caller Name: {0}", callerName);
    Console.WriteLine("Caller FilePath: {0}", callerFilePath);
    Console.WriteLine("Caller Line number: {0}", callerLine);
}

按照我的理解,这些是可选参数,编译器提供默认值,替换我们分配的任何默认值。在那种情况下,我们为什么要指定默认值?是否存在一些奇怪的边缘情况,编译器可能无法填写值,并诉诸我们提供的默认值?如果不是,那么为什么要求我们输入这些数据?要求开发人员提供永远不会使用的默认值似乎相当笨拙。

免责声明:我尝试用谷歌搜索,但找不到任何东西。我几乎不敢问关于 SO 的问题,因为大多数这样的新手问题都会遇到这样的敌意,但作为最后的手段,我会冒险提出一个问题。版主/高级用户,无意冒犯 - 在发布此之前,我确实尝试过在其他地方查找信息。

【问题讨论】:

  • 请不要误会直截了当的“你尝试了什么?”充满敌意地回答。鼓励简洁,无论它看起来多么冷酷。一个问题的新鲜度可以通过你获得的谷歌点击数来判断。如果你在谷歌上找不到它的查询范围很广,这可能不是一个微不足道的问题。这个问题不是微不足道的,我喜欢阅读答案。

标签: c# c#-5.0 callermembername


【解决方案1】:

这些参数需要一个默认值,因为调用者信息属性是使用可选参数实现的,而可选参数需要一个默认值。这样就可以简单地调用ShowCallerInfo(),而无需发送任何参数,编译器将添加相关参数。

为什么一开始就使用可选参数来实现是一个更深层次的问题。他们可能没有,编译器需要在实际编译开始之前“注入”这些参数,但与可选参数相反(这是一个C# 4.0功能)它将不向后兼容,并且会破坏其他编译器/代码分析工具

【讨论】:

  • 我是否正确理解开发 C# 的“开发人员”(主开发人员?)出于向后兼容性等原因,以一种有点笨拙的方式实现此功能?旧版本的 c# 编译器不会不支持新属性(因此仍然会出错?)这对向后兼容性有何帮助?
  • @Omaer 如果您使用支持可选参数的旧编译器,则代码可以编译,因为有默认值。您只需不断打印默认值。我不会称其为 hack,更多地依赖于现有功能。
  • 所以你的意思是说旧的编译器会忽略[CallerMemberName](和其他两个)属性,即使他们不知道这些属性是做什么的?
  • @Omaer 是的,为什么不呢?您可以创建所需的任何属性并将其应用于参数/方法/等。编译器没有理由介意。 然而这些类是 .net 4.5 的一部分,因此当使用较旧的框架时,您需要将它们实现为存根。
  • 好的,那是有道理的——仍然需要将属性实现为存根。我不确定这是否是我们的主开发人员实现此功能的最佳方式,但我想声明一些属性存根肯定比从代码中删除所有属性更容易。
【解决方案2】:

它们需要默认值,以便可以将参数标记为可选。如果您在调用方法时没有指定参数,编译器将为您注入正确的值,但前提是您没有指定它们。如果你这样做了,那么这些属性的“魔法”就不会发生。

据我了解,这些属性不会影响运行时,纯粹用于编译时,因此默认值只是为了确保参数是可选的。

【讨论】:

    【解决方案3】:

    换句话说,在被调用者(属性应用于参数的地方调用的方法)上,参数必须存在。另一方面,调用者必须传递这些参数,编译器允许未指定参数的唯一方法是给它一个默认值。

    虽然属性可能会影响代码生成或运行时执行,但如果删除所有属性,则源必须是有效的。因此必须在被调用者上定义默认值,编译器只是根据应用的属性生成参数值,而不是在被调用者上定义的当前默认值。

    【讨论】:

      【解决方案4】:

      其他答案中提到的一些用途似乎都有效。

      他们错过的一点是,这些实际上告诉编译器用静态值重写对这些函数的调用。但这些值并不总是可用的。在这些情况下,编译器不会重写调用,因此将使用默认值。

      示例:

      1. 如果您使用具有这些属性的函数编译 dll,将其暴露给内存中生成的脚本(例如通过 Roslyn),则该代码可能没有“文件名”。

        • 有人可能会争辩说,生成的脚本应该使用提供的参数值来调用方法,但这意味着编译器可以静态编译的相同代码(即csc mycodefile.cs)在运行时无法与动态编译一起使用,即使使用相同的会造成混淆的上下文。
      2. 您也可以通过反射调用此方法,编译器根本不知道添加这些值。

        • 可以构建运行时/BCL 以强制反射调用者提供这些值,但无论如何,在该上下文中文件名和行号没有任何有意义的值。
      3. 您还可以将[CallerMemberName] 添加到属性构造函数并将该属性应用于类。这将没有成员名称。

      请参阅文档中的Member Names

      属性构造函数

      应用属性的方法或属性的名称。如果属性是成员中的任何元素(例如参数、返回值或泛型类型参数),则此结果是与该元素关联的成员的名称。

      无包含成员(例如,程序集级别或应用于类型的属性)

      可选参数的默认值。

      如果您想隐藏调用者信息,也可以显式提供值。因为某些原因。 (可能如果您使用代码混淆,这些值可能不会受到影响,因此您可能希望在这些情况下提供这些值以隐藏调用者)。

      请参阅文档中的Remarks

      调用者信息值在编译时以文字形式发送到中间语言 (IL)。与 StackTrace 属性的异常结果不同,结果不受混淆的影响。

      您可以显式提供可选参数来控制调用者信息或隐藏调用者信息。

      【讨论】:

      • 这对我来说比上面写的其他答案更有意义。谢谢!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-12-11
      • 1970-01-01
      • 1970-01-01
      • 2012-07-16
      • 1970-01-01
      • 1970-01-01
      • 2010-12-13
      相关资源
      最近更新 更多