-
- 5.1. IComparable<T> / IComparer<T>
- 5.2. ICloneable
-
- 7.1. Abstract classes
- 7.2. Custom Interfaces
- Declaring enums
- Declaring structures
- Declaring classes
- Encapsulation using properties;
- Classes with multiple constructors
The enum keyword is used to declare an enumeration, a distinct type that consists of a set of named constants called the enumerator list. By default, the first enumerator has the value 0, and the value of each successive enumerator is increased by 1.
-
Add the following enumeration
internal enum OccupationEnum { Child = 0, Student, Employee }
-
In the Main method try to cast from
-
UserAccountTypeEnum.SoftwareDeveloper to System.Int32
-
System.Int32 to UserAccountTypeEnum
-
A Structure (struct in C#) type is a value type that is typically used to encapsulate small groups of related variables.
-
Add the PersonStruct defined below
internal struct PersonStruct { #region Attributes public int Age; public string Name; public OccupationEnum Occupation; #endregion public PersonStruct(int age, string name, OccupationEnum occupation) { Age = age; Name = name; Occupation = occupation; } public override string ToString() { return string.Format("Name: {0}, Age: {1}, Occupation: {2}", Name, Age, Occupation); } }
-
Add the ValueTypeAssignment method in Program.cs and call it from the Main() method.
private static void ValueTypeAssignment() { Console.WriteLine("###Assigning value types\n"); var personStruct1 = new PersonStruct(1, "name1", OccupationEnum.Student); var personStruct2 = personStruct1; Console.WriteLine(personStruct1); // automatically calls .ToString(). The method is defined in System.Object and overridden in PersonStruct Console.WriteLine(personStruct2); // Change personStruct1.Name and print again. personStruct2.Name is not changed. personStruct1.Name = "NewName"; Console.WriteLine(personStruct1); Console.WriteLine(personStruct2); }
Questions
- Why is it possible to override the
ToString
method?
-
Add the PersonClass class defined below.
internal class PersonClass { #region Properties #region Age - Without using Properties private int _age; public int GetAge() { return _age; // "this._age" is implicit } public void SetAge(int value) { _age = value; // "this._age" is implicit } #endregion #region Name - Using properties private string _name; //Read/Write property public string Name { get { return _name; } set { _name = value; } } //Readonly property public string Name2 { get { return _name; } } #endregion #region Occupation - Using auto-property public OccupationEnum Occupation { get; set; } #endregion #endregion public PersonClass(int age) { Console.WriteLine("Constructor(default)"); _age = age; //equivalent with this._age = age; } public PersonClass(int age, string name, OccupationEnum occupation):this(age) { Console.WriteLine("Constructor(parameters)"); Name = name; Occupation = occupation; } //Copy constructor - https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/how-to-write-a-copy-constructor public PersonClass(PersonClass previousPerson) : this(previousPerson.GetAge(), previousPerson.Name, previousPerson.Occupation) { Console.WriteLine("Copy Constructor"); } //Destructor ~PersonClass() { Console.WriteLine("Destructor"); } public override string ToString() { return string.Format("Name: {0}, Age: {1}, Occupation: {2}", Name, _age, Occupation); } }
-
Add the ReferenceTypeAssignment method in Program.cs and call it from the Main() method.
private static void ReferenceTypeAssignment() { Console.WriteLine("Assigning reference types\n"); var personClass1 = new PersonClass(1, "name1", OccupationEnum.Student); var personClass2 = personClass1; Console.WriteLine(personClass1); // automatically calls .ToString(). The method is defined in System.Object and overridden in PersonClass Console.WriteLine(personClass2); // Change personClass1.Name and _age and print again. personClass2.Name and _age have changed. personClass1.Name = "NewUserName"; personClass1.SetAge(22); Console.WriteLine("\n=> Changed personClass1.Name and personClass1._age\n"); Console.WriteLine(personClass1); Console.WriteLine(personClass2); }
Question
- Can the
PersonClass()
constructor be made private? (can we have private constructors?)
-
Create a new project with the name “StandardInterfaces”
-
Add the following
Person
classHint: You can generate the constructor by choosing the corresponding option in Visual Studio.
internal class Person { #region Properties public string Name { get; set; } public int Age { get; set; } #endregion public Person(string name, int age) { Name = name; Age = age; } }
-
Add the following method in the
Program
class and call it from theMain()
method (Note: an exception will be thown when you run the project)private static void ReferenceTypeArray() { var p1 = new Person("Name3", 42); var p2 = new Person("Name1", 23); var p3 = new Person("Name2", 32); var pArray = new Person[] { p1, p2, p3 }; Array.Sort(pArray); //IComparable implementation is called automatically by methods such as Array..::.Sort foreach (var person in pArray) { Console.WriteLine(person); } }
-
Implement the
IComparable<Person>
interface for thePerson
class.internal class Person : IComparable<Person> { #region Properties public string Name { get; set; } public int Age { get; set; } #endregion public Person(string name, int age) { Name = name; Age = age; } public int CompareTo(Person other) { //Note: string.CompareTo is culture-specific return Name.CompareTo(other.Name); } }
-
Change the
IComparable<Person>
implementation in order to sort the persons in descending order, based on their age -
Change the
IComparable<Person>
implementation in order to sort the persons using 2 criteria at the same time (name and age) -
Add a new class to the project that implements the
IComparer<Person>
interface in order to sort the persons in ascending order based on their name -
Add a new class to the project that implements the
IComparer<Person>
interface in order to sort the persons in descending order based on their name
-
Based on the
Person
class, derive theStudent
class.internal class Student : Person { public int[] Marks { get; set; } public Student(string name, int age, int[] marks) : base(name, age) { Marks = marks; } }
-
Add the following method in the
Program
class and call it from theMain
methodprivate static void ShallowCopyEqualOperator() { var p1 = new Student("Name 1", 21, new []{10, 10, 10}); var p2 = p1; p1.Age = 12; p1.Marks[0] = 2; Console.WriteLine(p1); Console.WriteLine(p2); }
-
Run the application and notice the values in the two objects
-
Implement the
ICloneable
interface for theStudent
class as follows (shallow copy only)internal class Student : Person, ICloneable { public int[] Marks { get; set; } public Student(string name, int age, int[] marks) : base(name, age) { Marks = marks; } public object Clone() { // First get a shallow copy. var clone = (Student)MemberwiseClone(); return clone; } }
-
Add the following method in the
Program
class and call it from theMain
methodprivate static void DeepCopyICloneable() { var p1 = new Student("Name 1", 21, new[] { 10, 10, 10 }); Console.WriteLine(); var p2 = p1.Clone(); p1.Age = 12; p1.Marks[0] = 1; Console.WriteLine(p1); Console.WriteLine(p2); }
-
Run the application and check the values in the two objects using the Watch window (run the application in Debug mode)
-
Change the implementation of the
Clone()
method in order to perform a deep copypublic object Clone() { // First get a shallow copy. var newPerson = (Student)MemberwiseClone(); // Then fill in the gaps. newPerson.Marks = new int[Marks.Length]; for (var i=0; i< Marks.Length; i++) { newPerson.Marks[i] = Marks[i]; } //newPerson.Marks = (int[])Marks.Clone(); return newPerson; }
-
Change only the line in which you call the
Clone
method in theDeepCopyICloneable
method as follows.//Cast to base type var p2 = ((Person)p1).Clone();
-
Run the application and check the values in the two objects. What happened? How would you fix the code? (hint:
virtual
).
- can be overload by defining static member functions using the operator keyword.
- not all operators can be overloaded and others have restrictions
- further reading: link
Operators | Overloadability |
---|---|
+, -, !, ~, ++, --, true, false | These unary operators can be overloaded. |
+, -, *, /, %, &, |, ^, <<, >> | These binary operators can be overloaded. |
==, !=, <, >, <=, >= | The comparison operators can be overloaded |
&&, || | The conditional logical operators cannot be overloaded, but they are evaluated using & and |, which can be overloaded. |
[] | The array indexing operator cannot be overloaded, but you can define indexers. |
(T)x | The cast operator cannot be overloaded, but you can define new conversion operators (see explicit and implicit). |
+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= | assignment operators cannot be overloaded, but +=, for example, is evaluated using +, which can be overloaded. |
=, ., ?:, ??, ->, =>, f(x), as, checked, unchecked, default, delegate, is, new, sizeof, typeof | These operators cannot be overloaded. |
Sample code available – Check the “Operators” Sample
-
For the standard Person class overload the >, < and explicit (int) operators
#region Operators // operator de conversie explicita la int public static explicit operator int(Person p) { return p.Age; } public static bool operator <(Person p1, Person p2) { return p1.Age < p2.Age; } public static bool operator >(Person p1, Person p2) { return p1.Age > p2.Age; } #endregion
-
Use the operators in the
Main
methodprivate static void Main(string[] args) { var p = new Person("Name1", 21); var p2 = new Person("Name2", 22); //int age = p; //error int age = (int)p; Console.WriteLine("Age: {0}", age); if(p<p2) Console.WriteLine("p.Age < p2.Age"); }
-
Implement the implicit (int) cast
-
Add the following
Fraction
classclass Fraction { int num, den; public Fraction(int num, int den) { this.num = num; this.den = den; } // overload operator + public static Fraction operator +(Fraction a, Fraction b) { return new Fraction(a.num * b.den + b.num * a.den, a.den * b.den); } // overload operator * public static Fraction operator *(Fraction a, Fraction b) { return new Fraction(a.num * b.num, a.den * b.den); } // user-defined conversion from Fraction to double public static implicit operator double(Fraction f) { return (double)f.num / f.den; } }
-
Add the following method in the
Program
class and call it from theMain()
method.static void Main() { Fraction a = new Fraction(1, 2); Fraction b = new Fraction(3, 7); Fraction c = new Fraction(2, 3); Console.WriteLine((double)(a * b + c)); }
Let’s imagine that you are asked to develop an application that handles the wage and bonus calculations for the persons that work in a certain software development company. The categories of persons are: Software Developer, Managers.
Sample code available – Check the "Inheritance" Sample
-
Create a new project that will include the classes shown in Figure 1.
-
Add an abstract Person class (keep in mind that we only consider the three types of person categories mentioned above)
internal abstract class Person { public string Name { get; set; } public Person(string name) { Name = name; } }
-
Add an abstract
Employee
classHint: You can generate the constructor of the
Employee
class by clicking onPerson
and choosing the corresponding option from the contextual menu.internal abstract class Employee : Person { #region Normal/Virtual/Abstract Methods public void PrintWageNormal() { Console.WriteLine("Employee - PrintWageNormal"); } public virtual void PrintWageVirtual() { Console.WriteLine("Employee - PrintWageVirtual"); } public abstract void PrintWageAbstract(); #endregion public Employee(string name) : base(name) { } }
-
Add a SoftwareDeveloper class
internal class SoftwareDeveloper : Employee, IDeveloper { #region Normal/Virtual/Abstract Methods public new void PrintWageNormal() { Console.WriteLine("SoftwareDeveloper - PrintWageNormal"); } public override void PrintWageVirtual() { Console.WriteLine("SoftwareDeveloper - CalculateBonusVirtual"); } public override void PrintWageAbstract() { Console.WriteLine("SoftwareDeveloper - PrintWageAbstract"); } #endregion #region IDeveloper public string[] Languages { get; set; } public bool Knows(string language) { return Languages.Contains(language); } #endregion public SoftwareDeveloper(string name) : base(name) { } }
-
Add the following method to the “Program” class and call it from the “Main()” method. Inside the method declare a SoftwareDeveloper object and call the CalculateBonusAbstract, CalculateBonusNormal and CalculateBonusVirtual
private static void AbstractNormalVirtualMethods() { SoftwareDeveloper sd = new SoftwareDeveloper("SoftwareDeveloper1"); Employee e = (Employee) sd; //same instance as above //Normal method Console.Write("\n###Hide"); sd.PrintWageNormal(); e.PrintWageNormal(); //Virtual method Console.Write("\n###Override"); sd.PrintWageVirtual(); e.PrintWageVirtual(); //Abstract method Console.WriteLine("\n###Abstract"); sd.PrintWageAbstract(); e.PrintWageAbstract(); }
-
In the previous method declare an array of
Employee[]
and call the previously mentioned methods
Let’s imagine that the company starts to work with external contractors. You are required to add this category of persons to the previously developed application. Moreover, the management is interested to quickly find what programming languages a Software Developers or Contractor knows.
-
Add the
Contractor
classinternal class Contractor : Person { }
-
Add the
IDeveloper
interfaceinternal interface IDeveloper { string[] Languages { get; set; } bool Knows(string language); }
-
Derive the
SoftwareDevloper
andContractor
classes from theIDeveloper
interface -
Add a new method to the
Program
class and call it from theMain()
method. Inside the method define an array of persons and populate it with the three categories of persons in the company (SofwareDeveloper, Manager, Contractor). -
In the previous method iterate the list of persons and display the known programming languages for each person
-
Search for all the persons that know “C#”.