Built-In Interfaces and Classes

Introduction

The .NET library provides a large collection of interfaces that you can implement in your classes. Many of the classes available in the .NET library implement these interfaces. In some of your projects, you may have to implement one or more of the existing interfaces for your class.

Practical LearningPractical Learning: Introducing Built-In Interfaces

  1. Start Microsoft Visual Studio
  2. Create a C# Console App named FormattingValues

Formatting a Value

The collection of techniques and formulas used by a language to display its values is referred to as a format provider. When you use a variable that uses a particular formula to display its value, to help you specify the right formula, the .NET library provides an interface named IFormatProvider. IFormatProvider is defined in the System namespace.

There are two main ways you can use the IFormatProvider interface. You can create a class and implement it. The IFormatProvider interface is equipped with only one method: GetFormat.

In most cases, you will use classes that already implement the IFormatProvider interface. Those classes are equipped with an overridden version of the ToString() method that uses a parameter of type IFormatProvider. Its syntax is:

public string ToString(IFormatProvider provider)

This method requires that you create an IFormatProvider object and pass it as argument. An alternative is to pass a string. This is possible with another version of the ToString() method whose syntax is:

public string ToString(string format)

This method takes a string as argument. The string can take a letter as one of the following:

Letter Description
c C Currency values
d D Decimal numbers
e E Scientific numeric display such as 1.45e5
f F Fixed decimal numbers
d D General and most common type of numbers
n N Natural numbers
r R Roundtrip formatting
x X Hexadecimal formatting
p P Percentages

Practical LearningPractical Learning: Formatting Some Values

  1. Change the document as follows:
    using static System.Console;
    
    (int, double) GetNumbers()
    {
        int x = 0;
        double y = 0.00;
    
        try
        {
            Write("Decimal Number:              ");
            y = double.Parse(ReadLine()!);
        }
        catch (FormatException)
        {
            WriteLine("Make sure you enter a valid decimal number.");
        }
    
        try
        {
            Write("Natural Number:              ");
            x = int.Parse(ReadLine()!);
        }
        catch (FormatException)
        {
            WriteLine("You must provide an appropriate natural number.");
        }
    
        return (x, y);
    }
    
    (int natural, double fractional) values = GetNumbers();
    
    WriteLine("=============================================");
    WriteLine("Decimal Number Fixed:        {0}", values.fractional.ToString("f"));
    WriteLine("---------------------------------------------");
    WriteLine("Natural Number Decimal:      {0}", values.natural.ToString("D"));
    WriteLine("---------------------------------------------");
    WriteLine("Natural Number Currency:     {0}", values.natural.ToString("c"));
    WriteLine("---------------------------------------------");
    WriteLine("Decimal Number Currency:     {0}", values.fractional.ToString("c"));
    WriteLine("---------------------------------------------");
    WriteLine("Decimal Number Scientific:   {0}", values.natural.ToString("e"));
    WriteLine("---------------------------------------------");
    WriteLine("Natural Number Scientific:   {0}", values.fractional.ToString("e"));
    WriteLine("---------------------------------------------");
    WriteLine("Decimal Number Natural:      {0}", values.fractional.ToString("n"));
    WriteLine("---------------------------------------------");
    WriteLine("Natural Number Natural:      {0}", values.natural.ToString("n"));
    WriteLine("---------------------------------------------");
    WriteLine("Decimal Number Natural:      {0}", values.fractional.ToString("n"));
    WriteLine("---------------------------------------------");
    WriteLine("Natural Number Natural:      {0}", values.natural.ToString("n"));
    WriteLine("---------------------------------------------");
    WriteLine("Decimal Number General:      {0}", values.fractional.ToString("g"));
    WriteLine("---------------------------------------------");
    WriteLine("Natural Number Hexadecimal:  {0}", values.natural.ToString("x"));
    WriteLine("=============================================");
  2. To execute the application to see the results, press Ctrl + F5
  3. When requested, type the Decimal Number as 13972418.65 and press Tab
  4. Type the Natural Number as 13972418 and press Enter
    Decimal Number:              13972418.65
    Natural Number:              13972418
    =============================================
    Decimal Number Fixed:        13972418.65
    ---------------------------------------------
    Natural Number Decimal:      13972418
    ---------------------------------------------
    Natural Number Currency:     $13,972,418.00
    ---------------------------------------------
    Decimal Number Currency:     $13,972,418.65
    ---------------------------------------------
    Decimal Number Scientific:   1.397242e+007
    ---------------------------------------------
    Natural Number Scientific:   1.397242e+007
    ---------------------------------------------
    Decimal Number Natural:      13,972,418.65
    ---------------------------------------------
    Natural Number Natural:      13,972,418.00
    ---------------------------------------------
    Decimal Number Natural:      13,972,418.65
    ---------------------------------------------
    Natural Number Natural:      13,972,418.00
    ---------------------------------------------
    Decimal Number General:      13972418.65
    ---------------------------------------------
    Natural Number Hexadecimal:  d533c2
    =============================================
    
    Press any key to close this window . . .
  5. To close the window, press Enter, and return to your programming environment

