04
Referencje i mechanizmy kopiowania obiektów
Cel zadania

Zrozumienie natury typów referencyjnych oraz różnicy między kopiowaniem płytkim a głębokim. Student nauczy się korzystać z metody MemberwiseClone(), implementować własne metody klonowania oraz weryfikować tożsamość obiektów za pomocą ReferenceEquals().

Scenariusz problemowy

W komisie "Auto-Premium" pojawia się potrzeba kopiowania danych pojazdów (np. gdy mamy dwa niemal identyczne modele na stanie). Szybko odkrywasz, że zwykłe przypisanie auto2 = auto1 nie tworzy nowego samochodu, a jedynie drugą nazwę dla tego samego obiektu w pamięci. Co więcej, dodając do auta szczegółowe informacje o silniku (osobny obiekt), zauważasz, że nawet użycie standardowych mechanizmów kopiowania może być mylące.

Jeśli zmienisz moc silnika w pozornie nowej "kopii", zmieni się ona również w "oryginale"! To zjawisko nazywamy kopiowaniem płytkim (shallow copy) – skopiowane zostały wartości pól prostych, ale pola będące obiektami wciąż wskazują na te same miejsca w pamięci. Musisz zaprojektować mechanizm kopiowania głębokiego (deep copy), który zapewni pełną niezależność obu pojazdów. Twoim zadaniem jest stworzenie metody, która nie tylko powieli dane samochodu, ale również stworzy zupełnie nowy, oddzielny obiekt silnika dla każdego auta.

Opis wykonania
  • Stwórz prostą klasę Silnik z polem Moc (int).
  • W klasie Samochod dodaj pole referencyjne typu Silnik.
  • Zaimplementuj metodę KlonujPlytko(), która wykorzystuje systemową metodę this.MemberwiseClone().
  • Zaimplementuj metodę KlonujGleboko(), która tworzy nowy obiekt klasy Samochod i ręcznie kopiuje wartości, w tym tworzy nową instancję silnika.
  • W metodzie Main utwórz obiekt oryginal z określonym modelem i silnikiem.
  • Utwórz kopia1 przy użyciu kopiowania płytkiego i kopia2 przy użyciu kopiowania głębokiego.
  • Zmień moc silnika w obu kopiach i sprawdź, jak wpłynęło to na obiekt oryginal.
  • Użyj metody object.ReferenceEquals(), aby sprawdzić, czy obiekty i ich silniki to te same instancje w pamięci.
  • Zwróć uwagę na zachowanie typu string – mimo że jest typem referencyjnym, zachowuje się przy kopiowaniu bezpiecznie ze względu na swoją niezmienność (immutability).
Kod źródłowy
Program.cs
using System;

namespace LaboratoriumOOP
{
    // Klasa pomocnicza reprezentująca obiekt składowy (pole referencyjne)
    public class Silnik
    {
        public int Moc { get; set; }
        public Silnik(int moc) { Moc = moc; }
    }

    public class Samochod
    {
        public string Model { get; set; }
        public Silnik DaneSilnika { get; set; }

        public Samochod(string model, int mocSilnika)
        {
            Model = model;
            DaneSilnika = new Silnik(mocSilnika);
        }

        // KOPIOWANIE PŁYTKIE (Shallow Copy)
        // Kopiuje wartości pól bit po bicie. Dla typów referencyjnych (Silnik) 
        // kopiuje tylko ADRES, a nie cały obiekt.
        public Samochod KlonujPlytko()
        {
            return (Samochod)this.MemberwiseClone();
        }

        // KOPIOWANIE GŁĘBOKIE (Deep Copy)
        // Tworzy całkowicie nowy graf obiektów w pamięci.
        public Samochod KlonujGleboko()
        {
            Samochod nowy = (Samochod)this.MemberwiseClone();
            // Ręczne klonowanie obiektów referencyjnych:
            nowy.DaneSilnika = new Silnik(this.DaneSilnika.Moc);
            return nowy;
        }

        public void Info(string nazwa)
        {
            Console.WriteLine($"{nazwa}: {Model}, Moc silnika: {DaneSilnika.Moc} KM");
        }
    }

    class Program
    {
        static void Main()
        {
            Console.WriteLine("--- ZADANIE 04: Mechanizmy kopiowania obiektów ---\n");

            Samochod oryginal = new Samochod("Audi RS6", 600);
            
            // 1. Kopiowanie płytkie
            Samochod kopiaPlytka = oryginal.KlonujPlytko();
            
            // 2. Kopiowanie głębokie
            Samochod kopiaGleboka = oryginal.KlonujGleboko();

            Console.WriteLine("Stan początkowy:");
            oryginal.Info("Oryginał");
            
            Console.WriteLine("\n--- ZMIANA MOCY W KOPIACH ---");
            kopiaPlytka.DaneSilnika.Moc = 150; // Zmieniamy w płytkiej kopii
            kopiaGleboka.DaneSilnika.Moc = 900; // Zmieniamy w głębokiej kopii

            Console.WriteLine("\nEfekt zmiany w pamięci:");
            oryginal.Info("Oryginał"); 
            kopiaPlytka.Info("Kopia Płytka");
            kopiaGleboka.Info("Kopia Głęboka");

            Console.WriteLine("\n--- WERYFIKACJA TOŻSAMOŚCI (ReferenceEquals) ---");
            Console.WriteLine("Czy kopia płytka to ten sam obiekt? " + 
                object.ReferenceEquals(oryginal, kopiaPlytka));
            
            Console.WriteLine("Czy ich silniki to ten sam obiekt (kopia płytka)? " + 
                object.ReferenceEquals(oryginal.DaneSilnika, kopiaPlytka.DaneSilnika));
            
            Console.WriteLine("Czy ich silniki to ten sam obiekt (kopia głęboka)? " + 
                object.ReferenceEquals(oryginal.DaneSilnika, kopiaGleboka.DaneSilnika));

            Console.WriteLine("\nWNIOSEK: Kopiowanie płytkie współdzieli obiekty składowe.");
            Console.WriteLine("Kopiowanie głębokie zapewnia pełną izolację danych.");
            Console.ReadKey();
        }
    }
}
                    
Wynik w konsoli
--- ZADANIE 04: Mechanizmy kopiowania obiektów --- Stan początkowy: Oryginał: Audi RS6, Moc silnika: 600 KM --- ZMIANA MOCY W KOPIACH --- Efekt zmiany w pamięci: Oryginał: Audi RS6, Moc silnika: 150 KM Kopia Płytka: Audi RS6, Moc silnika: 150 KM Kopia Głęboka: Audi RS6, Moc silnika: 900 KM --- WERYFIKACJA TOŻSAMOŚCI (ReferenceEquals) --- Czy kopia płytka to ten sam obiekt? False Czy ich silniki to ten sam obiekt (kopia płytka)? True Czy ich silniki to ten sam obiekt (kopia głęboka)? False WNIOSEK: Kopiowanie płytkie współdzieli obiekty składowe. Kopiowanie głębokie zapewnia pełną izolację danych.