【问题标题】:How to serialize interface default property?如何序列化接口默认属性?
【发布时间】:2020-09-15 10:25:42
【问题描述】:

这个问题复活了,因为我在评论“我的坏”时错了。 这个问题是存在的,或者我错过了什么。对于大众需求,这里有一个最小的可重现示例:

minimal reproducable example


我有一个带有默认属性的接口:

public interface INotification
{
    // ...
    Severity Severity { get; set; } // Severity is an Enum
    // ...
    public string SeverityName => Severity.ToString().ToLower();
}

我有一个实现类

public class Notification : INotification
{
    // ...
    public Severity Severity { get; set; }
    // ...
    // This class does not override default implementation of SeverityName
} 

问题

Notification 类没有SeverityName 属性...这很令人惊讶,但我可以接受,通过INotification 接口访问通知实例。

但是,我想用System.Text.Json 序列化这个类。我如何也序列化SeverityName 属性?

【问题讨论】:

  • 你有没有尝试序列化类的实例?有什么问题吗?您指的是哪个序列化程序? xml? json? ...?
  • @HimBromBeere OP 提到了System.Text.Json
  • 你试过了吗?什么没用?这个问题很模糊
  • @liam 看来您坚持要查看代码是正确的。事实证明,它可能是 SignalR(内部?)序列化,而不是 System.Text.Json。我必须看到 _hub.Clients.Clients(connectionIds).SendAsync("JsRuntimeCall", identifier, args) MS 代码是如何序列化 args 的。它可能是一些客户二进制文件?序列化。无论如何,正如我所写,我很惊讶 Notification 实例没有该属性,这可能是(任何?)序列化程序在序列化时忽略它的原因。
  • 我的错。 System.Text.Json 序列化属性...我被 SignalR 问题困住了。我将删除此帖子,对不起占用任何人的时间。

标签: c# .net-core interface system.text.json


【解决方案1】:

这对于 .NET Core 3.1 中的 System.Text.Json 是不可能的,除非为 Notification 编写自定义 JsonConverter。序列化器序列化类型的公共属性,但默认接口属性不作为类属性实现,而是通过某种扩展机制实现。请参阅规范:default interface methods

添加对虚拟扩展方法的支持 - 具有具体实现的接口中的方法。 .

只有当默认接口属性被覆盖时,实例属性才会真正添加到实现的具体类型中,从而变成可序列化的。

为了确认,如果我们修改您的fiddle 中的类如下:

public interface INotification
{
    Severity Severity { get; set; }
    string Message { get; set; }

    static string MakeDefaultSeverityName<TNotification>(TNotification notification) where TNotification : INotification => notification?.Severity.ToString().ToLower();

    public string SeverityName => MakeDefaultSeverityName(this);
}

public class Notification : INotification
{
    public Severity Severity { get; set; }
    public string Message { get; set; }
}

public class NotificationWithOverride : Notification
{
    public string SeverityName => INotification.MakeDefaultSeverityName(this);
}

并使用反射打印出这两种类型的属性和方法:

Console.WriteLine("Properties of {0}: {1}", typeof(TNotification).Name, string.Join(", ", typeof(TNotification).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Select(p => p.Name)));
Console.WriteLine("Methods of {0}: {1}", typeof(TNotification).Name, string.Join(", ", typeof(TNotification).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Select(p => p.Name)));

对于Notification,我们得到以下结果:

Properties of Notification: Severity, Message
Methods of Notification: get_Severity, set_Severity, get_Message, set_Message, GetType, MemberwiseClone, Finalize, ToString, Equals, GetHashCode

对于NotificationWithOverride

Properties of NotificationWithOverride: SeverityName, Severity, Message
Methods of NotificationWithOverride: get_SeverityName, get_Severity, set_Severity, get_Message, set_Message, GetType, MemberwiseClone, Finalize, ToString, Equals, GetHashCode

请注意,在Notification 中没有对应于SeverityName 的实例属性或方法——但在NotificationWithOverride 中有。由于缺少要序列化的属性,序列化程序不会输出 SeverityName 值。

注意事项:

  1. System.Text.Json在这方面与其他序列化器一致;它和 Json.NET 都生成完全相同的 JSON:

    • {"Severity":0,"Message":"Message"}Notification
    • {"SeverityName":"info","Severity":0,"Message":"Message"}NotificationWithOverride
  2. 1234563如需确认,请参阅问题 System.Text.Json API is there something like IContractResolver 的答案,目前没有
  3. 在 c# 8.0 中,无法覆盖具体类中的默认接口方法并调用基接口方法。有关详细信息,请参阅 How can I call the default method instead of the concrete implementationMaking member virtual prevents calling default interface implementation and causes StackOverflowException in C# 8Default interface implementations and base() calls

    为了解决这个问题,我向 INotification 添加了一个 static 接口方法,它封装了默认逻辑,然后从默认接口实例方法和覆盖类方法中调用它。 p>

可以在here找到一个演示小提琴。

【讨论】:

  • 非常感谢您提供的详细信息。显然我对接口方法的了解是空白的。
猜你喜欢
  • 2018-11-02
  • 1970-01-01
  • 2012-08-29
  • 2010-11-22
  • 2011-03-25
  • 2016-03-23
  • 1970-01-01
相关资源
最近更新 更多