שיטוח של אובייקטים לקובץ - כתיבה לקובץ וקריאה מקובץ
הפעם אני רוצה לכתוב פוסט לטובת אלה שנכנסים לעולם ה - Net.
יצא לי בזמן האחרון כמה פעמים להדגים קריאה וכתיבה לקובץ (סירילזיציה ודיסיריליזציה), חשבתי שכדאי לכתוב פוסט על זה, כי מן הסתם יהיו עוד אנשים שלומדים ומחפשים הסבר על הנושא.
הרעיון בלקחת אובייקט ולשמור אותו בקובץ הוא כדי שנוכל לשמור על המצב של האפליקציה, נניח שיש לנו תוכנה לניהול עובדים, כשהתוכנה תיסגר כל הנתונים שיש לנו בזיכרון יעלמו, ולכן אנחנו רוצים לשמור את הנתונים שלנו בדיסק הקשיח, ולמעשה לשטח את האובייקטים שלנו מהזיכרון הנדיף לזיכרון קבוע - מה שנקרא Serialization.
את דוגמת הקוד אפשר להוריד
כאן.
אז איך מבצעים את זה,
הדרך המורכבת והמסובכת היא - לכתוב לבד את כל המאפיינים של האובייקט לקובץ, זה לא מומלץ כי נצטרך כל הזמן לתחזק את הקוד הזה, כלומר במידה ויש לנו מאפיין חדש נצטרך ללכת לשנות את הקוד של הכתיבה לקובץ.
בכל מקרה אני אדגים גם את השיטה הזאת, דרך נוספת היא להשתמש במנגנונים מובנים של Net, ונוכל לבחור מתוך כמה מימושים של Net. אני אדגים כאן כרגע שניים, הראשון יהיה XmlSerializer והשני יהיה BinaryFormatter. יש עוד כמה, על אחד נוסף (DataContractSerializer) תוכלו לקרוא
כאן.
נניח שזה מבנה האובייקטים שלנו.
public abstract class Employee
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Programer : Employee
{
public bool KnowCSharp { get; set; }
}
public class Manager : Employee
{
public int NumberOfProject { get; set; }
}
הנה ה - Main שלנו.
static void Main(string[] args)
{
List<Employee> employees = new List<Employee>();
employees.Add(new Programer() { Id = 1, Name = "Shlomo", KnowCSharp = true });
employees.Add(new Programer() { Id = 2, Name = "Pini", KnowCSharp = true });
employees.Add(new Manager() { Id = 1, Name = "Alon", NumberOfProject = 100 });
}
כעת אנחנו רוצים לשמור את ה - List שלנו בקובץ.
הדוגמא הראשונה ממומשת על ידי המתכנת בכתיבה עצמאית לקובץ. (אני לא מדגים כאן כתיבת OO נכונה - כי חלק מהפעולות היו צריכים להיות בתוך המחלקות)
כתיבה לקובץ דוגמא 1.
static void WriteToFileCustom(List<Employee> employees)
{
using (StreamWriter writer = File.AppendText(@"C:\a.txt"))
{
foreach (var item in employees)
{
Programer programer = item as Programer;
if (programer != null)
{
writer.WriteLine("Programer,{0},{1},{2}",
programer.Id, programer.Name, programer.KnowCSharp);
continue;
}
Manager manager = item as Manager;
if (manager != null)
{
writer.WriteLine("Manager,{0},{1},{2}",
manager.Id, manager.Name, manager.NumberOfProject);
continue;
}
}
}
}
וכעת בקובץ יופיע לנו:
Programer,1,Shlomo,True
Programer,2,Pini,True
Manager,1,Alon,100
כעת נראה איך אפשר לשחזר את המידע מתוך הקובץ.
static List<Employee> ReadFromFileCustom()
{
List<Employee> employees = new List<Employee>();
string[] lines = File.ReadAllLines(@"C:\a.txt");
foreach (var item in lines)
{
string[] props = item.Split(',');
Employee employee = null;
if (props[0] == "Programer")
{
Programer programer = new Programer();
programer.KnowCSharp = bool.Parse(props[3]);
employee = programer;
}
else
{
Manager manager = new Manager();
manager.NumberOfProject = int.Parse(props[3]);
employee = manager;
}
employee.Id = int.Parse(props[1]);
employee.Name = props[2];
employees.Add(employee);
}
return employees;
}
כמו שכתבתי זו אינה הדרך הממולצת, עדיף להשתמש עם מנגנון מובנה.
והנה קוד שמדגים שימוש ב - XmlSerializer.
כדי להשתמש בזה אנחנו צריכים לעמוד בשני תנאים.
1. שיהיה לנו בנאי ללא פרמטרים.
2. להוסיף מעל כל המחלקות שלנו את ה - Attribute של Serializable
3. להוסיף מעל האבא את כל הבנים שלו. שיכולים לעבור סירליזציה דרכו.
ולכן המחלקות שלנו יראו כך:
[Serializable]
[XmlInclude(typeof(Programer))]
[XmlInclude(typeof(Manager))]
public abstract class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public Employee()
{
}
}
[Serializable]
public class Programer : Employee
{
public bool KnowCSharp { get; set; }
public Programer()
{
}
}
[Serializable]
public class Manager : Employee
{
public int NumberOfProject { get; set; }
public Manager()
{
}
}
עכשיו נוסיף מתודה לשמירה לקובץ.
static void WriteToFileXml(List<Employee> employees)
{
XmlSerializer serializer = new XmlSerializer(typeof(List<Employee>));
using (StreamWriter writer = File.AppendText(@"C:\a.xml"))
{
serializer.Serialize(writer, employees);
}
}
כמו שאפשר לראות, הקוד הוא הרבה יותר פשוט ולא צריך לתחזק אותו בכל פעם שמשנים את האובייקטים.
הנה הקובץ שנוצר:
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfEmployee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Employee xsi:type="Programer">
<Id>1</Id>
<Name>Shlomo</Name>
<KnowCSharp>true</KnowCSharp>
</Employee>
<Employee xsi:type="Programer">
<Id>2</Id>
<Name>Pini</Name>
<KnowCSharp>true</KnowCSharp>
</Employee>
<Employee xsi:type="Manager">
<Id>1</Id>
<Name>Alon</Name>
<NumberOfProject>100</NumberOfProject>
</Employee>
</ArrayOfEmployee>
והנה המתודה לשיחזור הנתונים מהקובץ.
static List<Employee> ReadFromFileXml()
{
XmlSerializer serializer = new XmlSerializer(typeof(List<Employee>));
using (StreamReader reader = new StreamReader(@"C:\a.xml"))
{
return (List<Employee>)serializer.Deserialize(reader);
}
}
דרך נוספת שאני רוצה להראות, היא באמצעות סירליזציה בינארית. כלומר - לקחת את מקטע הזיכרון של המידע ולעשות לו סירליזציה (ולשמור את המידע הבינארי בקובץ)
במקרה הזה מה שצריך זה רק את ה - Attribute של Serializable. אבל לא צריך בנאי ריק וגם לא צריך את המידע אודות כל הבנים (כי אנחנו מעבירים בלוק של זיכרון).
הנה המתודה לכתיבה.
static void WriteToFileBinary(List<Employee> employees)
{
BinaryFormatter binary = new BinaryFormatter();
using (Stream writer = new FileStream(@"C:\c.txt", FileMode.Create))
{
binary.Serialize(writer, employees);
}
}
אני לא יכול להעתיק לכאן את מה שנוצר היות שזה מידע בינארי.
הנה הקוד לשיחזור.
static List<Employee> ReadFromFileBinary()
{
BinaryFormatter binary = new BinaryFormatter();
using (Stream reader = new FileStream(@"C:\c.txt", FileMode.Open))
{
return (List<Employee>)binary.Deserialize(reader);
}
}