CodeGym /Kurslar /C# SELF /Siklik referensiya problemləri

Siklik referensiya problemləri

C# SELF
Səviyyə, Dərs
Mövcuddur

1. Giriş

Siklik (və ya dairəvi) referensiya o zaman yaranır ki, bir obyekt birbaşa və ya dolayı şəkildə başqa obyektə referensiya edir və nəticədə həmin obyekt yenidən ilk obyektə referensiya edir.

Real nümunə

Gəlin kitabxana nümunəsi götürək. Tutaq ki, bizim Book sinifimizdə Author property-si var, amma Author sinifində isə bütün kitabları saxlayan Books tipi List<Book> property-si var.


public class Author
{
    public string Name { get; set; }
    public int BirthYear { get; set; }
    public List<Book> Books { get; set; } = new List<Book>();
}

public class Book
{
    public string Title { get; set; }
    public Author Author { get; set; }
}

İndi bir müəllif və bir kitab yaratsaq və əlaqələri qursaq, "dairə" alınacaq:


var author = new Author { Name = "Marsel Prust", BirthYear = 1871 };
var book = new Book { Title = "V storonu Svana", Author = author };

author.Books.Add(book);

// Bəs, indi author book-a referensiya edir, book isə author-a!

Niyə bu problem yaradır?

Belə obyekt JSON-a serialize ediləndə serializer property-ləri izləməyə başlayır. O görür ki, müəllifin kitabları var, içində yenə müəllif... yenə kitablar... belə sonsuzluğa qədər.

author -> books[] -> author -> books[] ...

Bu, digər güzgünün qarşısında dayanıb əksin-əksini izləmək kimidir — refleksiyalar sonsuzluğa gedir. Yalnız gözəl əks deyil, StackOverflowException ilə nəticələnə bilər.

2. Serializer siklik referensiyalara necə reaksiya verir?

Serialization xətası

Standart olaraq System.Text.Json siklik referensiyaları idarə etmir. Belə struktur serialize etməyə çalışsanız, JsonException alacaqsınız: "A possible object cycle was detected".

Xətaya səbəb olacaq nümunə:


string json = JsonSerializer.Serialize(author); // BUM! JsonException

Vizualizasiya:

graph TD;
    Author --> Book;
    Book --> Author;

3. Siklik referensiyaları necə həll etmək olar?

Bir neçə real yanaşma var, hər birinin öz üstünlükləri və kompromisləri. Universal "sehrli flag" olmadığını artıq başa düşdünüz.

Serialize-dən əvvəl siklları ləğv etmək

Ən sadə yol — belə etməmək. Serialize etmədən əvvəl sikl yaradan referensiyaları null etmək (və ya nəzərə almamaq).

Praktikada belə görünür:


// Müvəqqəti olaraq müəllifin kitablarını silirik
var authorToSerialize = new Author
{
    Name = author.Name,
    BirthYear = author.BirthYear,
    Books = null // və ya property-ni ümumiyyətlə daxil etməmək olar
};

string json = JsonSerializer.Serialize(authorToSerialize);
// İndi hamısı problemsiz serialize olundu!

Üstünlüklər: sadə, sürətli, aydın.
Eksikliklər: bəzi məlumatları itirirsiniz (deserialization-dan sonra geriyə çevrilən əlaqələr olmayacaq).

[JsonIgnore] atributundan istifadə etmək

Siklikdə iştirak edən property-ni ignore kimi işarələmək olar:


public class Author
{
    public string Name { get; set; }
    public int BirthYear { get; set; }
    
    [JsonIgnore] 
    public List<Book> Books { get; set; }
}

İndi müəllifi serialize edəndə onun kitabları JSON-a daxil olmayacaq. Bu yuxarıdakı üsula oxşardır, amma deklarativdir və manuel təmizləmə tələb etmir.

Üstünlüklər: daha sadə, linki unutmaq ehtimalı azalır.
Eksikliklər: müəllifin kitabları haqda informasiya JSON-da itirilir.

Nested obyektlər əvəzinə identifikatorlardan istifadə etmək

Hər iki tərəfi (müəllif və kitab) saxlamaq vacibdirsə, amma dairəvi referensiyalardan qaçmaq istəyirsinizsə, nested obyektlər yerinə unikal Id-lərdən istifadə edin:


public class Book
{
    public string Title { get; set; }
    public int AuthorId { get; set; } // Author yerinə
}
public class Author 
{
    public int AuthorId { get; set; }
    public string Name { get; set; }
    // ya kitabları saxlamırıq, ya da onların Id-lərini saxlayırıq
}

JSON-da artıq obyektlər yox, identifikatorlar olacaq. Bu yanaşma DB-lərdə, REST API-lərdə və əlaqəli sistemlərdə geniş istifadə olunur.

Üstünlüklər: sikllar yoxdur, JSON kompakt, linkləri Id-ə görə bərpa etmək olar.
Eksikliklər: obyekt modelindən imtina edilir, deserialization zamanı Id üzrə lookup tələb olunur.

