【问题标题】:CallerMemberName is empty if used from base class constructor and not initialized如果从基类构造函数中使用且未初始化,则 CallerMemberName 为空
【发布时间】:2022-01-04 16:11:36
【问题描述】:

我有两个这样的课程:

public class test1: BaseClass
{
    public test1() : base()
    {
    }
...

public class BaseClass
{
    public BaseClass(
        [CallerMemberName]string membername ="",
        [CallerFilePath] string path = "")
    {
        var sf = new System.Diagnostics.StackTrace(1).GetFrame(0);
    }

如果我在调用 base 时指定 test1 ctor - 我会正确初始化 membernamepath,但如果没有 - 编译器会生成默认构造函数调用,并且 membernamepath 都是空的。

这是错误还是功能?

(Visual Studio 2019 16.11.8,net core 3.1 或 net 5.0)。

【问题讨论】:

  • 文档没有明确说明这是否是所需的行为。当然,参数可以通过两种方式进行,因为调用不是显式的(请注意,“错误”的信息永远不是参数,因为这始终是可能的,因为您可以自己合法地将参数值指定为您想要的任何值)。快速搜索在GH 中也没有发现适用的问题,尽管CallerMemberName 在该回购之前,所以我也不希望出现这样的问题。我认为从马口中得到官方消息的新人不会出错。
  • 我猜这是意料之中的,因为编译器生成的默认构造函数调用将显式传递您在可选参数中指定的值(即空字符串)

标签: c# callermembername


【解决方案1】:

这可能是预期的行为,因为编译器生成的默认构造函数调用将显式传递您在可选参数中指定的值(即空字符串)。所以如果你有这样的东西:

public BaseClass(
    [CallerMemberName]string membername = "default-member-name",
    [CallerFilePath] string path = "default-path")
{
    var sf = new System.Diagnostics.StackTrace(1).GetFrame(0);
}

然后这段代码...

public class test1 : BaseClass
{
    public test1()
    {
    }
}

... 被转换成这个:

public class test1 : BaseClass
{
    public test1()
        : base("default-member-name", "default-path")
    {
    }
}

【讨论】:

  • 这当然是很容易解释的行为,但它有一个明显的实现这一事实并不一定意味着它是正确的或打算做的事情。故事的结局仍然是,尽管从同一个调用站点调用,但显式调用基本构造函数与隐式调用它具有不同的行为,这至少是值得商榷的。
  • @JeroenMostert 这是添加的构造函数的组合使用的默认值,因为它会覆盖编译器将插入的任何内容。请记住,编译器会将上面代码中的值替换为public test1() : base(".ctor", "some-path")。我不认为这是“对”或“错”,它就是这样。当然,这可能值得开发人员在 repo 上讨论。
  • 没错,但问题是它是对是错,我们不知道,所以这就是为什么这在技术上不是一个答案:) 编译器在这里做出了一个特别的选择,这是可以辩护的,但是正在做出相反的选择。编译器必须做出 a 选择(它不能选择 not 调用基本构造函数,因为这会破坏其他语义,并且它不能选择提供参数值,因为这不是在 IL 级别上的工作方式),但它所做的选择似乎没有记录在案,因此很难判断它是否是有意的。
【解决方案2】:

我用谷歌搜索了“c# compiler github”并最终找到了https://github.com/dotnet/roslyn

通过CallerMemberName 搜索后 - 我设法找到了这个问题的答案:

https://github.com/dotnet/roslyn/issues/53757

它提到这是设计使然。

但快速浏览票证让我想到 - “我可以将属性用于相同目的吗?”

因为我所做的是单元测试——我已经为此目的重新编码了我自己的属性:TestAttribute => FactAttribute 并从 NUnit 方法信息中解析该属性,然后返回 文件路径和方法名。

public class FactAttribute : TestAttribute
{
    public string FunctionName { get; }
    public string FilePath { get;  }

    public FactAttribute( [CallerMemberName] string functionName = "", [CallerFilePath] string filePath = "")
    {
        FunctionName = functionName;
        FilePath = filePath;
    }
}

[TestFixture]
public class BaseClass
{
    /// <summary>
    /// Accesses private class type via reflection.
    /// </summary>
    /// <param name="_o">input object</param>
    /// <param name="propertyPath">List of properties in one string, comma separated.</param>
    /// <returns>output object</returns>
    object getPrivate(object _o, string propertyPath)
    {
        object o = _o;
        var flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
        
        foreach (var name in propertyPath.Split('.'))
        {
            System.Type type = o.GetType();

            if (char.IsUpper(name[0]))
                o = type.GetProperty(name, flags).GetValue(o);
            else
                o = type.GetField(name, flags).GetValue(o);
        }

        return o;
    }

    [SetUp]
    public void EachSpecSetup()
    {
        var mi = (MemberInfo)getPrivate(TestContext.CurrentContext.Test, "_test.Method.MethodInfo");
        FactAttribute attr = mi.GetCustomAttribute<FactAttribute>();
        string path = attr.FilePath;
        string funcName = attr.FunctionName;
    }

这允许确定来自哪个文件以及来自哪个方法调用。

【讨论】:

    猜你喜欢
    • 2018-06-12
    • 2020-02-13
    • 1970-01-01
    • 2013-05-23
    • 2011-02-22
    • 1970-01-01
    • 2019-07-31
    • 2014-07-02
    • 1970-01-01
    相关资源
    最近更新 更多