Как сериализовать свойство интерфейса по умолчанию? - PullRequest
2 голосов
/ 28 мая 2020

Этот вопрос воскрешен, потому что я ошибся в комментарии "My bad". Эта проблема есть, или я что-то упускаю. Для массового спроса вот минимальный воспроизводимый пример:

минимальный воспроизводимый пример


У меня есть интерфейс со свойством по умолчанию:

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?

1 Ответ

1 голос
/ 31 мая 2020

Это невозможно с System.Text.Json дюйм. NET Core 3.1, кроме записи пользовательского JsonConverter для Notification. Сериализатор сериализует свойства publi c типа, но свойства интерфейса по умолчанию не реализованы как свойства класса , вместо этого они реализуются через какой-то механизм расширения. См. Spe c: методы интерфейса по умолчанию

Добавить поддержку виртуальных методов расширения - методов в интерфейсах с конкретными реализациями . .

Только когда свойство интерфейса по умолчанию переопределено , свойство экземпляра фактически добавляется к реализующему конкретному типу и, таким образом, становится сериализуемым.

Для подтверждения , если мы изменим классы из вашего 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

Обратите внимание, что просто нет свойства экземпляра или метода, соответствующего SeverityName в Notification - но есть в 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 не имеет доступа publi c к сопоставителю контрактов; для подтверждения см. вопрос System.Text. Json API есть ли что-то вроде IContractResolver , на который ответ: сейчас нет .

  3. В c# 8.0 невозможно переопределить метод интерфейса по умолчанию в конкретном классе и вызвать метод базового интерфейса. См. Как я могу вызвать метод по умолчанию вместо конкретной реализации , Создание виртуального члена предотвращает вызов реализации интерфейса по умолчанию и вызывает исключение StackOverflowException в C# 8 и Реализации интерфейса по умолчанию и base() вызывает для получения подробной информации.

    Чтобы обойти это, я добавил static метод интерфейса к INotification, который инкапсулирует logi c по умолчанию, а затем вызвал его как из метода экземпляра интерфейса по умолчанию, так и из метода класса переопределения.

Демо скрипт, который показывает выше можно найти здесь .

...