Qısa müqayisə cədvəli:

Yanaşma Siklik problemi həll olunur? Məlumat itkisi? Tətbiq sahəsi
[JsonIgnore] Bəli Bəli Nested vacib deyilsə
Manual link-ləri silmək Bəli Bəli Serialize-dən əvvəl sürətli həll
Object yerinə Id saxlamaq Bəli Xeyr* REST, DB, kompleks sistemlər

* Məlumat itmir, amma dərhal əlçatan deyil (Id üzrə axtarış tələb olunur).

4. System.Text.Json-ə siklik referensiyaları serialize etməyi necə öyrətmək olar?

.NET 5-dən etibarən JsonSerializerOptions-da referensiya rejimi var: options.ReferenceHandler = ReferenceHandler.Preserve.

Bu rejim təkrar obyektlər üçün $id və $ref kimi xüsusi sahələrdən istifadə edir.

Nümunə


var options = new JsonSerializerOptions
{
    WriteIndented = true,
    ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.Preserve
};

string json = JsonSerializer.Serialize(author, options);
Console.WriteLine(json);

Alınan JSON belə görünəcək:

{
  "$id": "1",
  "Name": "Marsel Prust",
  "BirthYear": 1871,
  "Books": {
    "$id": "2",
    "$values": [
      {
        "$id": "3",
        "Title": "V storonu Svana",
        "Author": {
          "$ref": "1"
        }
      }
    ]
  }
}
  • $id — JSON-də obyektin unikal identifikatoru
  • $ref — artıq serialize olunmuş obyektə referensiya

Deserialization zamanı hamısı düzgün bərpa olunur (sonsuz sikl və stack xətası olmadan).

Xüsusiyyətlər və məhdudiyyətlər

  • Belə JSON frontend üçün qeyri-adi ola bilər: çox JS client-lər $id/$ref-i əlavə məntiqsiz anlamır.
  • JSON ölçüsü böyüyür, gözlə yoxlamaq çətinləşir.
  • Bu rejim yalnız ReferenceHandler.Preserve açıq olduqda işləyir.
  • Value type-lara aid deyil (orada sikl ola bilməz).

Belə JSON-u necə deserialize etmək olar?

Yalnız adi qayda ilə, amma eyni JsonSerializerOptions-u istifadə edin:


var deserializedAuthor = JsonSerializer.Deserialize<Author>(json, options);

5. Newtonsoft.Json (Json.NET) ilə vəziyyət necədir?

Tarixən Newtonsoft.Json sikllarla işləməyi System.Text.Json-dan əvvəl bacarırdı. Bunun üçün [JsonObject(IsReference = true)] atributu və qlobal serialization ayarları var.

Referensiyalar üçün atributlar


[JsonObject(IsReference = true)]
public class Author
{
    public string Name { get; set; }
    public List<Book> Books { get; set; }
}

[JsonObject(IsReference = true)]
public class Book
{
    public string Title { get; set; }
    public Author Author { get; set; }
}

Sonra belə serialize edirik:


var settings = new JsonSerializerSettings
{
    PreserveReferencesHandling = PreserveReferencesHandling.Objects,
    Formatting = Formatting.Indented
};

string json = JsonConvert.SerializeObject(author, settings);

Nəticədə $id və $ref ilə JSON alınır, ReferenceHandler.Preserve rejiminə oxşar.

Qısa nəticə

  • Əgər .NET-app-lər arasında data mübadiləsi varsa — referensial serializasiyanı açın (ReferenceHandler.Preserve və ya PreserveReferencesHandling).
  • Əgər data JavaScript/başqa client-lərə gedirsə — siklları qırın: [JsonIgnore], referensiyaları təmizləmək və ya Id-lərə keçmək.

6. Xətalardan və baş ağrılarından necə qaçmaq olar

Çox vaxt yeni başlayanlar (və hətta təcrübəlilər) serializer-in sikllar səbəbindən düşməsiylə üzləşirlər. Yadda saxlayın: əgər kolleksiyalar/property-lər bir-birinə referensiya edirsə — model arxitekturasını yoxlayın.

İstifadə etməyə çəkinməyin: [JsonIgnore] property-lər üçün, hansı ki xarici mübadilə üçün lazım deyil.

Klassik tələ — "many-to-many" əlaqələrin serialize edilməsi (məsələn, tələbələr ↔ kurslar). Siklları qırmadan və ya referensial serializasiyanı aktiv etmədən bu işləməyəcək.

REST API-də adətən bir tərəf göndərilir: misal üçün, book müəllifi bilir, müəllif isə bu contract-da yalnız kitabların Id-lərini və ya heç nəyi bilməyə bilər.

2
Tapşırıq
C# SELF, səviyyə, dərs
Bağlanıb
Dairəvi istinadlarla obyektlərin yaradılması
Dairəvi istinadlarla obyektlərin yaradılması
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION