כתבנו class פשוט בצורה הזאת:
Product.cs
public class Product
{
public int id { get; set; }
public string? name { get; set; }
public string? description { get; set; }
public decimal price { get; set; }
}
זאת לא הצורה המיטבית לכתוב את ה-class. יכול להיות שאני לא ארצה לחשוף ל-client את כל השדות של ה-class.
את חלק מהשדות אני ארצה להציג ב-client אחרי מניפולציה, ולא כדאי להעמיס את ה-class עם כל השדות האלה.
אנחנו נבצע מיפוי בין האובייקטים שישבו בתוך המערכת לבין אלה שהולכים ל-client, אלו לא יהיו אותם אובייקטים.
השרת אמור להביא את המידע ל-client כמה שיותר מוכן.
שמות ל-classes
צריך לתת שמות שיסמנו לנו מה התפקיד של האובייקטים.
Dto – האוביקט שהולך ל-client. משמעות: Data Transfer Object. הוא מעביר את המידע בין ה-server ל-client והפוך.
public class ProductDto
שדות readonly
האובייקט הזה מגיע מה-client והוא לא אמור לעבור שינוי. אם יש לי מניפולציות אני רוצה לבצע אותן על אובייקט אחר. הפתרון הוא ליצור את כל השדות כ-readonly.
אם שדה מוגדר כ-readonly צריך להגדיר לו דברים נוספים. אנחנו נגדיר משתנה פרטי, נגדיר גם משתנה ציבורי שבתוכו תהיה פונקציה שמחזירה את המשתנה הפרטי.
ההבדל בין readonly ל-const, שאפשר להגדיר אותו כמו const בשורת ההגדרה, אבל אפשר גם להגדיר אותו ב-constructor.
public class ProductDto
{
private readonly int _id;
public int Id {
get{
return this._id;
}
}
public ProductDto(int id) {
this._id = id;
}
}
הכתיבה הזאת קצת ארוכה ואפשר גם להגדיר משתנה כ-readonly אם יש לו רק פונקציית get. בכל מקרה נצטרך לאתחל את הערכים ב-constructor.
public class ProductDto
{
public int id { get; }
public string? name { get; }
public string? description { get; }
public decimal price { get; }
public ProductDto(int id, string name, string desc, decimal price) {
this.id = id;
this.name = name;
this.description = desc;
this.price = price;
}
}
הקוד עדיין ארוך ואנחנו רוצים לקבל ערכים דרך initializer, אבל שהערכים יהיו readonly. הבעיה היא שה-initializer אומר שיש לערכים set ואת זה אנחנו לא רוצים. אנחנו רוצים להגדיר אותם פעם אחת ביצירה. הפתרון הוא הגדרת init במקום set. זה אומר שאפשר להגדיר את השדות ב-initializer בלבד ולא ניתן לשנות אותם אחר כך.
public class ProductDto
{
public int id { get; init; }
public string? name { get; init; }
public string? description { get; init; }
public decimal price { get; init; }
}
ביצירה של האובייקט נאתחל אותו.
ProductDto p = new ProductDto {name = "lamp", price = 23};
הבעיה פה שהוגדר לי משתנה ללא id ואת זה היה אפשר לאכוף אם constructor. כדי לטפל בזה בכל זאת אפשר להגדיר את המשתנה כ-required.
public record ProductDto
{
public required int id { get; init; }
public string? name { get; init; }
public string? description { get; init; }
public decimal price { get; init; }
}
עכשיו יהיה חייב לספק את ה-id.
למה שהגדרנו עכשיו קוראים record. ישות נתונים לא פונקציונלית, שכל המהות שלה להחזיק מידע שמרגע שהוא נוצר לא ניתן לשנות אותו.
With keyword
מה קורה כאשר אני רוצה לבצע מניפולציה על ה-record?
var p = new ProductDto {
id = 10,
name = "lamp",
description = "description of lamp",
price = 23
};
אנחנו נשכפל את הקובייקט ונבצע מניפולציה על מה שמשתנה. למשל:
var p1 = new ProductDto {
id = p.id,
name = p.name,
description = p.description,
price = 32
};
כל הערכים נשארו והשתנה המחיר.
זה לא הגיוני כי יכולים להיות גם 20 שדות.
במקום זה, אפשר להשתמש ב-with ואז:
var p1 = p with { price = 32 };
כל הערכים מועתקים חוץ ממה שנתנו לו ערך חדש.
הגדרת record פשוט
אם יש record שהוא פשוט ועושה פעולות קטנות אפשר להגדיר אותו בשורה אחת:
public record ProductDtoSingleLine(int id, string name, string desc, decimal price);
והפניה אליו תהיה כמו אל constructor:
var p3 =new ProductDtoSingleLine(15, "p name", "p desc", 14);
זה פחות קריא ומתאים לפעולות קטנות.