🔍 Κατανόηση των Properties, Getters/Setters και Initialization στην C#
Εισαγωγή
Στη C#, τα properties είναι ο τρόπος με τον οποίο εκθέτουμε δεδομένα (fields) μιας κλάσης με ελεγχόμενο τρόπο. Αντί να κάνουμε απευθείας public τα πεδία, χρησιμοποιούμε properties με getters και setters, ώστε να μπορούμε να προσθέσουμε λογική ελέγχου, προστασία και συμβατότητα με serialization frameworks.
🧱 Τι είναι τα Properties
Ένα property είναι ένας συνδυασμός:
- ενός getter, που διαβάζει την τιμή ενός πεδίου, και
- ενός setter, που τη γράφει.
Παράδειγμα:
public class User
{
public string Name { get; set; }
}
Αυτό είναι ένα auto-property, που σημαίνει πως η C# δημιουργεί εσωτερικά ένα private field για σένα.
Μπορείς όμως να το γράψεις και πιο ρητά:
private string _name;
public string Name
{
get => _name;
set => _name = value;
}
🧩 Γιατί να κάνω τον Setter private;
Το private set χρησιμοποιείται όταν θέλουμε το property να είναι μόνο αναγνώσιμο εκτός της κλάσης, αλλά να μπορεί να αλλάξει μόνο εσωτερικά από την ίδια την κλάση.
Παράδειγμα:
public class Order
{
public Guid Id { get; private set; }
public DateTime CreatedAt { get; private set; }
public Order()
{
Id = Guid.NewGuid();
CreatedAt = DateTime.UtcNow;
}
}
👉 Εδώ, ο Order μπορεί να δημιουργήσει μόνος του το Id και το CreatedAt, αλλά κανείς άλλος δεν μπορεί να τα αλλάξει απ’ έξω.
📌 Χρησιμότητα:
- Ακεραιότητα αντικειμένου (Encapsulation) — δεν επιτρέπεις σε εξωτερικό κώδικα να "πειράξει" σημαντικά properties.
- Domain consistency — ιδανικό σε Domain Models ή Read-Only DTOs.
- Ελεγχόμενη τροποποίηση — μόνο μέσα από methods, όχι με άμεση ανάθεση.
💡 Τι εξυπηρετούν τα Properties γενικά
Encapsulation — προστατεύεις την εσωτερική κατάσταση.
Validation — μπορείς να ελέγχεις τιμές όταν γίνεται set.
Serialization — frameworks όπως JSON serializers δουλεύουν πάνω στα properties.
Data Binding — σε UI frameworks, τα properties είναι θεμελιώδη για binding.
⚙️ Τι είναι το init και πότε το χρησιμοποιούμε
Από την C# 9.0 και μετά, έχουμε τον init accessor.
Αυτό επιτρέπει immutable αντικείμενα, αλλά με ευκολία αρχικοποίησης κατά τη δημιουργία.
Παράδειγμα:
public class Product
{
public string Name { get; init; }
public decimal Price { get; init; }
}
Μπορούμε να το χρησιμοποιήσουμε έτσι:
var p = new Product { Name = "Laptop", Price = 999.99m };
Αλλά δεν μπορούμε να αλλάξουμε τις τιμές μετά:
p.Price = 1200; // ❌ Compile error
📌 Χρησιμοποιούμε init όταν θέλουμε:
- immutable models (π.χ. DTOs, ViewModels),
- ασφάλεια από μεταβολές μετά την κατασκευή του αντικειμένου,
- καθαρό, predictable state.
🧮 Τι είναι το string.Empty και γιατί το χρησιμοποιούμε στην αρχικοποίηση
Όταν αρχικοποιούμε ένα property, π.χ. σε ένα DTO, συχνά βλέπουμε:
public string Name { get; set; } = string.Empty;
Αυτό γίνεται για να αποφύγουμε το null στα models που προορίζονται για serialization, binding ή απλή μεταφορά δεδομένων (DTOs).
📌 Γιατί:
- Όταν κάνεις JSON serialization, αν Name == null, μπορεί να λείψει από το JSON
- Όταν κάνεις binding σε UI, ένα null string μπορεί να προκαλέσει exceptions ή warnings.
- Γενικά, τα DTOs είναι "dumb objects", δεν χρειάζονται null state.
Αντίθετα, σε entity models (π.χ. EF Core):
public string? Name { get; set; }
✅ Εκεί επιτρέπουμε το null, γιατί το EF πρέπει να αντικατοπτρίζει την πραγματική βάση δεδομένων, όπου μια στήλη μπορεί να είναι nullable.
📊 Σύνοψη:
Τύπος Μοντέλου | Συνήθης Αρχικοποίηση | Λόγος |
---|---|---|
DTO / ViewModel | = string.Empty; |
Αποφυγή null references |
EF Entity |
= null!; ή string?
|
Αντιστοίχιση με DB nulls |
🧱 DTOs vs Entities
DTOs (Data Transfer Objects)
- Ελαφριά μοντέλα που μεταφέρουν δεδομένα μεταξύ layers ή μέσω API.
- Δεν έχουν επιχειρησιακή λογική.
- Συνήθως immutable (με init ή private setters).
- Συχνά αρχικοποιούνται με string.Empty, για αποφυγή nulls.
Entities (EF Core)
- Αντιστοιχούν άμεσα σε πίνακες βάσης δεδομένων.
- Μπορεί να έχουν navigation properties, tracking, lazy loading.
- Πρέπει να επιτρέπουν null όπου η βάση το επιτρέπει.
- Δεν θέλουμε default τιμές που δεν αντικατοπτρίζουν το DB state.
Extra Tip: Defensive vs Pragmatic Initialization
- Defensive initialization (DTOs):
public string Name { get; set; } = string.Empty;
→ Προστατεύει από null, χρήσιμο σε API responses.
- Pragmatic initialization (Entities):
public string? Name { get; set; }
→ Εξασφαλίζει σωστό mapping με τη βάση.
🔒 Παράδειγμα πλήρους κατανόησης
public class CustomerEntity // EF Core Entity
{
public int Id { get; set; }
public string? Name { get; set; } // nullable στη DB
public string? Email { get; set; }
}
public class CustomerDto // DTO
{
public string Name { get; init; } = string.Empty;
public string Email { get; init; } = string.Empty;
}
👉 Ο CustomerEntity καθρεφτίζει τη βάση.
👉 Ο CustomerDto είναι ασφαλής για API χρήση, χωρίς nulls, immutable.
🔚 Συμπέρασμα
get; set; → πλήρως αναγνώσιμο/εγγράψιμο property.
get; private set; → εξωτερικά read-only, εσωτερικά εγγράψιμο.
init → immutable μετά τη δημιουργία.
string.Empty στα DTOs → αποφυγή nulls σε serialization/UI.
null στα Entities → συμβατότητα με DB schema.
Ορθό encapsulation → θεμέλιο του καθαρού, συντηρήσιμου κώδικα.
Top comments (0)