【问题标题】:Custom print of object in C# InteractiveC# Interactive 中对象的自定义打印
【发布时间】:2018-04-19 09:47:42
【问题描述】:

考虑这个MCVE 类:

public class Foo<T>
{
    private readonly T value;

    public Foo(T value)
    {
        this.value = value;
    }
}

当我在 C# Interactive 中评估和打印这样一个对象时,它看起来像这样:

> new Foo<string>("bar")
Foo<string> { }

这没什么用。我希望它看起来像这样:

Foo<string> { "bar" }

我该怎么做?

我尝试过像这样覆盖ToString

public override string ToString()
{
    return $"Foo{typeof(T)} {{ {value} }}";
}

这并不完全符合我的要求:

> new Foo<string>("bar")
[Foo<System.String> { bar }]

此输出至少存在三个问题:

  1. 值不在引号中。我希望它是 "bar" 而不是 bar
  2. 类型参数显示为System.String,而不是string
  3. 整个值用方括号括起来。这是我最不关心的问题。

有没有办法让 C# 交互式显示具有自定义格式的对象?

我知道我可以将公共属性添加到类中以显示值,但由于封装问题我不想这样做。不过,要清楚,这就是我的意思:

public class Foo<T>
{
    public Foo(T value)
    {
        Value = value;
    }

    public T Value { get; }
}

打印出来的结果更接近我想要的:

> new Foo<string>("bar")
Foo<string> { Value="bar" }

但是,正如我所写,我不想添加公共属性。

如何让它表现如下?

> new Foo<string>("bar")
Foo<string> { "bar" }
> new Foo<int>(42)
Foo<int> { 42 }

请注意,当使用字符串以外的任何内容时(例如 int),不应使用引号。

【问题讨论】:

  • 也许说的很明显,虽然我不知道如何回答这个问题,但一种途径是了解 C# 交互在评估对象时调用的内容。看起来它循环通过公共属性/成员(我猜是使用反射)并漂亮地打印出来,或者如果它在类中被覆盖,则调用 ToString 。也许它提供了一个钩子来用其他东西替换这个逻辑。我很快在文档中查找了类似的内容,但没有找到。
  • 您是否尝试过添加DebuggerDisplay 属性?
  • @khillang 我有,但显然有点太随意了。我认为这是正确的解决方案。如果您想添加答案,我很乐意接受。谢谢。

标签: c# .net c#-interactive


【解决方案1】:

您可以使用[DebuggerDisplay] 属性来自定义对象打印。除了覆盖ToString(),您还可以在此属性中使用任何方法/属性。例如:

[DebuggerDisplay("{GetDebuggerDisplay(), nq}")]
public class Foo<T>
{
    private readonly T value;

    public Foo(T value)
    {
        this.value = value;
    }

    private string GetDebuggerDisplay()
    {
        var val = value is string ? "\"" + value + "\"" : value?.ToString();
        return $"Foo<{typeof(T).Name.ToLower()}> {{ {val} }}";
    }
}

这避免了必须覆盖ToString(),或使用不同的实现。比如返回T value的字符串表示。

您需要添加一个 switch/case 来将类名称(例如 Int32)转换为 int

[DebuggerDisplay] 属性removes the quotes around the valuenq 部分。

结果如下:

> new Foo<string>("bar")
Foo<string> { "bar" }

如需进一步参考,请查看 Jared Parson 关于[DebuggerDisplay] 属性的优秀博文:https://blogs.msdn.microsoft.com/jaredpar/2011/03/18/debuggerdisplay-attribute-best-practices/

【讨论】:

    【解决方案2】:

    如果您可以像在第一次尝试中那样覆盖ToString(),那么您可以在那里做任何您喜欢的事情。这让您更接近您想要的,您可以根据需要对其进行修改:

    public override string ToString()
    {
        string v = value is string ? $"\"{value}\"" : $"{value}";
        string t = typeof(T).Name.ToLower();
        return $"Foo<{t}> {{ {v} }}";
    }
    

    【讨论】:

    • typeof(T).Name.ToLower() 不会为您提供所有类型的内置 C# 类型,例如typeof(int).Name.ToLower() 将返回 int32
    • @Krimson 是的,这很明显,这就是为什么我说“这让你更接近你想要的,你可以根据需要进行修改”。修复整数类型(intshortlong 等)很容易,只需一个简单的switch 扩展我为string 所做的工作以包含所有原始类型。真正困难的是非原始类型,如int?List&lt;T&gt; 等......
    【解决方案3】:

    正如 Kristian Hellang 所建议的,这可以通过在类中添加 [DebuggerDisplay] 属性来简单轻松地解决:

    [DebuggerDisplay("{ value }")]
    public class Foo<T>
    {
        private readonly T value;
    
        public Foo(T value)
        {
            this.value = value;
        }
    }
    

    这解决了所有问题:

    > new Foo<string>("bar")
    Foo<string>("bar")
    > new Foo<int>(42)
    Foo<int>(42)
    > new Foo<DateTime>(new DateTime(2018, 4, 23))
    Foo<DateTime>([23.04.2018 00:00:00])
    

    渲染没有使用大括号,而是使用构造函数指示的普通括号,但我只觉得合适。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-04-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-11
      • 1970-01-01
      相关资源
      最近更新 更多