record c# - תכנות - HWzone פורומים
עבור לתוכן
  • צור חשבון

record c#


eido300
 Share

Recommended Posts

אני לומד עכשיו על c# ולא הצלחתי להבין מה העניין בrecord, הבנתי שזה תחליף למחלקה ולstuct, אבל תכלס מה מיוחד בו ולמה שאשתמש בו ולא במחלקה/מבנה, אשמח אם מישהו יוכל להבהיר את העניין.

תודה.

קישור לתוכן
שתף באתרים אחרים

אני אצא מנקודת הנחה שאתה מתכוון לrecords של C# 9.

 

במשפט אחד: records הם immutable data structure.

 

למה immutable?

כי למרות שזה פחות יעיל, זה גם פחות קשה לשימוש שגוי. שפות פונקציונליות עושות הכל immutable, כי אם זה immutable, אז אי אפשר לשנות את זה בטעות (אי אפשר לשנות את זה גם בכוונה). ואז אין סיכוי שאני מקבל אובייקט ופתאום החצוף משתנה לי ביד בלי להודיע.

יותר מזה, היום הכל multithreaded (משתדלים, על כל פנים), ואם ניסית, אתה יודע איזה סיוט זה לגלות שיש לך race condition. עם immutable objects אין לך. בכלל.

ונוסף לכל, אם יש תמיכה מהשפה, בד"כ זה מפשט את הקוד (למרות שצריך להתרגל).

 

אז איך יוצרים immutable data structure בC#?

בוא ננסה:

public class Person
{
  public string FirstName { get; }
  public string LastName { get; }
  public string? Phone { get; }
  public string? Address { get; }
  
  public Person(string firstName, string lastName, string? phone, string? address) =>
    (FirstName, LastName, Phone, Address) = (firstName, lastName, phone, address);
}

var person = new Person("Jacob", "Something", "123456789", "Somewhere");
var johnDoe = new Person("Jhon", "Doe", null, null);

אבל כמו שאתה רואה, אנחנו לא יודעים את הטלפון או הכתובת של John Doe (כמובן). אבל חבל להעביר null, זה מסרבל. לא היה עדיף אם היינו דורשים רק שם ושם משפחה וכל השאר מאפשרים להעביר (אבל לא דורשים) באתחול?

אפשר לנסות עם { get; set }:

public class Person
{
  public string FirstName { get; }
  public string= LastName { get; }
  public string? Phone { get; set; }
  public string? Address { get; set; }
  
  public Person(string firstName, string lastName) => (FirstName, LastName) = (firstName, lastName);
}

var person = new Person("Jacob", "Something") { Phone = "123456789", Address = "Somewhere" };
var johnDoe = new Person("Jhon", "Doe");

אבל זה כבר לא immutable. כלומר, אפשר לא לשנות אותו, אבל הקומפיילר לא יאכוף את זה.

בוא נשתמש בתכונה חדשה של C# 9, שנקראת init-only properties:

public class Person
{
  public string FirstName { get; }
  public string LastName { get; }
  public string? Phone { get; init; }
  public string? Address { get; init; }
  
  public Person(string firstName, string lastName) => (FirstName, LastName) = (firstName, lastName);
}

var person = new Person("Jacob", "Something") { Phone = "123456789", Address = "Somewhere" };
var johnDoe = new Person("Jhon", "Doe");

אם במקום set יש init, אז זה כמו readonly - אפשר לאתחל בקונסטרקטור, אבל גם בinitializer list. חוץ מזה לא. נהדר.

 

אבל רגע, מה עם Clone? כדאי שיהיה לנו כדי שנוכל ליצור חדש ששונה רק במעט מהקודם:

public class Person
{
  public string FirstName { get; }
  public string LastName { get; }
  public string? Phone { get; init; }
  public string? Address { get; init; }
  
  public Person(string firstName, string lastName) => (FirstName, LastName) = (firstName, lastName);
  