Cloning an Object

Consider a class as follows:

using System;

public class Circle
{
    public Circle()
    {
        Radius = 0.00;
    }

    public Circle(double rad)
    {
        Radius = rad;
    }

    public double Radius { get; set; }
    public double Area { get { return Radius * Radius * Math.PI; } }
    public double Diameter { get { return Radius * 2; } }

    public double Circumferemce
    {
        get { return Diameter * Math.PI; }
    }
}

Copying an object consists of creating another sample of it and that contains the same values as the original. To make this operation available to your classes, you can implement an interface named ICloneable. The ICloneable interface is defined in the System namespace of the mscorlib.dll library.

The ICloneable interface is equipped with one method named Clone. Its syntax is:

object Clone();

To assist you with making a copy of a variable, the Object class is equipped with a method named MemberwiseClone. This means that all classes of the .NET library and any class you create in your C# application automatically inherits this method. The syntax of this method is:

protected Object MemberwiseClone();

When implementing the ICloneable interface, in your class, you can simply call the MemberwiseClone() method. Here is an example:

using System;

public class Cylinder : Circle,
                        ICloneable
{
    public double Height { get; set; }

    public Cylinder(double baseRadius, double height)
    {
        Radius = baseRadius;
        Height = height;
    }

    public double LateralArea
    {
        get
        {
            return Circumferemce * Height;
        }
    }

    public double Volume
    {
        get
        {
            return Area * Height;
        }
    }

    public object Clone()
    {
        return this.MemberwiseClone();
    }
}

Comparing Two Objects

Comparing two objects consists of finding out which one comes first, for whatever criterion (or criteria, plural) you want to consider. The comparison is simple if you are dealing with values of primitive types. For example, it is easy to know that 2 is lower than 5, but it is not obvious to compare two objects created from a composite type, such as two students, two cars, or two food items. Consider the following Student class:

public enum Genders { Male, Female, Unknown }

public class Student
{
    public Student(int number = 0, string first = "John",
                   string last = "Doe", double ag = 1,
                   Genders gdr = Genders.Unknown)
    {
        StudentNumber = number;
        FirstName     = first;
        LastName      = last;
        Age           = ag;
        Gender        = gdr;
    }

    public int     StudentNumber { get; set; }
    public string  FirstName     { get; set; }
    public string  LastName      { get; set; }
    public double  Age           { get; set; }
    public Genders Gender        { get; set; }
}

To assist you with comparing two objects, the .NET library provides various comparable interfaces. One of these interfaces is named IComparable. The IComparable interface is a member of the System namespace. Obviously you must define what would be compared and how the comparison would be carried. For example, to compare two Student objects of the above class, you can ask the compiler to base the comparison on the student number. Here is an example:

using System;

public enum Genders { Male, Female, Unknown }

public class Student : IComparable
{
    public Student(int number = 0, string first = "John",
                   string last = "Doe", double ag = 1,
                   Genders gdr = Genders.Unknown)
    {
        StudentNumber = number;
        FirstName     = first;
        LastName      = last;
        Age           = ag;
        Gender        = gdr;
    }

    public int     StudentNumber { get; set; }
    public string  FirstName     { get; set; }
    public string  LastName      { get; set; }
    public double  Age           { get; set; }
    public Genders Gender        { get; set; }

    public int CompareTo(object obj)
    {
        Student std = (Student)obj;

        return std.StudentNumber.CompareTo(this.StudentNumber);
    }
}

Here is an example of comparing two Student objects:

using System;
using static System.Console;

Student std1 = new Student(294759, "Patricia", "Katts", 14.50, Genders.Female);
Student std2 = new Student(294706, "Raymond", "Kouma", 18.50, Genders.Male);
Student std3 = new Student(747747, "Patricia", "Childs", 12.00, Genders.Female);

WriteLine("Red Oak High School");
WriteLine("-----------------------");
        
WriteLine("Comparison by Student Number");
Write("First == Second: ");
WriteLine(std1.CompareTo(std2));
Write("First == Third:  ");
WriteLine(std1.CompareTo(std3));
WriteLine("=============================");

public enum Genders { Male, Female, Unknown }

public class Student : IComparable
{
    public Student(int number = 0, string first = "John",
                   string last = "Doe", double ag = 1,
                   Genders gdr = Genders.Unknown)
    {
        StudentNumber = number;
        FirstName = first;
        LastName = last;
        Age = ag;
        Gender = gdr;
    }

