|
|
| נשלח ב-27/7/2011 11:37 |
|
| |
מדריך C# - תכנות מונחה עצמים: הורשה - Inheritance
הורשה הינה אחד מהעקרונות החשובים ביותר בתכנות מונחה עצמים (OOP). הורשה מאפשרת לממש את עקרון ה- reuse – שימוש חוזר בקוד ללא צורך לכתוב אותו מספר פעמים. מימוש הורשהבכדי לממש הורשה ב C# יש לכתוב במחלקה היורשת את מחלקת הבסיס שלה בצורה הבאה: class Base { } class Derived : Base { } דגשים חשובים לגבי הורשה:- כל מה שיש במחלקה עובר בהורשה כולל: משתנים, properties, פונקציות וכו'. למעט בנאים (c'tors).
- אלמנטים שמוגדרים כ- private עוברים בהורשה אך לא ניתן לגשת אליהם מהמחלקה היורשת.
- השימוש במחלקה היורשת הוא כשימוש במחלקה רגילה.
- במחלקה היורשת חובה להפעיל בנאי כלשהו ממחלקת הבסיס.
- הבנאי של מחלקת הבסיס יופעל קודם לבנאי של המחלקה היורשת.
- אם לא נציין במפורש איזה בנאי נרצה להפעיל יופעל אוטומטית הבנאי שאינו מקבל פרמטרים.
הפעלת בנאי ממחלקת הבסיס מתבצעת באמצעות המילה השמורה base ובאותה שיטה בה השתמשנו להפעלת בנאי אחר מאותה מחלקה (באמצעות this): class Base { public Base(int x) { //... } } class Derived : Base { public Derived(int x, int y) : base(x) { //... } } דריסת פונקציותלעיתים קיימת פונקציה במחלקת הבסיס אך היא אינה מתאימה בדיוק במימוש שלה למחלקה היורשת. לדוגמא: פונקציה שמדפיסה את כל המאפיינים של המחלקה. לעיתים היא לא מתאימה כלל ולעיתים היא מתאימה חלקית. ב- C# ניתן לדרוס פונקציה שקיבלנו ממחלקת הבסיס ואם נרצה (לא חובה) נוכל להשתמש בתוך המחלקה היורשת גם בפונקציה שקיבלנו ממחלקת הבסיס. השימוש יתבצע באמצעות המילה השמורה base ולאחריה נקודה וקריאה לפונקציה. לדוגמא: class Base { public void Print() { //... } } class Derived : Base { public new void Print() { //... base.Print(); //... } } דוגמא מלאה להורשה:להלן דוגמא מלאה להורשה הכוללת מחלקת בסיס בשם Person ושתי מחלקות יורשות בשם Employee,Student. דוגמא זו כוללת את כל הנושאים במאמר זה כולל הפעלת בנאי ודריסת פונקציות וכן דוגמא לשימוש בכל המחלקות. תרשים המחלקות: 
class Person { //Fields: string firstName; string lastName; //Properties: public string FirstName { get { return firstName; } set { firstName = value; } } public string LastName { get { return lastName; } set { lastName = value; } } //C'tors: public Person() : this("", "") { } public Person(string firstName, string lastName) { FirstName = firstName; LastName = lastName; } //Methods: public string Print() { return string.Format("\nName: {0} {1}", FirstName, LastName); } }
class Employee : Person { //Fields: private double salary; //Properties: public double Salary { get { return salary; } set { if (value < 0) throw new Exception("Illegal salary"); salary = value; } } //C'tors: public Employee() { Salary = 0; } public Employee(string fName, string lName, double salary) : base(fName, lName) { Salary = salary; } //Methods: public new string Print() { return base.Print() + string.Format("\nSalary: {0}", Salary); } } class Student : Person { //Fields: private int[] grades; //Properties: public int[] Grades { get { return grades; } set { grades = value; } } //C'tors: public Student() { Grades = new int[5]; } public Student(string fName, string lName, int[] grades) : base(fName, lName) { Grades = grades; } //Methods: public new string Print() { string str = base.Print() + "\nGrades:\t"; foreach (int g in Grades) str += g + "\t"; return str; } }
class Program { static void Main(string[] args) { Employee e1 = new Employee(); Employee e2 = new Employee("Lior", "Zamir", 25000); Console.WriteLine("Employees:"); Console.WriteLine(e1.Print()); Console.WriteLine(e2.Print()); int[] grades = new int[] { 90, 100, 85 }; Student s1 = new Student(); Student s2 = new Student("Avi", "Levi", grades); Console.WriteLine("\n--------------\n\nStudents:"); Console.WriteLine(s1.Print()); Console.WriteLine(s2.Print()); } }
פלט התוכנית:
 |
|
|
|
|
| נשלח ב-27/7/2011 11:37 |
|
| |
מדריך C# - תכנות מונחה עצמים: רב צורתיות - Polymorphism
פולימורפיזם (רב-צורתיות) הינה היכולת לתת מימוש שונה לאותה הפונקציה במחלקה היורשת כך שבזמן ריצה תופעל הפונקציה שמתאימה לטיפוס האובייקט. גם בנושא זה דנו במאמר המבוא ל- OOP ובמאמר זה נלמד איך לממש זאת. פולימורפיזם מתבסס על הורשה ואין פולימורפיזם ללא הורשה.
מימוש Polymorphismבכדי לממש פולימורפיזם יש תחילה להגדיר את הפונקציה במחלקת הבסיס כוירטואלית (virtual) ובמחלקה היורשת להגדיר לדרוס אותה באמצעות override. לאחר מכן יש ליצור ייחוס ממחלקת הבסיס שיצביע על אובייקט מסוג המחלקה היורשת ולהפעיל את הפונקציה. שימו לב שלא ניתן לבצע override אם הפונקציה במחלקת הבסיס לא הוגדרה ב- virtual ובדריסה רגילה, כפי שלמדנו בפרק ההורשה (באמצעות new) לא תופעל הפונקציה המתאימה לאובייקט. בנוסף חשוב לדעת שפונקציה שביצענו ל- override היא גם וירטואלית לדור הבא שירש אותה. בדוגמא הבאה תופעל הפונקציה Print השייכת למחלקה היורשת: class Program { static void Main(string[] args) { Base b = new Derived(); Console.WriteLine(b.Print()); } } class Base { public int X { get; set; } public virtual string Print() { return "Base print"; } } class Derived : Base { public int Y { get; set; } public override string Print() { return "Derived print"; } } גישה לאובייקטכאשר הייחוס הוא מסוג הבסיס והאובייקט הוא מסוג יורש לא ניתן לגשת לאלמנטים של היורש, אם ננסה לעשות זאת תתקבל שגיאת קומפילציה. אם בכל זאת נרצה לעשות נצטרך להצביע על האובייקט עם ייחוס המתאים לאובייקט (מסוג היורש). לדוגמא אם ננסה לבצע זאת נקבל שגיאת קומפילציה: b.Y = 10;
נצטרך לבצע זאת כך: Derived d = (Derived)b; d.Y = 10; או בקיצור: ((Derived)b).Y = 10;
שימושים של Polymorphism:- יצירת מערך הטרוגני – ניתן ליצור מערך מסוג הבסיס כך שכל איבר במערך יהיה אובייקט מסוג יורש וכאשר נרוץ על המערך ונפעיל את הפונקציה, תופעל הפונקציה המתאימה לאובייקט. היתרון הוא שלא נצטרך לבדוק עבור כל איבר מה הטיפוס שלו.
- קבלה לפונקציה – ניתן לבנות פונקציה המקבלת משתנה מסוג בסיס ולשלוח אליה בכל פעם אובייקט מסוג אחר היורש את הבסיס.
דוגמא מלאה ל- Polymorphism:להלן דוגמא מלאה הכוללת מימוש של פולימורפיזם בהורשה עם מספר דורות ושימושים של פולימורפיזם. תרשים המחלקות: 
class Person { //Fields: string firstName; string lastName; //Properties: public string FirstName { get { return firstName; } set { firstName = value; } } public string LastName { get { return lastName; } set { lastName = value; } } //C'tors: public Person() : this("", "") { } public Person(string firstName, string lastName) { FirstName = firstName; LastName = lastName; } //Methods: public virtual string Print() { return string.Format("Name: {0} {1}", FirstName, LastName); } }
class Student : Person { //Fields: private int[] grades; //Properties: public int[] Grades { get { return grades; } set { grades = value; } } //C'tors: public Student() { Grades = new int[5]; } public Student(string fName, string lName, int[] grades) : base(fName, lName) { Grades = grades; } //Methods: public override string Print() { string str = base.Print() + "\nGrades:\t"; foreach (int g in Grades) str += g + ";"; return str; } } class Employee : Person { //Fields: private double salary; //Properties: public double Salary { get { return salary; } set { if (value < 0) throw new Exception("Illegal salary"); salary = value; } } //C'tors: public Employee() { Salary = 0; } public Employee(string fName, string lName, double salary) : base(fName, lName) { Salary = salary; } //Methods: public override string Print() { return base.Print() + string.Format("\nSalary: {0}", Salary); } } class Manager : Employee { //Fields: private double bonus; //Properties: public double Bonus { get { return bonus; } set { if (value < 0) throw new Exception("Illegal bonus"); bonus = value; } } //C'tors: public Manager() { Bonus = 0; } public Manager(string fName, string lName, double salary, double bonus) : base(fName, lName, salary) { Bonus = bonus; } //Methods: public override string Print() { return base.Print() + string.Format("\tBonus: {0}", Bonus); } } class Program { static void Main(string[] args) { Person[] pArr = new Person[5]; pArr[0] = new Person("A","AA"); pArr[1] = new Employee("B", "BB",10000); pArr[2] = new Student("C", "CC",new int[]{80,100,90}); pArr[3] = new Person("D", "DD"); pArr[4] = new Manager("E", "EE",20000,5000); foreach (Person p in pArr) { Console.WriteLine("\n"+p.GetType().Name); Console.WriteLine(p.Print()); } Person p1 = new Person("Avi", "Levi"); Manager m1 = new Manager("Lior", "Zamir", 20000,5000); Console.WriteLine("\n---------\nPrinting Person:"); PrintPerson(p1); Console.WriteLine("\nPrinting Manager:"); PrintPerson(m1); } static void PrintPerson(Person p) { Console.WriteLine(p.Print()); } }
פלט התוכנית: 
 |
|
|
|
|
|
| נשלח ב-27/7/2011 11:37 |
|
| |
מדריך C# - תכנות מונחה עצמים: אוספים מותאמים וסדרנים (Indexers)
לעיתים נרצה לבנות Collection (אוסף) משלנו אשר יכיל בתוכו אובייקטים מסוג שאנו נבנה ויספק לנו את הפונקציונאליות הקשורה לאובייקטים שלנו. ישנם הרבה דרכים ליצור Custom collection, החלק מלבנות מאפס ועד לרשת מ- Collection קיים, לכל שיטה היתרונות והחסרונות שלה. לצורך ההבנה של הדברים בצורה מיטבית נתמקד בשיטה של לעטוף Collection קיים. הרעיון הוא לבנות מחלקה אשר תחזיק בתוכה משתנה מסוג ArrayList ותחשוף החוצה את הפונקציונאליות הנדרשת. מכיוון שאנו בונים מחלקה ישנה בעיה בגישה אליה באמצעות סוגריים מרובעים [ ] מכיוון שניתן לגשת רק למערכים באמצעות סוגריים מרובעים. לכן במחלקת ה- collection שלנו נבנה property מיוחד שנקרא indexer (סדרן) המאפשר גישה באמצעות [ ]. להלן דוגמא מלאה ל- Custom Collection עבור המחלקות שבנינו בפרק הפולימורפיזם, כולל מימוש indexer ושימוש ב- Collection: using System.Collections; namespace CustomCollections { class PersonCollection : IEnumerable { //Fields: private ArrayList pArr; //C'tors: public PersonCollection() { pArr = new ArrayList(); } //Properties: public int Count { get { return pArr.Count; } } //Indexers: public Person this[int idx] { get { return (Person)pArr[idx]; } set { pArr[idx] = value; } } public Person this[string fName] { get { for (int i = 0; i < Count; i++) if (this[i].FirstName == fName) return this[i]; //return the Person return null; //Person not Found! } } //Methods: public void Add(Person newPerson) { pArr.Add(newPerson); } public void Remove(Person p) { pArr.Remove(p); } public void RemoveAt(int idx) { pArr.RemoveAt(idx); } public string PrintAll() { string str = ""; for (int i = 0; i < Count; i++) { //str+=((Person)pArr[i]).Print(); //Same as: str += this[i].Print() + "\n"; } return str; } public double GetSumSalary() { double sum = 0; for (int i = 0; i < Count; i++) { if (this[i] is Employee) sum += ((Employee)this[i]).Salary; } return sum; } #region IEnumerable Members public IEnumerator GetEnumerator() { return pArr.GetEnumerator(); } #endregion } } using System; namespace CustomCollections { class Program { static void Main(string[] args) { PersonCollection pcol = new PersonCollection() { new Person("A","AA"), new Employee("B", "BB",10000), new Student("C", "CC",new int[]{80,100,90}), new Person("D", "DD"), new Manager("E", "EE",20000,5000) }; //using indexer (set): pcol[3] = new Person("Lior", "Zamir"); //using indexer (get): Console.WriteLine("** The fourth person:" + pcol["Lior"].Print()); Console.WriteLine("\n** Printing all persons:\n" + pcol.PrintAll()); Console.WriteLine("\n** Sum Salary (Employees only):\n" + pcol.GetSumSalary()); } } } פלט התוכנית: 
 |
|
|
|
|
| נשלח ב-27/7/2011 11:38 |
|
| |
מדריך C# - תכנות מונחה עצמים: ממשקים - Interfaces
interface (ממשק) הינו מבנה לוגי מופשט (אבסטרקטי) המכיל רק הצהרות. אחד מתפקידי ה- interface העיקריים הוא ליצור ממשק זהה לאובייקטים שונים (אפילו בהיררכיות שונות) ובכך הוא אחד היישומים של עיקרון הפולימורפיזם בתכנות מונחה העצמים (OOP). דוגמא ל- interface: interface IPrint { string Print(); } דגשים חשובים לגבי interface- אינו יכול להכיל משתנים או מימושים (פונקציות עם תוכן) אלא רק הצהרות. המחלקות/מבנים שירשו את ה- interface הם אלה שיממשו את ההצהרות.
- כאשר נממש (נירש) interface חובה לממש את כל ההצהרות שבו
- ניתן לרשת רק מחלקה אחת ובנוסף ניתן לרשת ולממש מספר בלתי מוגבל של interfaces
- לא ניתן ליצור אובייקט מ- interface אלא רק ייחוס (reference)
- גם struct יכול לרשת מ- interface
- נהוג ששם של interface יתחיל באות I
דוגמא למימוש interface: class Person : IPrint { public string Print() { return "Hello"; } } דוגמא לשימוש ב- interface: IPrint p = new Person(); Console.WriteLine(p.Print()); Interface הינו למעשה חוזה בין מחלקות המאפשר למתכנת לבנות תבנית ולבנות שירות המבוסס על תבנית זו וכל אחד שיממש את התבנית יוכל להשתמש בשירות זה. בסביבת ה- NET. יש מספר רב של interface המאפשרים להשתמש בשירותים שונים הקיימים בסביבה. להלן דוגמא לכמה מהם: - IComparable
- IComparer
- IEnumerable
- IEnumerator
ניקח כדוגמא את הממשק IComparable המספק פונקציה אחת להשוואה בין אובייקטים: public interface IComparable { int CompareTo(object obj); } להלן התיעוד של הפונקציה CompareTo המסביר מה צריך להיות הערך החוזר: //The return value has these meanings: //Value Meaning Less than zero This instance is less than obj. //Zero This instance is equal to obj. //Greater than zero This instance is greater than obj. השימוש ב- interface מאפשר להשתמש בשירותים ב- .NET הדורשים השוואה כגון מיון. אם נרצה לאפשר יכולת כזו למחלקה שלנו נצטרך לממש את ה- interface לפי הקווים המנחים המופיעים בתיעוד. לדוגמא נבנה מחלקה Person ונבצע את ההשוואה לפי הגיל: public class Person : IComparable { public int Age { get; set; } public string Print() { return string.Format("Age: {0}", Age); } public int CompareTo(object obj) { Person p = (Person)obj; if (Age < p.Age) return -1; if (Age > p.Age) return 1; return 0; } } עכשיו ניתן להשתמש בשירות Sort הקיים במחלקה Array אשר משתמש ב- interface כדי לבצע את ההשוואה: class Program { static void Main(string[] args) { Person[] persons = new Person[] { new Person() { Age = 25 }, new Person() { Age = 17 }, new Person() { Age = 30 } }; Console.WriteLine("Before Sorting:"); foreach (Person p in persons) Console.WriteLine(p.Print()); Array.Sort(persons); Console.WriteLine("\nAfter Sorting:"); foreach (Person p in persons) Console.WriteLine(p.Print()); } } בפלט ניתן לראות את מצב המערך לפני ואחרי המיון: 
 |
|
|
|
|
| נשלח ב-27/7/2011 11:38 |
|
| |
מדריך C# - תכנות מונחה עצמים: הרשאות גישה
להלן טבלה המסכמת את הרשאות הגישה ב- #C: רמת נגישות | המשמעות | האם חל עלnamespaces ? | האם חל עלTypes ? (enum, struct, class, interface, delegate) | האם חל עלmembers ? (variable, const, readonly, property, method, event) | private ("פרטי") | הגישה מוגבלת לסוג (type) המכיל בלבד. | לא | לא | כן (ברירת מחדל ל- members שבתוך class או struct) | protected ("מוגן") | הגישה מוגבלת למחלקה (class) המכילהולמחלקות היורשות אותה. | לא | לא | כן | internalprotected ("פנימי-מוגן") | הגישה מוגבלת לפרויקט (assembly) הנוכחי או למחלקות היורשות את המחלקה (גם אם הם בפרויקט אחר). | לא | לא | כן | internal ("פנימי") | הגישה מוגבלת לפרויקט (assembly) הנוכחי בלבד. | לא | כן (ברירת מחדל) | כן | public ("ציבורי") | גישה לא מוגבלת. | כן (ברירת מחדל) | כן | כן (ל- members שבתוך enum או interface זוהיברירת מחדל ולא ניתן להגדיר אחרת) |
|
|
|
|
| נשלח ב-27/7/2011 11:38 |
|
| |
מדריך C# - תכנות מונחה עצמים: מחלקה אבסטרקטית
מחלקה אבסטרקטית הינה מחלקה אשר מהווה בסיס למחלקות אחרות, אך אין לנו באמת צורך באובייקטים ממנה בתוכנית. - בכדי ליצור מחלקה אבסטרקטית יש לכתוב abstract לפני שם ה- class
- לא ניתן ליצור אובייקטים ממחלקה אבסטרקטית
- ניתן ליצור ייחוס ממחלקה אבסטרקטית (בד"כ לצורכי פולימורפיזם)
- מחלקה אבסטרקטית יכולה להכיל כל דבר שמחלקה רגילה יכולה להכיל
- מחלקה אבסטרקטית יכולה (אבל לא חייבת) להכיל פונקציות אבסטרקטיות
- לא ניתן לכתוב פונקציה אבסטרקטית במחלקה שהיא לא אבסטרקטית
- פונקציה אבסטרקטית היא פונקציה שאין לה מימוש. היא נכתבת כהצהרה ללא גוף (כלומר ללא בלוק של פקודות)
- כאשר יורשים מחלקה אבסטרקטית חובה לממש את כל הפונקציות האבסטרקטית שבה (אם יש)
- המימוש של פונקציה אבסטרקטית הוא באמצעות מילת המפתח override
להלן דוגמא ליצירת מחלקה אבסטרקטית עם פונקציה אבסטרקטית: abstract class Shape { public abstract double GetArea(); } class Circle : Shape { public double Radius { get; set; } public override double GetArea() { return Math.PI * Radius * Radius; } } class Program { static void Main(string[] args) { Shape b = new Circle() { Radius = 5 }; Console.WriteLine(b.GetArea()); } } השוואה בין virtual ל- abstract| | abstract | virtual | מימוש הפונקציה במחלקה | אין מימוש של הפונקציה - לא צריך להגדיר את גוף הפונקציה, רק כותרת. | יש לממש את הפונקציה (בדומה לפונקציה רגילה). משמש לפולימורפיזם. | מימוש הפונקציה במחלקות היורשות | חובה לממש במחלקות היורשות בצורה ישירה. | לא חובה לממש. | מיקום הפונקציה | במחלקה אבסטרקטית בלבד | בכל מחלקה | הצהרה | לא private (משום שאז הפונקציה לא תיראה במחלקות הנגזרות ולא נוכל לממש אותה)
|
 |
|
|
|
|
| נשלח ב-27/7/2011 11:39 |
|
| |
מדריך #C מתקדם - Generics
לעיתים נרצה לבנות מחלקות אשר מאד דומות בתבניתם, ושונות רק בסוג הנתונים שהם מחזיקות.
דוגמאות: - נרצה מחלקה Point המייצגת נקודה במישור בעלות יכולות, כמו למשל להזיז את הנקודה, כאשר לעיתים נרצה שהאורדינאטות (x,y) של הנקודה יהיו מסוג int ולעיתים נרצה שיהיו מסוג double (או מכל סוג אחר – long, byte, decimal...)
- מחלקה לניהול רשימה של נתונים (Collection) עם יכולת להוסיף ולמחוק איברים כאשר בכל פעם נרצה לשים שם נתונים אחרים
בעזרת הידע שיש לנו עד עתה נצטרך לבנות את המחלקות מספר פעמים, פעם אחת עבור כל טיפוס. אפשרות אחרת היא להשתמש בטיפוס object אך אפשרות זו לא מספיק טובה כי היא מחייבת אותנו לבצע casting בכל פעם שנרצה לעבוד עם המשתנה ובנוסף אנו עלולים לטעות ולהכניס למשתנה משתנה מטיפוס שלא התכוונו אליו והקומפיילר לא יתריע על כך, אלא נקבל את השגיאה רק בזמן ריצה ב- casting הלא חוקי. Generics נותן פתרון לבעיה זו בכך שהוא מאפשר להגדיר גם את סוג המשתנה כפרמטר, כך שנוכל ביצירת האובייקט לקבוע מה יהיה הטיפוס עבור האובייקט המסוים ובאובייקט אחר נקבע טיפוס אחר. בשיטה זו לא נצטרך לבצע casting מפני שאנו מגדירים את הטיפוס ואם ננסה להכניס ערך מטיפוס לא נכון בטעות נקבל שגיאת קומפילציה. מיד לאחר שם המחלקה לדוגמא: class Point <T> { ... } לאחר מכן נוכל להשתמש בפרמטר זה בתוך המחלקה: class Point<T> { private T x; private T y; public T X { get { return x; } set { x = value; } } public T Y { get { return y; } set { y = value; } } public Point(T x, T y) { X = x; Y = y; } public override string ToString() { return string.Format("X = {0}, Y = {1}", X, Y); } }
כאשר ניצור את האובייקט נספק לו את הפרמטר של הטיפוס גם כן בסוגריים משולשים. לדוגמא Point כאשר ה- T הוא מסוג int: Point<int> pInt = new Point<int>(10, 5);
שימו לב שבדוגמא הבאה שתי השורות האחרונות יגררו שגיאת קומפילציה מפני שטיפוס הנתונים מוגדר מראש ולא ניתן להכניס ערך מסוג אחר: static void Main(string[] args) { Point<int> pInt = new Point<int>(10, 5); Point<string> pString = new Point<string>("a", "b"); pInt.X = "a"; pString.X = 10; } עוד נקודות חשובות לגבי Generics: - ניתן לקבל יותר מפרמטר אחד עבור מספר סוגים של משתנים.
- ניתן לבנות כמעט כל אלמנט שאנו מכירים כ- generics כגון: class, struct, method, delegate, interface
- ניתן לרשת מחלקה generics ולהגדיר לה את הטיפוסים כקבועים
דוגמא להורשת מחלקה generics: class PointDouble : Point<double> { }
בספריות של NET. ישנם הרבה טיפוסים מוכנים המממשים את הרעיון של Generics וזמינים לשימושינו, לדוגמא: - <IComparable<T - למימוש IComparable עם הפרמטר המתאים לפונקציה CompareTo
- <List<T - דומה ל ArrayList אך עם הגדרת הטיפוס של הנתונים ביצירת האובייקט
- <Dictionary<TKey, TValue - דומה ל- Hashtable עם הגדרת הסוג של ה- key ושל ה- value ביצירת האובייקט
 |
|
|
|
|
| נשלח ב-27/7/2011 11:40 |
|
| |
מדריך #C מתקדם - Generics: Constraints – התניות על פרמטרים גנריים
ניתן להגביל את הטיפוס שנהיה מוכנים לקבל כפרמטר גנרי באמצעות המילה השמורה where. בדרך-כלל נגביל את הטיפוסל- class, struct או שנחייב אותו לממש ממשק (interface) מסוים. הפונקציה בדוגמא הבאה, מקבלת 2 פרמטרים גנריים. הראשון חייב להיות struct היורש משני הממשקים IComparable, IFormattable. הפרמטר השני חייב להיות class היורש מהממשק ICloneable: public void MethodWithConstrain<T, K>(T a, K b) where T : struct, IComparable, IFormattable where K : class, ICloneable { Console.WriteLine(a); Console.WriteLine(b); } דוגמא נוספת – שימו לב ל- 11 ההעמסות של פונקצית Max של מחלקת Math:

בדרך-כלל, ניתן לחסוך מימושים שונים של אותה הפונקציה (בהעמסות שונות של סוגי טיפוסים) על-ידי שימוש במנגנון הג'נרי. כלומר, באמצעות Generics ,לדוגמא, נוכל ליצור פונקציה אחת שמקבלת טיפוס כלשהו שהוא בר-השוואה (שניתן להשוות בין ערכים שונים מאותו הטיפוס) ולחסוך את המימושים הנוספים המיותרים: public static T Max<T>(T a, T b) where T : IComparable { if (a.CompareTo(b) > 0) return a; return b; } פונקציה זו יכולה להשוות בין שני פריטים כלשהם, מכל class/struct קיים/עתידי כל עוד הוא עונה לקריטריון הבא: יורש מהממשק IComparable. Console.WriteLine(Max<int>(3, 5)); Console.WriteLine(Max<double>(9.5, 3.6)); Console.WriteLine(Max<string>("Hi", "Bye")); Console.WriteLine(Max<DateTime>(DateTime.Now, new DateTime(2011, 2, 1))); Console.WriteLine(Max<Person>(p1, p2)); (הטיפוס Person הוא דוגמא למחלקה שאנחנו יכולים ליצור בעצמנו, ליישם לה IComparable, ואז להפעיל עליה את הפונקציה הנ"ל). הפלט: 
באופן זה ניתן להגביל גם את הטיפוסים במחלקות (נחייב את הטיפוס לממש את IComparable): class Point <T> where T : IComparable { … }
 |
|
|
|
|
| נשלח ב-27/7/2011 11:40 |
|
| |
מדריך #C מתקדם - אוספים גנריים - Generic Collections
ב- Net. קיימים מספר אוספים הנמצאים ב- namespace הנקרא System.Collections. להלן דוגמא למספר אוספים נפוצים: - ArrayList – מייצג אוסף דינמי של פריטים.
- Hashtable – מייצג אוסף של זוגות ערכים (key/value) הממויין לפי ה- hash code של ה- key.
- SortedList – מייצג אוסף של זוגות ערכים (key/value) הממויין לפי הערך של ה- key.
- Queue – מייצג אוסף של פריטים בתור (הראשון שנכנס לאוסף, הוא הראשון שייצא – FIFO).
- Stack – מייצג אוסף של פריטים במחסנית (האחרון שנכנס לאוסף, הוא הראשון שייצא – LIFO).
לשימוש באוספים אלו שני חסרונות עיקריים: - ביצועים – שימוש באוספים הנ"ל יכול לגרום לביצועים נמוכים כאשר אנו מכניסים לאוסף טיפוסים מסוג struct בשל הפעולות בזיכרון המתבצעות בזמן ריצה ע"י המנגנונים של Net. (בתהליך הנקרא boxing שבו מתייחסים ל- value-type כגון struct כ- reference type). כלומר, פגיעה במהירות זמן הריצה של התוכנית.
- המרות – האוספים הרגילים אינם סוגים בטוחים (Typed-Safe) מיכוון שהם עובדים עם מחלקת Object. זה אומנם מקנה להם את היכולת להכיל כל טיפוס אך מנגד יוצר בעיה שהאוסף יכול לקבל טיפוסים שאנחנו לא רוצים שיהיו בו. וכמובן שכל שליפה של פריטים מהאוסף תאלץ אותנו להשתמש בהמרות מיותרות. הפיתרון הנהוג היה ליצור מחלקות אוסף משלנו ולממש אותם לטיפוס מסויים - פיתרון שמצריך המון עבודה.
וכאן נכנס לתמונה השימוש ב- Generics (הקיים החל מגירסה 2 של .Net): שימוש באוספים גנריים. לכל האוספים הקלאסיים המוזכרים לעייל יש גירסה גנרית שניתן להשתמש בה. האוספים נמצאים ב- namespace הנקרא System.Collections.Generic (מופיע כברירת מחדל ב- using בראש של כל קובץ C# החל מגירסה 2). אוספים אלו משתמשים בטיפוס גנרי מסויים אותו אנו מגדירים בקוד, כך ששני החסרונות שציינו לא רלוונטיים באוספים אלו. לדוגמא, על-מנת ליצור אוסף גנרי דינמי (המסוגל לגדול/לקטון בהתאם לצורך) המתנהג כמו האוסף הקלאסי ArrayList אך ללא החסרונות שציינו, ניתן להשתמש באוסף הגנרי <>List : List<int> numbers = new List<int>();
בדוגמא הנ"ל בחרנו לאסוף מספרים שלמים (int). כמובן שניתן לאסוף כל טיפוס נתונים שנרצה. האוסף מכיל את כל הפונקציונליות הרלוונטית (הוספה/הסרת פריטים, פונקציות שירות כמו מיון וכו'): List<int> numbers = new List<int>(); numbers.Add(500); numbers.Add(-25); numbers.Add(17); numbers.Sort(); Console.WriteLine("The numbers (After a Sort):"); foreach (int n in numbers) Console.WriteLine(n); Console.WriteLine("Count: {0} numbers", numbers.Count); הפלט: 
 |
|
|
|
|
| נשלח ב-27/7/2011 11:40 |
|
| |
מדריך #C מתקדם – נציגים - Delegates
נציגים (delegates) ואירועים (events) הינם נושאים מאד מרכזיים בעבודה ב- NET. . הם מאפשרים למפתח לבנות רכיב בלי להגביל את מי שמשתמש בו לפעולות מסוימות. delegate הוא טיפוס המצביע על פונקציה. תפקידו של ה- delegate הוא ליצור אובייקט המצביע על פונקציה. במקום להפעיל את הפונקציה בצורה הרגילה, נפעיל אותה דרך ה- delegate. היתרון של עבודה עם delegate הוא בהצבעה על פונקציה שאנו לא מכירים בזמן כתיבה של רכיב (מחלקה / פונקציה וכד') מסוים, ומי שיקבע איזו פונקציה תופעל הוא מי שמשתמש ברכיב. להלן דוגמא שננתח אותה מיד: namespace Delegates { delegate int Calc(int x, int y); class Program { static int Add(int a, int b) { return a + b; } static void Main(string[] args) { Calc c= new Calc(Add); int result = c(4, 6); Console.WriteLine(result); } } } הגדרת delegate מתבצעת באמצעות המילה השמורה delegate ולאחריה חתימה של פונקציה, כאשר שם "הפונקציה" הוא למעשה שם ה- delegate. להלן הגדרת delegate בשם Calc: delegate int Calc(int x, int y);
delegate יכול להצביע רק על פונקציות שחתימתן היא בדיוק כמו חתימת ה- delegate. ה- delegate (נציג) נקרא כך מיכוון שתפקידו "לייצג" פונקציות. בדוגמא שלנו ה- delegate יכול להצביע רק על פונקציות שמקבלות בדיוק שני פרמטרים מסוג int ומחזירות int. כלומר, הוא יוכל לייצג כל פונקציה שעונה לתנאים אלו בלבד – מקבלת שני מספרים שלמים (int) ומחזירה מספר שלם. להלן דוגמא להצבעה על פונקציה בשם Add: Calc c = new Calc(Add);
בכדי להפעיל את הפונקציה דרך ה- delegate יש להתייחס למשתנה שיצרנו כאילו הוא שם הפונקציה. בדוגמא שלהלן יודפס 10 (הסכום של 4,6): int result = c(4, 6); Console.WriteLine(result); Multicast delegatedelegate יכול להצביע גם על מספר פונקציות, ניתן לעשות זאת באמצעות האופרטור =+ לדוגמא: namespace Delegates { delegate int Calc(int x, int y); class Program { static int Add(int a, int b) { Console.WriteLine("Add"); return a + b; } static int Sub(int a, int b) { Console.WriteLine("Sub"); return a - b; } static void Main(string[] args) { Calc c; c = new Calc(Add); c += new Calc(Sub); int result = c(4, 6); Console.WriteLine(result); } } } בפלט התוכנית ניתן לראות שהופעלו 2 הפונקציות אך התשובה שהוחזרה היא התשובה של הפונקציה האחרונה שהופעלה: 
עד כאן הבנו איך יוצרים ומשתמשים ב- delegate להלן דוגמא הממחישה למה צריך delegate (מיד ננתח אותה): namespace SortWithDelegate { public delegate int CompareDeleg(int a, int b); class Program { static int[] arr; static int CompareAsc(int x, int y) { return x - y; } static int CompareDesc(int x, int y) { return y - x; } static void Sort(CompareDeleg compareMethod) { for (int i = 0; i < arr.Length - 1; i++) for (int j = i + 1; j < arr.Length; j++) if (compareMethod(arr[i], arr[j]) > 0) Replace(i, j); } private static void Replace(int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } static void PrintArr() { foreach (int num in arr) Console.Write(num + ","); Console.WriteLine(); } static void Main(string[] args) { arr = new int[] { 123, 200, -63, 2, 7612, -13, 8 }; Console.WriteLine("Orginal nuumbers:"); PrintArr(); Sort(new CompareDeleg(CompareAsc)); Console.WriteLine("\nAscending order:"); PrintArr(); Sort(new CompareDeleg(CompareDesc)); Console.WriteLine("\nDescending order:"); PrintArr(); } } } בתוכנית זו ישנה פונקציה בשם Sort שתפקידה למיין מערך מסוג []int. חישבו שהיינו רוצים למיין את המערך בצורות שונות פעם בסדר עולה ופעם בסדר יורד, עם הידע שיש לנו עד היום היינו צריכים לבנות 2 פונקציות, אחת למיון עולה ואחת למיון יורד. ואם בעתיד היינו רוצים שיטת מיון אחרת (חישבו למשל על תאריכים שאפשר למיין בשיטות שונות: שנים, רבעונים, חודשים ועוד). הרעיון הוא שהפונקציה Sort תקבל את המערך ו- delegate בשם CompareDeleg שיטפל בצורת המיון, כך שהפונקציה לא תגביל לסוג מיון מסוים אלא תיתן למי שמפעיל אותה לקבוע את סוג המיון. ה- delegate בנוי כך שהוא מקבל שני מספרים (int) ומחזיר מספר המייצג מי יותר גדול בצורה הבאה: - מספר חיובי – המספר הראשון יותר גדול
- מספר שלילי – מספר השני יותר גדול
- אפס – המספרים שווים
כעת כאשר נרצה להפעיל את הפונקציה נשלח אליה את המערך וכתובת של פונקציה אשר מגדירה את צורת המיון ובכל פעם נוכל לשלוח צורת מיון אחרת. ניתן לראות בדוגמא שישנן 2 פונקציות שכל אחת מהן מייצגת מיון בצורה אחרת: - CompareAsc – למיון עולה
- CompareDesc – למיון יורד
בכל הפעלה של הפונקציה Sort אנו שולחים כתובת של פונקציה אחרת ולאחר מכן מדפיסים את המערך. בפלט התוכנית ניתן לראות שבכל הדפסה סדר המיון הוא שונה (המצב הראשוני, עולה, יורד): 
 |
|
|
|
|
| נשלח ב-27/7/2011 11:41 |
|
| |
מדריך #C מתקדם – אירועים - Events
event – הודעה שאובייקט שולח לאובייקטים אחרים. event הינו למעשה מקרה פרטי של delegate. הרעיון של event הינו להצביע על פונקציה והוא מספק מספר הגבלות אשר לא קיימות ב- delegate רגיל: - ניתן להגדיר event רק כחבר מחלקה (member) ולא ניתן בתוך פונקציה או כפרמטר לפונקציה
- ניתן להפעיל event רק באותה מחלקה בה הוא נכתב
- ניתן להוסיף ולהוריד פונקציות מ- event באמצעות =+, =- (כמו multicast delegate), אך לא ניתן לדרוס את מה שיש שם באמצעות =
הגבלות אלו מספקות ביטחון גבוה יותר, אשר מונע טעויות כמו דרישה של הפונקציות אליהם מצביע ה- event, ולכן אם לא נצטרך משהו שסותר הגדרות אלו (כגון פרמטר לפונקציה), נעדיף להשתמש ב- event. בכדי להגדיר event צריך תחילה להגדיר delegate, לדוגמא: public delegate void SpeedDelegate(int newSpeed);
לאחר מכן יש להגדיר משתנה מסוג ה- delegate בצרוף המילה השמורה event: public event SpeedDelegate SpeedChanged;
רישום פונקציה ל- event (הצבעה) והפעלת ה- event הינם בדיוק כמו ב- delegate (למעט ההגבלה של =). להלן דוגמא שמיד ננתח: public delegate void SpeedDelegate(int newSpeed);
class Car { public event SpeedDelegate SpeedChanged; private int speed; public int Speed { get { return speed; } } public void AddSpeed(int delta) { speed += delta; if (SpeedChanged != null) SpeedChanged(speed); } } class Program { static void Main(string[] args) { Car c = new Car(); //רישום הפונקציה לאירוע c.SpeedChanged += new SpeedDelegate(PrintSpeed); c.AddSpeed(16); c.AddSpeed(28); c.AddSpeed(52); c.AddSpeed(-10); c.AddSpeed(14); } static void PrintSpeed(int newSpeed) { Console.WriteLine(newSpeed); } } בדוגמא זו ישנה מחלקה בשם Car המאפשרת שינוי מהירות הרכב. למחלקה event בשם SpeedChanged אשר מודיע כאשר השתנתה מהירות הרכב. בתוכנית רשמנו את הפונקציה PrintSpeed ל- event ובכל פעם שנוסיף מהירות (5 פעמים במקרה שלנו) ה- event יפעיל את הפונקציה הזו שמדפיסה את המהירות החדשה. פלט התוכנית: 
השימוש באירועים נפוץ ב- .NET בעיקר בסביבות עשירות ב- GUI, למשל, WinForms, WPF, ASP.NET וכו'. השימוש באירועים בסביבות אלו מאפשר, למשל, להפעיל פונקציות המתבצעות כתוצאה מפעולה של המשתמש. לדוגמא, אירוע של לחיצה על כפתור יגרור תגובה בצורה של פונקציה המסוגלת לבצע אוסף של פעולות. public partial class MyForm : Form { Button myButton; public MyForm() { InitializeComponent(); //הרישום לאירוע myButton.Click += new EventHandler(myButton_Click); } //הפונקציה המתבצעת כתוצאה מהפעלת האירוע private void myButton_Click(object sender, EventArgs e) { MessageBox.Show("Test"); } } אנחנו לא יכולים לדעת מראש (וגם לא גם צריכים לדעת) האם המשתמש יחלץ על הכפתור, ואם כן, מתי זה יקרה וכמה פעמים. אבל בטוח שבכל פעם שהמשתמש יחלץ על הכפתור האירוע Click יתעורר ויפעיל את הפונקציה או הפונקציות שנרשמו אליו מראש.
 |
|
|
|
|
| נשלח ב-27/7/2011 11:41 |
|
| |
מדריך #C מתקדם - Attributes - תכונות
Attributes הן תכונות שניתן לתת לאלמנט תכונתי כלשהו כגון: מחלקה, פונקציה, assembly ועוד. התכונה הינה למעשה מידע נוסף שאנו מספקים לאלמנט אשר בד"כ גורר אחריו התנהגות כלשהי. בשלב זה נכיר את התחביר ונלמד על מספר attributes פשוטים כאשר בהמשך נלמד על סוגים רבים של attributes בנושאים הרלוונטיים. כל attribute שניצור וכל attribute שכבר קיים ב-NET., הוא למעשה מחלקה (class) היורשת בצורה ישירה או עקיפה ממחלקה הנקראת Attribute. את ה- attribute נכתוב באמצעות סוגריים מרובעים מעל האלמנט אליו נרצה לשייך אותו: [AttributeName] Some element לדוגמא, attribute בשם Obsolete מעל הפונקציה Print: [Obsolete] static void Print() { Console.WriteLine("Print"); } ניתן להעביר פרמטרים ל- attribute במידה והוא מקבל לדוגמא: [Obsolete("This method is obsolete, please use other method")] static void Print() { Console.WriteLine("Print"); }כשנקמפל את הפרויקט נקבל את האזהרה הבאה: 
דוגמא נוספת ל- attribute: כאשר נרצה לתת attribute ל- assembly נכתוב זאת בקובץ AssemblyInfo.cs שנמצא בספריה Properties שבפרויקט ונעשה זאת כך: [assembly: AssemblyTitle("MyApplication")]
AssemblyTitle מייצג כותרת ל- assembly שתוצג במאפייני הקובץ במערכת ההפעלה.
 |
|
|
|
|
| נשלח ב-27/7/2011 11:42 |
|
| |
מדריך #C מתקדם - Operators Overloading - העמסת אופרטורים
ב- #C ניתן להעמיס פונקציות, בנאים ואופרטורים. העמסת אופרטורים (Operator Overloading) מאפשרת לתת משמעות רצויה לאופרטור כאשר הוא יופעל על המחלקה שלנו. המטרה היא לאפשר הפעלת אופרטורים על המחלקה שלנו או מתן מימוש שונה לאופרטורים שכבר ניתן להפעיל (כגון ==). התחביר להעמסת אופרטורים מתחלק ל- 2 סוגים: - אופרטור אונרי (Unary) – הפועל רק על אופרנד אחד, כגון ++a
- אופרטור בינארי (Binary) – הפועל על 2 אופרנדים, כגון a+b
העמסת אופרטור אונרי: public static Circle operator ++(Circle c) { //מימוש } מבנה כותרת ההעמסה של אופרטור אונרי יהיה בדיוק בסדר הבא: - public static
- הערך המוחזר (Circle בדוגמא הנ"ל)
- מילת המפתח operator
- האופרטור שרוצים להעמיס (++ בדוגמא)
- פרמטר מטיפוס המחלקה ( Circle c בדוגמא)
העמסת אופרטור בינארי: public static Circle operator +(Circle c, int x) { //מימוש } מבנה כותרת ההעמסה של אופרטור בינארי יהיה בדיוק בסדר הבא: - public static
- הערך המוחזר (Circle בדוגמא הנ"ל)
- מילת המפתח operator
- האופרטור שרוצים להעמיס (++ בדוגמא)
- פרמטר מטיפוס המחלקה Circle c)בדוגמא)
- פרמטר נוסף, מטיפוס כלשהו (int x בדוגמא)
לדוגמא, מימוש האופרטור ++ (אונרי) והאופרטור + (בינארי) במחלקה Circle: class Circle { public double Radius { get; set; } public static Circle operator ++(Circle c) { c.Radius++; return c; } public static Circle operator +(Circle c, int x) { Circle newC = new Circle(); newC.Radius = c.Radius + x; return newC; } } שימוש באופרטורים: class Program { static void Main(string[] args) { Circle c = new Circle() { Radius = 10 }; c++; Console.WriteLine(c.Radius); c = c + 5; Console.WriteLine(c.Radius); } } פלט התוכנית: 
האופרטורים הניתנים להעמסה:אופרטורים בינאריים | + | - | * | / | % | | | ^ | >> | << | & | אופרטורים אונריים | + | - | ++ | -- | ! | ~ | true | false |
|
| אופרטורים השוואתיים /לוגיים | == | =! | < | > | =< | => |
|
|
|
|
האופרטורים שלא ניתנים להעמסה:אופרטורים מיוחדים | = | . | :? | && | || |
| אופרטורים מילות-מפתח | new | is | sizeof | typeof | checked | unchecked | אופרטורים מורכבים | =+ | =- | =* | =/ | =% |
| סוגריים | ( | ) | [ | ] | { | } |
כללים להעמסת אופרטורים- מוגדרים תמיד כ- public static
- העמסה של אופרטור השוואתי (לוגי) מחייבת העמסה של האופרטור המנוגד (כולל true,false). לדוגמא, העמסה של == תחייב העמסה גם של =!
- העמסה של אופרטור השוואתי תחזיר ערך בוליאני בלבד
- העמסה של אופרטורים ==, =! מחייבים מימוש הפונקציה Equals ופונקציה זו מחייבת מימוש הפונקציה GetHashCode
- בכדי למנוע בילבול לא מומלץ לשנות את המשמעות המקורית (והמובנת) של האופרטור
העמסת castingניתן להעמיס גם את האופרטורים המבצעים casting בין טיפוסים. לדוגמא נרצה לאפשר המרה בין string ל- Circle ובחזרה, ובין double ל- Circle ובחזרה. תזכורת: - explicit casting – המרה מפורשת שבה חובה לבצע כתיבת ה- casting
- implicit casting – המרה מרומזת ללא צורך בכתיבת ה- casting
מימוש המרות מפורשות (explicitly casting) –
- המרה מפורשת מ- string ל- Circle :
public static explicit operator Circle(string str) { Circle c = new Circle(); c.Radius = double.Parse(str); return c; } - המרה מפורשת מ-double ל- Circle :
public static explicit operator Circle(double dbl) { Circle c = new Circle(); c.Radius = dbl; return c; } מימוש המרות מרומזות (implicitly casting) -
- המרה מרומזת מ- Circle ל- string:
public static implicit operator string(Circle c) { return c.Radius.ToString(); } - המרה מרומזת מ- Circle ל- double:
public static implicit operator double(Circle c) { return c.Radius; } שימוש: static void Main(string[] args) { string str = "9"; //explicitly convert String to Circle: Circle c1 = (Circle)str; Console.WriteLine(c1.Radius); double dbl = 7; //explicitly convert Double to Circle: Circle c2 = (Circle)dbl; Console.WriteLine(c2.Radius); c1 += 10; str = c1; //implicitly convert Circle to String Console.WriteLine(str); c2 += 10; dbl = c2; //implicitly convert Circle to Double Console.WriteLine(dbl); } פלט התוכנית: 
 |
|
|
|
|
| נשלח ב-27/7/2011 11:42 |
|
| |
אני מבקשת לא להוסיף לאשכול זה תגובות נוספות. אם יש הצעות ייעול - אשמח לקבל בפרטי.
|
|
|
|
|