  public CloneWith(string? firstName = null, string? lastName = null, string? phone = null, string? address = null)
  {
    firstName ??= FirstName;
    lastName ??= LastName;
    phone ??= Phone;
    address ??= Address;
    return new Person(firstName, lastName) { Phone = phone, Address = address };
  }
}

var person = new Person("Jacob", "Something") { Phone = "123456789", Address = "Somewhere" };
var johnDoe = new Person("Jhon", "Doe");
person = person.CloneWith(phone: "123456"); // Changed only Phone

אגב, הCloneWith שלנו לא מושלם: חוץ מזה שהסינטקס מכוער, אי אפשר לשנות את Phone או Address לnull. לא כל כך פשוט לפתור את זה בלי לעשות שמיניות באוויר ממש.

אבל רגע, מה עם GetHashCode? כדאי שנממש אותו כדי שנוכל לשים את הPerson שלנו באוספים שצריכים אותו:

public class Person
{
  public string FirstName { get; }
  public string LastName { get; }
  public string? Phone { get; init; }
  public string? Address { get; init; }
  
  public Person(string firstName, string lastName) => (FirstName, LastName) = (firstName, lastName);
  
  public CloneWith(string? firstName = null, string? lastName = null, string? phone = null, string? address = null)
  {
    firstName ??= FirstName;
    lastName ??= LastName;
    phone ??= Phone;
    address ??= Address;
    return new Person(firstName, lastName) { Phone = phone, Address = address };
  }
  
  public override int GetHashCode() => HashCode.Combine(FirstName, LastName, Phone, Address);
}

var person = new Person("Jacob", "Something") { Phone = "123456789", Address = "Somewhere" };
var johnDoe = new Person("Jhon", "Doe");
person = person.CloneWith(phone: "123456"); // Changed only Phone

הי, ומה עם Equals וIEquatable וoperator== וoperator!=?

public class Person : IEquatable<Person>
{
  public string FirstName { get; }
  public string LastName { get; }
  public string? Phone { get; init; }
  public string? Address { get; init; }
  
  public Person(string firstName, string lastName) => (FirstName, LastName) = (firstName, lastName);
  
  public CloneWith(string? firstName = null, string? lastName = null, string? phone = null, string? address = null)
  {
    firstName ??= FirstName;
    lastName ??= LastName;
    phone ??= Phone;
    address ??= Address;
    return new Person(firstName, lastName) { Phone = phone, Address = address };
  }
  
  public override int GetHashCode() => HashCode.Combine(FirstName, LastName, Phone, Address);
  
  public override bool Equals(object? other) => other is Person p && Equals(p);
  
  public static bool operator ==(Person? left, Person? right) => IEqualityComparer<Person>.Default.Equals(left, right);
  public static bool operator !=(Person? left, Person? right) => !(left == right);
  
  public bool Equals(Person? other) =>
    other != null &&
    FirstName == other.FirstName &&
    LastName == other.LastName &&
    Phone == other.Phone &&
    Address == other.Address;
}

var person = new Person("Jacob", "Something") { Phone = "123456789", Address = "Somewhere" };
var johnDoe = new Person("Jhon", "Doe");
person = person.CloneWith(phone: "123456"); // Changed only Phone

רגע, ומה עם ToString? שתהיה לנו הדפסה יפה, למטרות debugging?

public class Person : IEquatable<Person>
{
  public string FirstName { get; }
  public string LastName { get; }
  public string? Phone { get; init; }
  public string? Address { get; init; }
  
  public Person(string firstName, string lastName) => (FirstName, LastName) = (firstName, lastName);
  
  public CloneWith(string? firstName = null, string? lastName = null, string? phone = null, string? address = null)
  {
    firstName ??= FirstName;
    lastName ??= LastName;
    phone ??= Phone;
    address ??= Address;
    return new Person(firstName, lastName) { Phone = phone, Address = address };
  }
  