    public int StudentNumber { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public double Age { get; set; }
    public Genders Gender { get; set; }

    public int CompareTo(object obj)
    {
        Student std = (Student)obj;

        return std.StudentNumber.CompareTo(this.StudentNumber);
    }
}

In the same way, you can choose any other member of the class to base the comparison. An example would consist of comparing the last names of students or comparing each property of one object to the corresponding property of the other object.

Most of the .NET library's classes that need to perform comparison already implement the IComparable interface or one of its equivalents.

Disposing of an Object

Introduction

Most objects that are used in an application consume memory and other resources. Some objects use more resources than others. Whenever you have finished using an object, you should (mostly must) make sure you free the resources it was using. In traditional programming, you would add a destructor to your class. Then, in the destructor, you would call the method(s) from which your objects consume resources. Here is an example:

using System;

public class Book
{
    public int numberOfPages;

    public void Read()
    {
    }
}

public class Student
{
    public Book text;

    public Student()
    {
        text = new Book();
    }

    public void ReleaseTheResources()
    {
        
    }

    ~Student()
    {
        ReleaseTheResources();
    }
}

To assist you in dismissing the resources used in your application, the .NET library provides an interface named IDisposable. Most of the time, you will not need to implement this interface because most of the classes that are resource-intensive have already been created and you will just use them. Those classes directly or indirectly implement the IDisposable interface. Here is an example of a class that already implement the IDisposable interface:

public class ScrollableControl : Control,
	                         IComponent,
		                 IDisposable
{
}

There are two main ways you will use the IDisposable interface. One way is that, if you have to, that is, if you judge it necessary, you can create a class that implements the IDisposable interface. The IDisposable interface is defined in the System namespace that is a member of the mscorlib.dll library. This means that you don't have to import any library to use it. The IDisposable interface contains only one method, named Dispose. Its syntax is:

void Dispose();

When implementing the interface, use this method to free the resources a variable of the class was using. After calling this method, remember that the destructor of a class is always called when the variable gets out of scope. For this reason, it is a good idea to create a destructor for the class and call the Dispose() method from it. Here is an example:

using System;

public class Book
{
    public int numberOfPages;

    public void Read()
    {
    }
}

public class Student : IDisposable
{
    public Book text;

    public Student()
    {
        text = new Book();
    }

    public void Dispose()
    {
        
    }

    ~Student()
    {
        Dispose();
    }
}

Using and Disposing of an Object

The second way you can use the IDisposable interface is the most important for us. To support the ability to release the resources that an object was using, the C# language provides an operator named using. The formula to use it is:

using(parameter) { }

As you can see, this operator uses the formula of a method (or function). It is equipped with parentheses and a body delimited by curly brackets. In the parentheses, declare the variable that will use the resources you are concerned with. Inside the brackets, use the variable anyway you want and that is appropriate. When the compiler reaches the closing curly bracket, it does what is necessary. For example, the compiler may have to close a connection that was used or it would delete the object that was consuming the resources. Here is an example of using using:

using System;

public class Book
{
    public int numberOfPages;

    public void Read()
    {
    }
}

public class Student : IDisposable
{
    public Book text;

    public Student()
    {
        text = new Book();
    }
    
    public void Buy()
    {
    }

    public void Dispose()
    {
       
    }

    ~Student()
    {
        Dispose();
    }
}

public class Exercise
{
    public int View()
    {
        using (Student std = new Student())
        {
            std.Buy();
        }

        return 0;
    }
}

Further Managing Objects and Values

The Equality of Two Values

Remember that the .NET Framework provides the Object class that is the ancestor of all .NET classes. To let you find out whether two values of your applications are the same, the Object class is equipped with an overloaded Boolean method named Equals. The Equals() method comes in two versions. The first has the following syntax:

public virtual bool Equals(object obj);

This version allows you to call the Equals() method on a declared variable and pass the other variable as argument. The primitive types (int, double, bool, etc) are already configured to use this method. Here is an example:

using static System.Console;

int integer = 248;
int number = 248;

WriteLine("========================");
WriteLine("Integer: {0}", integer);
WriteLine("Number:  {0}", number);

WriteLine("------------------------");
if (integer.Equals(number))
    WriteLine("Those numbers are equal.");
else // if (integer.Equals(number) == false)
    WriteLine("Those numbers are different.");
WriteLine("========================");

This would produce:

========================
Integer: 248
Number:  248
------------------------
Those numbers are equal.
========================
Press any key to close this window . . .

In your application, you can call the Object.Equals() method to check the equailty of two values. As an alternative, you can use the == operators.

The first version of the Object.Equals() method is declared as virtual, which means you can override it if you create your own class. The second version of the Object.Equals() method is:

public static bool Equals(object obj2, object obj2);

As a static method, to use it, you can pass the variables of the two classes whose values you want to compare. Here is an example:

using static System.Console;

int distance = 928_304;
int length = 928_304;

WriteLine("=========================");
WriteLine("Distance: {0}", distance);
WriteLine("Length:   {0}", length);
WriteLine("-------------------------");

if(int.Equals(distance, length))
    WriteLine("Those numbers are equal.");
else // if (int.Equals(distance, length))
    WriteLine("Those numbers are equal.");
WriteLine("=========================");

This would produce:

=========================
Distance: 928304
Length:   928304
-------------------------
Those numbers are equal.
=========================

Press any key to close this window . . .

In both cases, if the values of the variables are similar, the Equals() method returns true. If they are different, the method returns false.

The Equality of Two Objects

Consider the following code

using static System.Console;

Element h1 = new(1, "H", "Hydrogen", 1.008, Phase.Gas);
Element h2 = new(1, "H", "Hydrogen", 1.008, Phase.Gas);

if(h1.Equals(h2))
    WriteLine("Element 1 and Element 2 are the same.");
else
    WriteLine("Element 1 and Element 2 are different.");
Write("======================================");

public enum Phase { Gas, Liquid, Solid, Unknown }

public class Element(int number, string symbol, string name, double mass, Phase phase)
{
    internal int AtomicNumber
    {
        get
        {
            return number;
        }
    }

    internal string ElementName
    {
        get
        {
            return name;
        }
    }
    internal string Symbol
    {
        get
        {
            return symbol;
        }
    }
    internal double AtomicWeight
    {
        get
        {
            return mass;
        }
    }
    internal Phase Phase
    {
        get
        {
            return phase;
        }
    }
}

The code uses a class. Two objects are created from that class. The objects hold the exact same values. The above code would produce:

Element 1 and Element 2 are different.
======================================
Press any key to close this window . . .

This means that, when you create a class, it is not directly ready to perform an equality comparison on its objects. You might have noticed that the Object.Equals() method is virtual. This means that, when you create a class, if you are planning to compare its objects for equality, you must override the Equals() method that all classes inherit from the Object class. When overriding the Object.Equals() method, it is recommended that you also override the Object.GetHashCode() method. To override the Object.Equals() method, you have various options. As one solution, in your implementation of the Object.Equals() method, you can write a conditional statement that compares just one of the primitive properties. This can be done as follows:

using static System.Console;

Element h1 = new(1, "H", "Hydrogen", 1.008, Phase.Gas);
Element h2 = new(1, "H", "Hydrogen", 1.008, Phase.Gas);

if(h1.Equals(h2))
    WriteLine("Element 1 and Element 2 are the same.");
else
    WriteLine("Element 1 and Element 2 are different.");
Write("======================================");

public enum Phase { Gas, Liquid, Solid, Unknown }

public class Element(int number, string symbol, string name, double mass, Phase phase)
{
    internal int AtomicNumber
    {
        get { return number; }
    }

    internal string ElementName
    {
        get { return name; }
    }
    internal string Symbol { get { return symbol; } }
    internal double AtomicWeight
    {
        get => mass;
    }
    internal Phase Phase => phase;

    public override bool Equals(object? obj)
    {
        Element? elm = obj as Element;

        if (elm?.AtomicNumber == this.AtomicNumber)
        {
            return true;
        }
        return base.Equals(obj);
    }

    public override int GetHashCode()
    {
        return base.GetHashCode();
    }
}

This would produce:

Element 1 and Element 2 are the same.
======================================
Press any key to close this window . . .

Another way to override the Object.Equals() method is to compare more than one property or each property of the class with the equavalent property of the argument.

Remember that the Object class provides a second version of its Equals() method. That second version is static and it takes two objects arguments. Once you have overriden the first version of the method, you can call the version to check the equality of two objects. Here is an example:

using static System.Console;

Element h1 = new(1, "H", "Hydrogen", 1.008, Phase.Gas);
Element h2 = new(1, "H", "Hydrogen", 1.008, Phase.Gas);

if( Equals(h1, h2) )
    WriteLine("Element 1 and Element 2 are the same.");
else
    WriteLine("Element 1 and Element 2 are different.");
Write("======================================");

public enum Phase { Gas, Liquid, Solid, Unknown }

public class Element(int number, string symbol, string name, double mass, Phase phase)
{
    internal int AtomicNumber { get { return number; } }

    internal string ElementName { get { return name; }}
    internal string Symbol { get => symbol; }
    internal double AtomicWeight => mass;
    internal Phase  Phase        => phase;

    public override bool Equals(object? obj)
    {
        Element? elm = obj as Element;

        if (elm?.AtomicNumber == this.AtomicNumber)
        {
            return true;
        }

        return base.Equals(obj);
    }

    public override int GetHashCode()
    {
        return base.GetHashCode();
    }
}

Practical LearningPractical Learning: Ending the Lesson


Previous Copyright © 2001-2024, FunctionX Monday o4 December 2024 Next