如何序列化接口默认属性?



这个问题复活了,因为我在评论"我的坏"时错了。 这个问题就在那里,或者我错过了一些东西。对于大众需求,这里有一个最小的可重现示例:

最小可重现示例


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

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
} 

问题

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

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

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

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

仅当重写默认接口属性时,实例属性才会实际添加到实现具体类型中,从而变得可序列化。

为了确认,如果我们按如下方式修改您的小提琴中的类:

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. 有了 Json.NET,就可以创建一个自定义合约解析器,该解析器会自动添加缺少的默认属性,但System.Text.Json没有对其合约解析器的公共访问权限;有关确认,请参阅问题System.Text.Json API是否有类似IContractResolver的东西,答案是,目前不是

  3. 在 c# 8.0 中,无法重写具体类中的默认接口方法并调用基接口方法。 有关详细信息,请参阅如何调用默认方法而不是具体实现、使成员成为虚拟可防止调用默认接口实现并导致 C# 8 中的 StackOverflowException和默认接口实现和base()调用

    为了解决此问题,我在INotification中添加了一个封装默认逻辑的static接口方法,然后从默认接口实例方法和重写类方法调用它。

可以在此处找到显示上述内容的演示小提琴。

最新更新