  public override int GetHashCode() => HashCode.Combine(FirstName, LastName, Phone, Address);
  
  public override bool Equals(object? other) => other is Person p && Equals(p);
  
  public static bool operator ==(Person? left, Person? right) => IEqualityComparer<Person>.Default.Equals(left, right);
  public static bool operator !=(Person? left, Person? right) => !(left == right);
  
  public bool Equals(Person? other) =>
    other != null &&
    FirstName == other.FirstName &&
    LastName == other.LastName &&
    Phone == other.Phone &&
    Address == other.Address;
  
  public override string ToString() =>
    $"Person {{ FirstName = {FirstName}, LastName = {LastName}, Phone = {Phone}, Address = {Address} }}"
}

var person = new Person("Jacob", "Something") { Phone = "123456789", Address = "Somewhere" };
var johnDoe = new Person("Jhon", "Doe");
person = person.CloneWith(phone: "123456"); // Changed only Phone

דייייייייי!!!!!!!!!!!! עזוב אותי מimmutability, נלך לmutable הישן והטוב! זה כל מה שצריך בשביל לכתוב מחלקה פשוטה??

 

ובכן, לא. כלומר, לא החל מC# 9.

public record Person
{
  public string FirstName { get; init; }
  public string LastName { get; init; }
  public string? Phone { get; init; }
  public string? Address { get; init; }
  
  public Person(string firstName, string lastName) => (FirstName, LastName) = (firstName, lastName);
}

var person = new Person("Jacob", "Something") { Phone = "123456789", Address = "Somewhere" };
var johnDoe = new Person("Jhon", "Doe");
person = person with { Phone = "123456" }; // Changed only Phone

טא-דם!

כל הבעיות מקודם נפתרו כבמטה קסם, פלוס קוד נורמלי. וכל הmethods שכתבנו מקודם עדיין נמצאים (חוץ מCloneWith, שהוחלפה בmethod פנימי לקומפיילר בשם <Clone>$ שהכרחית כדי שהביטוי with יעבוד).

 

עכשיו אתה מבין למה records? :P

נערך על-ידי af db creid
קישור לתוכן
שתף באתרים אחרים

אואה איזו השקעה.

אז בעצם היתרון של רקורד הוא שהמשתנים לא נתנים לשינוי ושהקומפלייר מממש במקומנו את כל הפונקציות והאופרטורים הנ"ל?

נערך על-ידי eido300
קישור לתוכן
שתף באתרים אחרים

האם המשתנים לא ניתנים לשינוי - החלטה שלך. אפשר לכתוב משהו כזה:

public record Person
{
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public string? Phone { get; set; }
  public string? Address { get; set; }
  
  public Person(string firstName, string lastName) => (FirstName, LastName) = (firstName, lastName);
}

var person = new Person("Jacob", "Something") { Phone = "123456789", Address = "Somewhere" };
var johnDoe = new Person("Jhon", "Doe");
person.Phone = "123456"; // Changed only Phone

אבל לא מומלץ כי הmethods שהקומפיילר יוצר מאמינים שזה לא ישתנה. היתרון של records הוא שהקומפיילר יצור עבורך את כל הmethods הboilerplate.

קישור לתוכן
שתף באתרים אחרים

הצטרפ/י לדיון

בשלב זה תוכל/י להצטרף לדיון, ולאחר מכן להצטרף לקהילה שלנו. אם כבר יש לך חשבון אצלנו, אנא התחבר/י עכשיו על מנת להגיב תחת שם המשתמש שלך.
לתשומת לבך: התגובה תופיע לגולשים לאחר אישור של צוות הנהלת הפורומים.

אורח
הוסף תגובה

×   התוכן שהודבק הוא עם עיצוב.   הסר עיצוב

  Only 75 emoji are allowed.

×   הקישור שלך הוטמע אוטומטית.   הצג כקישור רגיל

×   התוכן הקודם שלך שוחזר אוטומטית.   נקה הכל

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
  • צור חדש...