Razbezhkin
@Razbezhkin
программист, преподаватель

Как преобразовать десериализацию при переходе к null reference types?

Здравствуйте.
Постепенно пробую использовать подход null reference type в своих проектах, но пока что очень непривычно.
Вот еще одно сомнение меня застало врасплох.
Моя логическая цепочка рассуждений изложена ниже, чувствую, что что-то в ней не так, но что именно не могу понять.
предположим, что значения по умолчанию я не могу использовать. Итак:
1. когда мы объявляем класс, то обязаны разметить символом ? те поля и свойства, которые могут принимать null, типы без ? принимать null не могут.

2. поскольку у нас есть поля без ?, то они должны быть инициализированы при создании класса: либо значение по умолчанию, либо через конструктор. больше никак?

3. ну и при создании экземпляра мы должны только через конструктор передать начальные значения. соответственно, весь код, который у нас раньше присваивал значения полям в стиле

oldClass c = new oldClass();
c.field1 = "val1";
c.field2 = "val2";


или

oldClass x = new oldClass { field1 = "val1", field2 = "val2" };


Больше не прокатит.

Вот тут мне бы хотелось услышать экспертов в этом вопросе. все ли я правильно понимаю до этого момента?

А теперь еще один пример.
Допустим я сериализовал экземпляр класса в json, а потом мне нужно его десериализовать.
И я использую Newtonsoft.Json (13.0.1)
Конструктор - это метод и у него там логика присваивания полей может быть разной, во всяком случае, код десериализатора вряд ли будет задумываться о семантике конструктора. А раз так, то возникает опасения, как десериализатор сможет правильно передать значения через конструктор, чтобы правильно восстановить поля исходного класса.

Я провел эксперимент и оказалось, что опасения подтвердились.
исходный код тут: https://gitlab.com/p6928/nullable-reference-types

у нас есть класс, у которого в конструкторе входные параметры передаются не в том же порядке, в котором объявлены свойства класса.

public class notnullableclass
{
    public notnullableclass(string notnull1, string notnull2)
    {
        this.notnull1 = notnull2;//специально так, чтобы запутать десериализатор
        this.notnull2 = notnull1;
    }
    public string notnull1 { get; set; }
    public string notnull2 { get; set; }
}


Код сериализации и десериализации:

notnullableclass e1 = new notnullableclass("init1","init2");

string data1 = JsonConvert.SerializeObject(e1);

Console.WriteLine($"e1: notnull1:{e1.notnull1}; notnull2:{e1.notnull2}");
Console.WriteLine($"data1: {data1}");
            
notnullableclass e2 = JsonConvert.DeserializeObject<notnullableclass>(data1);
Console.WriteLine($"deserialized e2: notnull1:{e2.notnull1}; notnull2:{e2.notnull2}");

string data2 = "{\"notnull1\":\"init1\",\"notnull2\":\"init2\"}";
Console.WriteLine($"data2: {data2}");
notnullableclass e3 = JsonConvert.DeserializeObject<notnullableclass>(data2);
Console.WriteLine($"deserialized e3: notnull1:{e3.notnull1}; notnull2:{e3.notnull2}");


вывод в консоль:

e1: notnull1:init2; notnull2:init1
data1: {"notnull1":"init2","notnull2":"init1"}
deserialized e2: notnull1:init1; notnull2:init2
data2: {"notnull1":"init1","notnull2":"init2"}
deserialized e3: notnull1:init2; notnull2:init1

как видим, после десериализации значения полей перепутались.

Ну и как после того доверять десериализации null reference types?

А если серьезно, то как все же гарантировать правильную работу кода при применении похода null reference type?
Можно ли все таки гарантировать что поле не будет равно null и при этом не пользоваться конструктором?

Спасибо за внимание.
  • Вопрос задан
  • 60 просмотров
Решения вопроса 1
NRT - это просто аннотации и ничего они не обязывают. Хотя разные библиотеки могут использовать информацию из этих аннотаций.


как видим, после десериализации значения полей перепутались.

А зачем специально его запутывать? Если есть конструктор, то серилизатор будет использовать информацию из него, а ты сам его решил обмануть.
Если очень нужно так назвать - ты можешь разметить параметры при помощи атрибутов.

Инициализировать значением по-умолчанию совершенно не обязательно, хотя сделать конструктор - это хорошая идея (System.Text.Json умеет с ними хорошо работать)
Например ты можешь как значение по-умолчанию использовать "= null!; " - тогда предупреждения не будет.

И я использую Newtonsoft.Json (13.0.1)

Советую переходить на STJ


типы без ? принимать null не могут.

Формально могут. NRT ничего не гарантирует.



А если серьезно, то как все же гарантировать правильную работу кода при применении похода null reference type?
Можно ли все таки гарантировать что поле не будет равно null и при этом не пользоваться конструктором?

1. Переходи на NRT постепенно, проверяя весь код вручную, либо какими-нибудь навороченными статическими анализаторами.
2. Нет, без конструктора с проверками на null ты ничего не сможешь гарантировать.
Хотя если серилизатор очень умный и через emit напрямую фигачит в приватные поля - это также не спасёт.

Так что вот тебе дополнительная причина по скорее покрыть тестами хотябы наиболее важные сценарии в твоём приложении
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 1
yarosroman
@yarosroman
C# the best
Вопрос, а зачем добровольно себе стрелять в ногу?
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы