Laboratory Training 3

Inheritance and Polymorphism

1 Training Tasks

1.1 Individual task

Expand the program that was created in the previous laboratory training with a hierarchy of entities that are stored in the array, according to the following table:

Base Class Derived Class Derived Class
Student Budget student Contract student
Educational subject Subject with an exam Credit subject
Part of the city Ward The historical part of the city
Settlement Town Village
Participant of the sports section Member Head
A member of a football club Player Coach
A member of a musical group Member Head
Collection of songs Album Collection of sheet music
Music Song Instrumental work
Housing Room Service room
Work Story Novel
Work of art Painting Graphics
The subway facility Station Depot
Railway object Station Stop
Work Novel A play

Reproduce tasks of previous laboratory training. It is also necessary to override the ToString() method for all classes.

Provide methods for adding and removing an entity from an array.

Implement the functions of saving data in a text file and loading data from a text file. Reproduce tasks of previous laboratory trainings.

Implement handling possible exceptions.

1.2 Roots of an Equation

Implement a program that allows you to find all the roots of a certain equation on a given interval. Algorithm for finding roots is a sequential scan on the interval with a certain step. If the function changes sign on particular subrange, you should print the arithmetic mean of subinterval beginning and end.

Implement two approaches: Implement two approaches: using abstract classes and with interfaces.

1.3 Working with Text Files

Develop a program that performs copying from one file to another file only strings whose length is less than some integer value.

2 Instructions

2.1 Inheritance

Inheritance is the creation of derived classes from base classes. Objects of derived class implicitly contain all the fields of the base class, including private, despite the fact that the methods of the derived class cannot access private members of the base class. In addition, all public properties and methods are inherited. The base class members with protected modifier are available from derived classes.

Thanks to inheritance and the creation of class hierarchies, it is possible to significantly reduce the number of fragments of similar code present in different classes, which would be a violation of one of the fundamental principles of programming, the principle of DRY ("Don't Repeat Yourself"). In addition, inheritance allows you to implement one of the principles of SOLID - the Open / Closed Principle (OCP), according to which classes should be open for extension, but closed for modification.

Unlike C++, C# allows only single inheritance for classes. In contrast to C++, inheritance is always public. To create derived class, you should use colon character followed by base class name:

class Rectangle : Shape 
{
  // class body
}

All .NET classes are implicitly or explicitly derived from System.Object.

Constructors cannot be inherited. Each class of hierarchy should implement its own set of constructors. The base class constructor is automatically called each time you invoke constructor of a derived class.

The base keyword is used for access to base class elements from the derived class, especially:

  • for invocation of overridden method of a base class
  • for transferring arguments to base class constructor

For example:

class BaseClass
{
    int i, j;

public BaseClass(int i, int j) { this.i = i; this.j = j; } } class DerivedClass : BaseClass { int k;
public DerivedClass() : base(0, 0)
{
k = 0;
} public DerivedClass(int i, int j, int k) : base(i, j) { this.k = k; } }

You can create class with sealed modifier. Such class cannot be used as base class for creation of descendants. Methods with sealed modifier cannot be overridden.

You can implicitly convert reference to derived object to base class type, but not vice versa.

BaseClass b = new DerivedClass();
DerivedClass d = new DerivedClass();
b = d; // OK
d = b; // Compile error!

You can realize reverse conversion explicitly. If conversion cannot be done, an exception will be thrown, System.InvalidCastException. If you want to avoid exception handling, you can use as operator.

BaseClass b1 = new DerivedClass();
BaseClass b2 = new BaseClass();
DerivedClass d1 = b1 as DerivedClass; // OK
DerivedClass d2 = b2 as DerivedClass; // null

The is operator returns true if type cast is possible and false otherwise:

BaseClass b1 = new DerivedClass();
BaseClass b2 = new BaseClass();
if (b1 is DerivedClass)
{
    DerivedClass d1 = (DerivedClass) b1; // OK
}
if (b2 is DerivedClass)
{
    DerivedClass d2 = (DerivedClass) b2; // not executed
}

The as and is operators are imported form Delphi Pascal.

One of the principles of SOLID is related to inheritance - the Liskov substitution principle (LSP), according to which objects can be replaced by their descendants without changing the code. Inheritance should not limit the functionality of base classes. For example, the Square class should not be created as a derivative of the Rectangle class, since it is actually a narrowing of the type, not an extension of the type. Where a program requires any rectangle, you cannot use a square because you cannot change the height and width of such a rectangle separately. More correct derived classes from the "Rectangle" class are "Colored rectangle", "Rectangle with rounded corners", "Rectangle with text", etc.

Private and protected inheritance (types of inheritance introduced in C++ and absent in other object-oriented programming languages) are incompatible with the LSP principle.

2.2 Polymorphism. Interfaces

2.2.1 Runtime Polymorphism

The runtime polymorphism is a feature of classes, according to which the behavior of objects are determined not at compile time but at runtime. The concept of polymorphism and the polymorphic classes is generally common in all object-oriented programming languages, especially in C#.

All classes of C# are polymorphic, since they are derived from polymorphic class System.Object. Such other object-oriented programming languages, C# provides polymorphism based on mechanism of virtual methods. You must define virtual methods using virtual keyword in the base class and override keyword in derived classes. If you want to overload virtual method and break chain of polymorphism, you should use new keyword before function header.

class Shape
{
    public virtual void Draw()
    {
        . . .
    }
}

class Circle : Shape 
{
    public override void Draw()
    {
        . . .
    }
}

class Rectangle : Shape 
{
    public new void Draw()
    {
        . . .
    }
}

Often there is a need for override virtual methods of System.Object class. For example, in order to represent the object as a string, you need to define ToString() method, which returns necessary string. This representation can be used for any purpose, such as to display all the information about the object by using Console.WriteLine():

class MyClass
{
    int k;
    double x;

    public MyClass(int k, double x)
    {
        this.k = k;
        this.x = x;
    }

    public override string ToString()
    {
        return k + " " + x;
    }

    static void Main(string[] args)
    {
        MyClass mc = new MyClass(1, 2.5);
        Console.WriteLine(mc);
    }
}    

2.2.2 Abstract Classes

Sometimes classes are defined for representation of abstract concepts, not for instantiation. Such concepts can be represented using abstract classes. C# uses abstract keyword for definition of an abstract class.

abstract class SomeConcept 
{
    . . .
}

Abstract class can contain abstract methods, which are declared without implementation. To declare abstract methods, abstract specifier is used. Such methods have no body, only header followed by semicolon. Abstract method is virtual by default, but you cannot use virtual specifier. Method that override abstract method, must have override modifier.

For example, abstract class called Shape implements fields and methods, which can be used by derived classes. Such elements are, current position (fields), method MoveTo() that moves shape to a new position, etc. Abstract Draw() must be overridden in derived classes in different ways. The Draw() method does not need default implementation:

abstract class Shape 
{
    int x, y;
    . . .
    public void MoveTo(int newX, int newY) 
    {
        . . .
        Draw();
    }
    public abstract void Draw();
}

Specific classes derived from Shape, such as Circle or Rectangle, define their own implementation of Draw() method.

class Circle : Shape 
{
    public override void Draw()
    {
        . . .
    }
}

class Rectangle : Shape 
{
    public override void Draw()
    {
        . . .
    }
}

You can create abstract class without abstract methods. However, if you add at least one abstract method to class definition, this class must be declared with abstract specifier.

Abstract classes can declare abstract properties. Access code is not specified for such properties, but only the need to assign such code in descendants is indicated. For example:

abstract class Shape
{
    public abstract double Area 
    {
        get;
    }
}

...

class Rectangle : Shape 
{
    double width, height;

    public Rectangle(double width, double height)
    {
        this.width = width;
        this.height = height;
    }

    public override double Area   
    {
        get    
        {
            return width * height;
        }  
    }
}

Note: not to be confused with automatic abstract properties those have a similar syntax

2.2.3 Interfaces

C# supports concept of interfaces. Interface can be interpreted as an abstract class, which contains methods and properties. These methods are abstract by default (unless their implementation is given):

interface Int1 {
    void F();
    int G(int x);
    int P { get; set; }
}

In fact, an interface is not a data type and in fact only declares certain behavior that must be provided by the class that implements the interface.

Each C# class can be derived from the only base class, but it can implement several interfaces. Class that implements some interface must implement all methods declared in this interface. Otherwise, this class is abstract and needs appropriate specifier.

Interfaces implemented by some class are listed after base class and are separated by commas. By convention, interface names starts from capitalized I letter to distinguish them from class names.

Methods declared in interface are abstract and public by default. Appropriate methods of a class that implements interface must be defined with public specifier.

interface ISomeFuncs {
    void F();
    int G(int x);
}

class SomeClass : ISomeFuncs {
    public void F()
    {

    }

    public int G(int x) 
    {
        return x;
    }
}

Class can implement several interfaces:

interface IFirst 
{
    void F();
    int G(int x);
}

interface ISecond
{
    void H(int z);
}

class AnotherClass : IFirst, ISecond 
{
    public void F() 
    {
  
    }

    public int G(int x) 
    {
        return x;
    }

    public void H(int z) 
    {
  
    }
}

You can create derived interfaces. Multiple inheritance can be used for interfaces:

interface IFirst 
{
    void F();
    int G(int x);
}

interface ISecond
{
    void H(int z);
}

interface IDerived : IFirst, ISecond 
{

}

Interface can contain declarations of properties. You can also define constants within interfaces.

There is ability of explicit definition of interface methods. This is reasonable if some class implements several interfaces with matching method names and parameter lists.

type_name Interface_name.metod_name() 
{ 
    ...// implementation
}

Such methods do not allow specification of visibility. The invocation of these methods is only allowed for references to interface (not for references to implementing class).

Starting from C# 8 you can define default implementation for interface methods. The goal is to allow adding new methods to previously defined interfaces without modification of classes that implemented previous versions of interfaces. But there are differences in the definition and usage of methods with default implementation. You can define method without any modifiers. You can also define static methods:

public interface IGreetings
{
    void Hello()
    {
        Console.WriteLine("Hello world!");
    }
    static void HelloStatic()
    {
        Console.WriteLine("Hello as well!");
    }
}

The non-static methods can be invoked only through reference to interface (not through reference to implementing class):

class Greetings : IGreetings
{
    public void TestHello()
    {
        IGreetings greetings = this;
        greetings.Hello();
    }
}

class Program
{
    static void Main(string[] args)
    {
        new Greetings().TestHello();
    }
}

Static methods can be invoked through interface name:

class Program
{
    static void Main(string[] args)
    {
        IGreetings.HelloStatic();
    }
}

You can override default implementations in derived interfaces. Such methods can be also redeclared as abstract ones:

public interface IMyGreetings : IGreetings
{
    void IGreetings.Hello()
    {
        Console.WriteLine("Hello to me!");
    }
}

public interface IAbstractGreetings : IGreetings
{
    abstract void IGreetings.Hello();
}

Now this method must be realized in classes that implement IAbstractGreetings interface.

When designing type hierarchies, there is a temptation to create large "universal" interfaces that describe almost all the functionality of a complex system. When designing such interfaces, one should think about the difficulties associated with their implementation. In addition, how it provokes the creation of unwanted mutual dependencies and connections. One of the principles of SOLID - the Interface Segregation Principle (ISP) determines the feasibility of creating a large number of specialized interfaces instead of one universal one, which increases the flexibility of software components, removes unnecessary dependencies and simplifies implementation.

2.2.4 Use of Standard Interfaces

There are a large number of standard interfaces. Some of them are integrated into the C# syntax. For example, an interface IDisposable declares a method Dispose() whose purpose is to perform some final operation, such as closing files, freeing other resources, etc. In order for this method to be guaranteed to be called, special using construct can be applied:

using (X x = new X()) 
{
    // work with x object
}
// call of Dispose()

As mentioned earlier, arrays of numeric items are sorted in ascending order. The CompareTo() method of IComparable interface can be defined in classes and structures (that implement IComparable interface) for setting default sort order. This method should return a negative value (e.g. -1) if this object less than argument, 0 if objects are equal, and a positive value otherwise. The elements of arrays (lists) should be objects that implement IComparable interface. You can choose to create a class that implements IComparable interface. For example, an array of rectangles can be sorted by area:

class Rectangle : IComparable<Rectangle>
{
    double width, height;

    public Rectangle(double width, double height)
    {
        this.width = width;
        this.height = height;
    }

    public double Area()
    {
        return width * height;
    }

    public int CompareTo(Rectangle rect)
    {
        return Area().CompareTo(rect.Area());
    }

    public override String ToString()
    {
        return "[" + width + ", " + height + ", area = " + Area() + "]";
    }
  
}

class Program
{
    static void Main(string[] args)
    {
        Rectangle[] a = {new Rectangle(2, 7), new Rectangle(5, 3), new Rectangle(3, 4)};
        Array.Sort(a);
        foreach (Rectangle rect in a)
            Console.WriteLine(rect);
    }
}

If you attempt to sort items that do not implement the IComparable interface, you obtain InvalidOperationException.

If you do not want (or cannot) determine the CompareTo() function, you can create a separate class that implements the interface IComparer. Reference to this class object are passed as the second parameter in the function Sort() for arrays (or the first option for lists). IComparer interface the method Compare() with two parameters. The function should return a negative number if the first object should be considered less than another, zero if objects are equivalent, and a positive number otherwise.

class Rectangle
{
    double width, height;

    public Rectangle(double width, double height)
    {
        this.width = width;
        this.height = height;
    }

    public double Area()
    {
        return width * height;
    }

    public override String ToString()
    {
        return "[" + width + ", " + height + ", area = " + Area() + "]";
    } 
}

class CompareByArea : IComparer<Rectangle>
{
    public int Compare(Rectangle r1, Rectangle r2)
    {
        return r1.Area().CompareTo(r2.Area());
    }
}

class Program
{
    static void Main(string[] args)
    {
        Rectangle[] a = { new Rectangle(2, 7), new Rectangle(5, 3), new Rectangle(3, 4) };
        Array.Sort(a, new CompareByArea());
        foreach (Rectangle rect in a)
        {
            Console.WriteLine(rect);
        }
    }
}

Sorting lists (List type) is done similarly. Lists will be considered in the next laboratory training.

Some other standard interfaces will be discussed below in the context of their application.

2.2.5 Inheritance and Polymorphism when Working with Records, Structures and Tuples

Records support inheritance. For example:

public record PopulatedRegion
{
    public string Name { get; init; } = "";
    public double Area { get; init; }
    public int Population { get; init; }
}

public record Country : PopulatedRegion
{
    public string Capital { get; init; } = "";
}

All structs are explicit descendants of the System.ValueType that is descendant of System.Object. However, structures do not support the mechanism of explicit inheritance, although they can implement interfaces. You can override methods of System.Object class, for example, such as ToString(). Overriding is carried out using the override modifier, which is otherwise prohibited for structures.

Explicit use of inheritance and polymorphism for tuples is not supported.

2.3 Pattern Matching

The concept of pattern matching is an advance of the idea of implementing branching algorithms. At the general level, pattern matching involves the execution of program code, depending on the coincidence of the checked value with a particular pattern. Depending on the capabilities of the programming language, it can be

  • constant
  • predicate
  • data type
  • another construction of the programming language

Traditional means of comparing variable values to constants in if and switch statements are the simplest forms of pattern matching. When it comes to pattern matching in C#, it means that starting from C# 7.0, constructs for checking object types have been added, with creating a reference to a variable of the appropriate type. For example, we have a variable:

object obj = "Text";

Somewhere in the program you should check if this variable really refers to the string, and perform certain actions. Prior to the introduction of new constructions related to pattern matching, explicit type conversion had to be performed:

if (obj is string)
{
    string s = (string)obj;
    Console.WriteLine(s.Length);
}

Starting with version C# 7.0, you can use a more compact construct:

if (obj is string s)
{
    Console.WriteLine(s.Length);
}

But the most interesting innovation is the use of type checking in the switch() statement. For example:

switch (obj)
{
    case string s:
        Console.WriteLine(s.Length);
        break;
    case int i:
        Console.WriteLine(i + 1);
        break;
    default:
        Console.WriteLine("Wrong type");
        break;
}

In sample matching expressions, you can use the when construct to define an additional condition, for example:

switch (obj)
{
    case string s when s.Length == 0:
        Console.WriteLine("Empty string");
        break;
    case string s:
        Console.WriteLine(s);
        break;
    default:
        Console.WriteLine("Wrong type");
        break;
}

2.4 Reflection

Reflection is a mechanism that allows the program to monitor and modify its own structure and behavior at runtime. Information about the types that can be obtained through the mechanism of reflection is contained in the metadata of the assembly. The C# language provides classes Assembly, MethodInfo, PropertyInfo, FieldInfo and other types of System.Reflection namespace.

At runtime, type information can be obtained by specifying a string with the full type name (including namespaces and nested namespaces):

int k = 100;
Type type = Type.GetType("System.Int32");
MemberInfo[] members = type.GetMembers();
foreach(MemberInfo member in members)
{
    Console.WriteLine(member);
}

The result of the program will be a relatively large list of fields, methods and properties (including static members) defined in the System.Int32 structure and its basic types.

You can also create a variable to get type information.

int k = 100;
Type type = k.GetType();
Console.WriteLine(type); // System.Int32

You can get separate information about methods, fields, and properties:

FieldInfo[] fields = type.GetFields();
MethodInfo[] methods = type.GetMethods();
PropertyInfo[] properties = type.GetProperties();

You can use reflection to create objects of types whose names are defined by a string. You can call methods, work with fields and properties through the names defined at runtime. For example, you can create an instance of a specific class (MySpace.MyClass) and load its call method (MyFunc):

// Create an instance (object) of the class:
object o = assembly.CreateInstance("MySpace.MyClass");
// Get information about the type:
Type t = o.GetType();
// Get information about the method with the specified name: 
MethodInfo mi = t.GetMethod("MyFunc");
// Call the method with parameter x:
object result = mi.Invoke(o, new object[] { x });;

C# reflection tools allow you, in addition to public data, properties, and methods, to obtain information about private type members. For example, you can get information about all fields of some type:

FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);

Since through the mechanisms of reflection it is possible not only to receive information, but also to change the values of fields, call methods, etc., reflection actually allows you to bypass the limitations of encapsulation.

2.5 Exception Handling

Using exceptions handling mechanism is a very important part of programming on all modern object-oriented languages. Exceptions allow programmer to separate points where runtime errors occur from points of error handling. An exception is an event that occurs during the execution of a program that disrupts the normal flow of instructions.

To generate exception, throw operator is used. After the throw keyword, you should place object of System.Exception class or classes derived from it. These derived classes reflect the specifics of a particular program.

class SpecificException : Exception
{
}

The System.Exception class contains a number of properties that you can use to access information about the exception, including:

  • Message – text description of the error, defined as the constructor parameter when creating the exception object;
  • Source – name of the object or application that throws an exception;
  • StackTrace – sequence of calls that resulted in the error.

In most cases, an exception object is created at the point of throwing exception by using new operator, but sometimes an exception object can be created before. Typical throw statement might look like this:

void F()
{
    . . .
    if (/* error */) 
        throw new SpecificException();
    . . .
}

The function header does not specify types of exceptions thrown by this function. In the following example, the Reciprocal() function throws an exception in the case of division by zero.

class DivisionByZero : Exception 
{
}

class Test 
{
    public double Reciprocal(double x)
    {
        if (x == 0) 
        {
            throw new DivisionByZero();
        }
        return 1 / x;
    }
}

Unlike C++, C# does not allow the creation exceptions of primitive types. Only instances of classes derived from Exception are allowed.

The try block contains exception-prone code:

double x, y;
. . .
try 
{
    y = Reciprocal(x);
}

A try block is followed by a sequence of one or more catch statements, or handlers, each of which handles a different type of exception. The catch block without brackets handles all other exceptions:

catch (DivisionByZero d)
{
    // handling exception
}
catch (SpecificException)
{
    // handling exception
}
catch
{
    // handling exception
}

As shown in the example, you can omit object identifier in the header of catch block, if only type is important.

Exceptions form an object hierarchy, so a particular exception might match more than one catch block. What you have to do here is put catch blocks for the more specific exceptions before those for the more general exceptions.

In some cases, exceptions handler cannot ultimately handle the exception and should submit it to outer handler. This can be done by using the throw statement:

catch (SomeEx ex) 
{
    // local exception handling
    throw (ex);  // rethrowing 
}

You can use throw without expression, if exception object was not specified:

catch (Exception) 
{
    // local exception handling
    throw;
}
      

After the last catch block you can place finally block. This code is always executed regardless of whether an exception occurred or not.

try 
{
    OpenFile();
    // other activities
}
catch (FileError f)
{
    // handling exception
}
catch (Exception ex)
{
    // handling exception
}
finally {
    CloseFile();
}

.NET defined standard exceptions. These classes are also descendants of Exception. One of the most frequently occurring standard exception is System.NullReferenceException, which is thrown when you try to access class members by reference, which equal to null. The System.IndexOutOfRangeException is thrown when you try wok with improper array index.

There are also inner .NET exceptions that signal a serious problem during the execution of the program and may arise in any part of your program. These are ExecutionEngineException (internal CLR error), StackOverflowException, OutOfMemoryException, etc. Typically, these exceptions are not caught.

Starting from version 6 of the C# language, you can add the so-called exception filters to catch statements. Filter expressions after when keyword determine when a given catch clause should be applied. If this expression is true, the catch clause executes normally. Otherwise, the catch clause is skipped. For example:

static void SomeFunc(int k)
{
    if (k == 1)
    {
        throw new Exception("First case");
    }
    if (k == 2)
    {
        throw new Exception("Second case");
    }
    throw new Exception("Other case");
}

static void Main(string[] args)
{
    int n = int.Parse(Console.ReadLine());
    try
    {
        SomeFunc(n);
    }
    catch (Exception ex) when (ex.Message.Contains("First")) // Exception filter
    {
        Console.WriteLine("Our case!");
    }
    catch
    {
        Console.WriteLine("Something else");
    }
}
      

If this expression is put into catch block's code, it means that an exception is processed and rethrown. Usage of exception filters assumes that expression is not processed at all. Exception filters can be also used for debugging.

2.6 Initial Information about Working with Files

2.6.1 Working with Text Files

As almost all universal programming languages, C# provides means of working with files and other streams. These facilities are described in the System.IO namespace. The classes in this namespace offer a number of methods for creation streams, reading, writing, etc. Streams for working with text are called character streams. Base classes of all character streams are TextReader and TextWriter. Derived classes StreamWriter and StreamReader, and their derivatives, provide work with text files.

The following program performs reading lines from a text file and writes them into another text file.

using System;
using System.IO;

namespace LabThird
{
    class Program
    {
        static void Main(string[] args)
        {
            using (StreamReader reader = new StreamReader("From.txt", Encoding.Default))
            {
                using (StreamWriter writer = new StreamWriter("To.txt"))
                {
                    string s;
                    while ((s = reader.ReadLine()) != null)
                    {
                        writer.WriteLine(s);
                    }
                }
            }
        }
    }
}

Creation of stream objects within using block causes automatic call of Dispose() methods, which in turn invoke Close() functions. The From.txt file must be placed into bin\Debug or bin\Release folder of project before execution (depending on solution configuration).

The ReadToEnd() method allows you to read the entire file to the end and puts entire contents into a single string. This function allows reduction of the previous program:

using System;
using System.IO;

namespace LabThird
{
    class Program
    {
        static void Main(string[] args)
        {
            using (StreamReader reader = new StreamReader("From.txt", Encoding.Default))
            {
                using (StreamWriter writer = new StreamWriter("To.txt"))
                {
                    string s = reader.ReadToEnd();
                    writer.Write(s);
                }
            }
        }
    }
}    

Classes BinaryReader and BinaryWriter allow you to work with binary streams. There are also so-called memory streams, StringReader and StringWriter, which allow you to use strings as input and output streams.

2.6.2 Working with Binary Files

.NET tools include classes to conveniently work with binaries. The BinaryWriter class provides functions for writing to a data file various built-in value types, as well as byte arrays and character arrays. The BinaryReader class provides methods for reading data of all these types from a binary file. In order to create the corresponding streams, we first create file streams (objects of FileStream class). If fileName is a string containing a file name, streams are created as follows:

FileStream outputStream = new(fileName, FileMode.Create);
FileStream inputStream = new(fileName, FileMode.Open);

Further, these streams are used to create objects of types BinaryWriter and BinaryReader classes:

BinaryWriter writer = new BinaryWriter(outputStream);
BinaryReader reader = new BinaryReader(inputStream);

There are a number of Write() methods of the BinaryWriter class designed to record data of various types. The object of BinaryReader class can read this data using the methods ReadInt16(), ReadInt32(), ReadInt64(), ReadDouble(), ReadDecimal() etc. The best way of writing strings is to convert the string into an array of characters. Before writing this array to the file, it is advisable to write its length. The storing of the string called s will look like this:

writer.Write(s.Length);
writer.Write(s.ToCharArray());

When reading from a file, you should first read the length and then the characters from the array that was saved:

int len = reader.ReadInt32();
s = new String(reader.ReadChars(len));

Custom types can be stored, for example, as strings. If you need to store the date and time (an object of the DateTime class), the best way is to get the representation in the form of a long integer using ToBinary() method. Then the date and time can be reproduced in the corresponding object by reading the number of long type from the file long and using the static DateTime.FromBinary() method.

In the following example, data of various types contained in an object of type Employee is recorded:

namespace BinaryFilesDemo
{
    public class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; } = "";
        public string Surname { get; set; } = "";
        public DateTime DateOfBirth { get; set; }
        public decimal Salary { get; set; }

        public override string ToString()
        {
            string s = "Id:\t\t" + Id
                + "\nName:\t\t" + Name
                + "\nSurname:\t" + Surname
                + "\nDate of Birth\t" + DateOfBirth.ToShortDateString()
                + "\nSalary\t\t" + Salary;
            return s;
        }

        public void WriteToFile(string fileName)
        {
            using (FileStream fs = new(fileName, FileMode.Create))
            {
                using (BinaryWriter writer = new(fs))
                {
                    writer.Write(Id);
                    writer.Write(Name.Length);
                    writer.Write(Name.ToCharArray());
                    writer.Write(Surname.Length);
                    writer.Write(Surname.ToCharArray());
                    writer.Write(DateOfBirth.ToBinary());
                    writer.Write(Salary);
                }
            }
        }

        public void ReadFromFile(string fileName)
        {
            using (FileStream fs = new(fileName, FileMode.Open))
            {
                using (BinaryReader reader = new(fs))
                {
                    Id = reader.ReadInt32();
                    int len = reader.ReadInt32();
                    Name = new String(reader.ReadChars(len));
                    len = reader.ReadInt32();
                    Surname = new String(reader.ReadChars(len));
                    DateOfBirth = DateTime.FromBinary(reader.ReadInt64());
                    Salary = reader.ReadDecimal();
                }
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Employee employee = new()
            {
                Id = 1,
                Name = "John",
                Surname = "Smith",
                DateOfBirth = DateTime.Parse("1989/12/31"),
                Salary = 1000
            };
            Console.WriteLine(employee);
            employee.WriteToFile("employee.bin");
            employee = new();
            Console.WriteLine(employee);
            employee.ReadFromFile("employee.bin");
            Console.WriteLine(employee);
        }
    }
}

The program first creates an object of type Employee. Then we display the data of the created object and write it to a binary file. Next, we create a new empty object of Employee type and read data from the binary file. The object with the read data is displayed on the screen.

It should be remembered that working with binary files does not involve viewing and editing files outside a special program that was created for reading and writing data.

3 Sample Programs

3.1 Hierarchy of Real World Objects

Our goal is to create the following class hierarchy:

  • Region
  • Populated Region
  • Country
  • City
  • Island

Several classes of this hierarchy can be used as base classes for other classes (for instance, "Uninhabited island", "National park", "Borough", etc.). Each class should provide its original constructor for fields' initialization. Array of references to base object can be filled with references to objects of different derived types.

To obtain string representation of some object, you should override ToString() method. The class hierarchy can be as follows:

namespace HierarchyTest
{
    // Class hierarchy
    class Region {
        public string Name { get; set; }
        public double Area { get; set; }

        public Region(string name, double area) 
        {
            Name = name;
            Area = area;
        }

        public override string ToString() 
        {
            return Name + ".\tArea " + Area + " sq.km.";
        }
  
    }

    class PopulatedRegion : Region {
        public int Population { get; set; }

        public PopulatedRegion(string name, double area, int population) 
              : base(name, area)
        {
            Population = population;
        }

        public int Density()
        {
            return (int) (Population / Area);
        }

        public override string ToString()
        {
            return base.ToString() + 
                   "   \tPopulation " + Population + " inhab.\tDensity" +
                   Density() + " inhab. per sq.km.\n";
        }

    }

    class Country : PopulatedRegion 
    {
        public string Capital { get; set; }

        public Country(string name, double area, int population, string capital) :
              base(name, area, population) 
        {
            Capital = capital;
        }

        public override string ToString()
        {
            return "Country  " + base.ToString() + ".\tCapital " + Capital + "\n";
        }

    }

    class City : PopulatedRegion 
    {
        public int Boroughs { get; set; } // Count of boroughs
        public City(string name, double area, int population, int boroughs) :
              base(name, area, population) 
        {
            Boroughs = boroughs;
        }

        public override string ToString()
        {
            return "City " + base.ToString() + " " + Boroughs + " boroughs\n";
        }
  
    }

    class Island : PopulatedRegion 
    {
        public string Sea { get; set; }

        public Island(string name, double area, int population, string sea) :
            base(name, area, population) 
        {
            Sea = sea;
        }

        public override string ToString()
        {
            return "Island " + base.ToString() + "Sea:     " + Sea + "\n";
        }  
    }

    class Program
    {
        static void Main(string[] args)
        {
            Region[] a = { new City("Kiev", 839, 2679000, 10),
                           new Country("Ukraine", 603700, 46294000, "Kiev"),
                           new City("Kharkov", 310, 1461000, 9),
                           new Island("Zmiyiny", 0.2, 30, "Black Sea") };
            foreach (Region region in a)
            {
                System.Console.WriteLine(region);
            }
        }
    }
}

3.2 Finding a Minimum using Dichotomy Method

Suppose we want to create a universal class for finding the minimum of any function f(x) using dichotomy method. Algorithm for finding a minimum at a certain interval [a, b] with precision h is as follows:

  • determined mean of the interval (x)
  • calculated values of f(x - h) and f(x + h)
  • if f(x - h) > f(x + h), the beginning of the interval moved to x, otherwise transferred the end of the interval moved to x
  • if the length of the new interval is less than h, the process terminates, otherwise all repeats for the new interval.

Note that this algorithm only works for the case when we get an interval with exactly one minimum.

We can offer two approaches: with abstract classes and with interfaces.

The First Version

We can create an abstract class (AbstractMinimum) containing an abstract F() method and the function of the minimum (Find()). In a derived class, this function F() should be overridden.

using System;

namespace LabSecond
{
    public abstract class AbstractMinimum {
        abstract public double F(double x);

        public double Solve(double a, double b, double h)
        {
            double x = (a + b) / 2;
            while (Math.Abs(b - a) > h) {
                if (F(x - h) > F(x + h))
                {
                    a = x;
                }
                else
                {
                    b = x;
                }
                x = (a + b) / 2;
            }
            return x;
        }
    }

    class SpecificMinimum : AbstractMinimum 
    {
         public override double F(double x)
        {
            return x * x - x;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            SpecificMinimum sm = new SpecificMinimum();
            Console.WriteLine(sm.Solve(0, 3, 0.000001));
        }
    }
}

The Second Version

Now we define interface for the representation of some function. The Finder class implements a static method for finding minimum. Another class that implements our interface contains a specific implementation of F().

using System;

namespace LabSecond
{
    public interface IFunction
    {
        double F(double x);
    }

    public class Solver
    {
        public static double Solve(double a, double b, double h, IFunction func)
        {
            double x = (a + b) / 2;
            while (Math.Abs(b - a) > h)
            {
                if (func.F(x - h) > func.F(x + h))
                {
                    a = x;
                }
                else
                {
                    b = x;
                }
                x = (a + b) / 2;
            }
            return x;
        }
    }

    class MyFunc : IFunction {
        public double F(double x) {
            return x * x - x;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(Solver.Solve(0, 3, 0.000001, new MyFunc()));
        }
    }
}

3.3 The Hierarchy of Publications

Suppose we want to expand previously created a program that describes the bookshelf, adding a hierarchy of publications that can be located on the shelf. For example, separate types of publications are books and magazines. Instead of an overloaded string casting operation, we'll implement an override of the ToString() method.

In addition, methods for adding and removing publications and authors from arrays should be provided. We can also implement the functions of saving data in a text file and loading data from a text file.

We add classes Publication, Magazine, FileData and FileUtils to the file FileUtils . In addition, we'll make the necessary changes to the classes that were created earlier. We will get the following file code of Books.cs file:

using System.Text;
/// <summary>
/// A namespace that contains the classes to represent the bookshelf
/// </summary>
namespace Bookshelf
{
    /// <summary>
    /// Represents the author of the book on the bookshelf
    /// </summary>
    public class Author
    {
        public string Name { get; set; } = "";
        public string Surname { get; set; } = "";

        public Author() { }
        public Author(string name, string surname)
        {
            Name = name;
            Surname = surname;
        }

        /// <summary>
        /// Provides a string representation of author data
        /// </summary>
        /// <returns>a string representing the author of the book</returns>
        public override string ToString()
        {
            return StringRepresentations.ToString(this);
        }

    }

    /// <summary>
    /// Represents a separate publication (book, magazine, etc.)
    /// </summary>
    public abstract class Publication
    {
        public string Title { get; set; } = "";
        public int Year { get; set; }

        /// <summary>
        /// Converts publication data into a line of a text file
        /// </summary>
        /// <returns>a string ready to be written to a text file</returns>
        abstract public string ToFileData();

        /// <summary>
        /// Creates an object whose data is read from a line of a text file
        /// </summary>
        /// <param name="data">string with data about publication read from text file</param>
        /// <returns>the object whose data is read from the string</returns>
        abstract public Publication FromFileData(string data);
    }
    
    /// <summary>
    /// Represents a book on a bookshelf
    /// </summary>
    public class Book : Publication
    {
        public Author[] Authors { get; set; } = { };
        
        public Book() { }
        public Book(string title, int year)
        {
            Title = title;
            Year = year;
        }
        
        /// <summary>
        /// Provides a string representation of the book data
        /// </summary>
        /// <returns>a string representing data about the book</returns>
        public override string ToString()
        {
            return StringRepresentations.ToString(this);
        }

        /// <summary>
        /// Converts book data into a line of a text file
        /// </summary>
        /// <returns>a string ready to be written to a text file</returns>
        public override string ToFileData()
        {
            return FileData.ToFileData(this);
        }

        /// <summary>
        /// Creates an object whose data is read from a line of a text file
        /// </summary>
        /// <param name="data">string with data about publication read from text file</param>
        /// <returns>the object whose data is read from the string</returns>
        public override Publication FromFileData(string data)
        {
            return FileData.BookFromFileData(data);
        }
        
        /// <summary>
        /// Creates and adds an author to the authors array
        /// </summary>
        /// <param name="name">author's name</param>
        /// <param name="surname">author's surname</param>
        public void AddAuthor(string name, string surname)
        {
            Authors = Authors.Append(new Author(name, surname)).ToArray();
        }

        /// <summary>
        /// Removes data about the author
        /// </summary>
        /// <param name="author">author whose data should be found and deleted</param>
        public void RemoveAuthor(string name, string surname)
        {
            Authors = Authors.Where(author => author.Name != name ||
                author.Surname != surname).ToArray();
        }
    }

    /// <summary>
    /// Represents a magazine on the shelf
    /// </summary>
    public class Magazine : Publication
    {
        public int Volume { get; set; }
        public int Number { get; set; }

        /// <summary>
        /// Provides a string representation of a magazine
        /// </summary>
        /// <returns>a string representing a magazine</returns>
        public override string ToString()
        {
            return StringRepresentations.ToString(this);
        }

        /// <summary>
        /// Converts a magazine data into a line of a text file
        /// </summary>
        /// <returns>a string ready to be written to a text file</returns>
        public override string ToFileData()
        {
            return FileData.ToFileData(this);
        }

        /// <summary>
        /// Creates an object whose data is read from a line of a text file
        /// </summary>
        /// <param name="data">publication data string read from text file</param>
        /// <returns>the object whose data is read from the string</returns>
        public override Publication FromFileData(string data)
        {
            return FileData.MagazineFromFileData(data);
        }

     }

    /// <summary>
    /// Bookshelf
    /// </summary>
    public class Bookshelf
    {
        /// <summary>
        /// Array of references to publications
        /// </summary>
        public Publication[] Publications { get; set; } = { };

        /// <summary>
        /// An indexer that allows getting a publication by index
        /// </summary>
        /// <param name="index">publication index</param>
        /// <returns>publication with corresponding index</returns>
        public Publication this[int index]
        {
            get => Publications[index];
            set => Publications[index] = value;
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="publications">open array of publications</param>
        public Bookshelf(params Publication[] publications)
        {
            Publications = publications;
        }

        /// <summary>
        /// Adds a new publication to the bookshelf
        /// </summary>
        /// <param name="publication">the publication to add to the bookshelf</param>
        public void AddPublication(Publication publication)
        {
            Publications = Publications.Append(publication).ToArray();
        }

        /// <summary>
        /// Deletes the publication with the specified title
        /// </summary>
        /// <param name="title">the title of the publication to find and delete</param>
        public void Remove(string title)
        {
            Publications = Publications.Where(publication => publication.Title != title).ToArray();
        }

        /// <summary>
        /// Provides a string representation of the bookshelf data
        /// </summary>
        /// <returns>a string representing the bookshelf data</returns>
        public override string ToString()
        {   
            return StringRepresentations.ToString(this);
        }
    }

    /// <summary>
    /// A static class that provides a string representation
    /// of various application objects
    /// </summary>
    public static class StringRepresentations
    {
        /// <summary>
        /// Provides a string representation of author data
        /// </summary>
        /// <param name="author">author of the book</param>
        /// <returns>a string representing the author of the book</returns>
        public static string ToString(Author author)
        {
            return author.Name + " " + author.Surname;
        }

        /// <summary>
        /// Provides a string representation of the book data
        /// </summary>
        /// <param name="book">book</param>
        /// <returns>a string representing data about the book</returns>
        public static string ToString(Book book)
        {
            if (book == null)
            {
                return "";
            }
            string result = string.Format("Book. Title: \"{0}\". Year of publication: {1}", 
                                          book.Title, book.Year);
            result += "   Authors:\n";
            for (int i = 0; i < book.Authors.Length; i++)
            {
                result += string.Format("      {0}", book.Authors[i]);
                result += (i < book.Authors.Length - 1 ? "," : "") + "\n";
            }
            return result;
        }

        /// <summary>
        /// Provides a string representation of a magazine
        /// </summary>
        /// <param name="magazine">magazine</param>
        /// <returns>a string representing a magazine</returns>
        public static string ToString(Magazine magazine)
        {
            if (magazine == null)
            {
                return "";
            }
            return string.Format("Magazine. Title: \"{0}\". Year of publication: {1}. Volume: {2}. Number: {3}",
                                  magazine.Title, magazine.Year, magazine.Volume, magazine.Number);
        }

        /// <summary>
        /// Provides a string representation of the bookshelf data
        /// </summary>
        /// <param name="bookshelf">bookshelf</param>
        /// <returns>a string representing the bookshelf data</returns>
        public static string ToString(Bookshelf bookshelf)
        {
            StringBuilder result = new ("");
            foreach (Publication publication in bookshelf.Publications)
            {
                result.Append(publication + "\n");
            }
            return result.ToString();
        }
    }

    /// <summary>
    /// Provides methods for finding and sorting publications on a shelf
    /// </summary>
    public static class BookHandle
    {
        /// <summary>
        /// Searches for a specified sequence of characters in publication titles
        /// </summary>
        /// <param name="bookshelf">bookshelf</param>
        /// <param name="characters">character sequence to find</param>
        /// <returns>an array of publications whose titles contain the specified sequence</returns>
        public static Publication[] ContainsCharacters(Bookshelf bookshelf, string characters)
        {
            return Array.FindAll(
                bookshelf.Publications, publication => publication.Title.Contains(characters));
        }

        /// <summary>
        /// Sorts publications alphabetically by name ignoring case
        /// </summary>
        /// <param name="bookshelf">bookshelf</param>
        public static void SortByTitles(Bookshelf bookshelf)
        {
            Array.Sort(bookshelf.Publications,
                       (b1, b2) => string.Compare(b1.Title.ToUpper(), b2.Title.ToUpper()));
        }
    }

    /// <summary>
    /// Provides methods for converting object data to lines of a text file and vice versa
    /// </summary>
    public static class FileData
    {
        /// <summary>
        /// Converts book data into a line of a text file
        /// </summary>
        /// <param name="book">the book whose data is converted to string</param>
        /// <returns>a string ready to be written to a text file</returns>
        public static string ToFileData(Book book)
        {
            string s = string.Format("{0}\t{1}\t{2}", book.GetType().ToString(), book.Title, book.Year);
            StringBuilder sb = new(s);
            foreach (Author author in book.Authors)
            {
                sb.Append("\t" + author);
            }
            return sb.ToString();
        }

        /// <summary>
        /// Converts a magazine data into a line of a text file
        /// </summary>
        /// <param name="magazine">the magazine whose data is converted to a string</param>
        /// <returns>a string ready to be written to a text file</returns>
        public static string ToFileData(Magazine magazine)
        {
            return string.Format("{0}\t{1}\t{2}\t{3}\t{4}", magazine.GetType().ToString(), magazine.Title,
                magazine.Year, magazine.Volume, magazine.Number);
        }

        /// <summary>
        /// Creates an object whose data is read from a line of a text file
        /// </summary>
        /// <param name="data">string with book data read from text file</param>
        /// 
        /// <returns>the object whose data is read from the string</returns>
        public static Book BookFromFileData(string data)
        {
            string[] parts = data.Split('\t');
            Book book = new(title: parts[1], year: int.Parse(parts[2]));
            foreach (string author in parts[3..^0])
            {
                string[] authorData = author.Split(' ');
                book.AddAuthor(name: authorData[0], surname: authorData[1]);
            }
            return book;
        }

        /// <summary>
        /// Creates an object whose data is read from a line of a text file
        /// </summary>
        /// <param name="data">string with magazine data read from text file</param>
        /// 
        /// <returns>the object whose data is read from the string</returns>
        public static Magazine MagazineFromFileData(string data)
        {
            string[] parts = data.Split('\t');
            Magazine magazine = new()
            {
                Title = parts[1],
                Year = int.Parse(parts[2]),
                Volume = int.Parse(parts[3]),
                Number = int.Parse(parts[4])
            };
            return magazine;
        }
    }

    /// <summary>
    /// Provides methods for reading from file and writing to a file
    /// </summary>
    public static class FileUtils
    {
        /// <summary>
        /// Writes data about the bookshelf to a text file
        /// </summary>
        /// <param name="bookshelf">reference to bookshelf</param>
        /// <param name="fileName">file name</param>
        public static void WriteToFile(Bookshelf bookshelf, string fileName)
        {
            using StreamWriter writer = new(fileName);
            foreach (Publication publication in bookshelf.Publications)
            {
                writer.WriteLine(publication.ToFileData());
            }
        }

        /// <summary>
        /// Reads data about the bookshelf from a text file
        /// </summary>
        /// <param name="fileName">file name</param>
        /// <returns>the bookshelf object</returns>
        public static Bookshelf ReadFromFile(string fileName)
        {
            System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
            Bookshelf bookshelf = new();
            using StreamReader reader = new(fileName);
            string? s;
            while ((s = reader.ReadLine()) != null)
            {
                string typeName = s.Split()[0];
                if (assembly.CreateInstance(typeName) is Publication publication)
                {
                    bookshelf.AddPublication(publication.FromFileData(s));
                }
            }
            return bookshelf;
        }
    }
}

We add a new function called AdditionalProcessing() to the Program.cs file. We will get the following code:

namespace Bookshelf;

/// <summary>
/// A console application to demonstrate working with publications on a bookshelf
/// </summary>
class Program
{
    /// <summary>
    /// Prepares test data to demonstrate working with publications on a bookshelf
    /// </summary>
    /// <returns>Bookshelf with added publications</returns>
    public static Bookshelf CreateBookshelf()
    {
        return new Bookshelf(
            new Book(@"The UML User Guide", 1999)
            {
                Authors = new[] {
                    new Author("Grady", "Booch"),
                    new Author("James", "Rumbaugh"),
                    new Author("Ivar", "Jacobson")
                }
            },
            new Book(@"Pro C# 2010 and the .NET 4 Platform", 2010)
            {
                Authors = new[] { new Author("Andrew", "Troelsen") }
            },
            new Book(@"Thinking in Java", 2005)
            {
                Authors = new[] { new Author("Bruce", "Eckel") }
            },
            new Book(@"Design Patterns: Elements of Reusable Object-Oriented Software", 1994)
            {
                Authors = new[] {
                    new Author("Erich", "Gamma"),
                    new Author("Richard", "Helm"),
                    new Author("Ralph", "Johnson"),
                    new Author("John", "Vlissides")
                }
            },
            new Book(@"C# 9.0 in a Nutshell: The Definitive Reference", 2021)
            {
                Authors = new[] { new Author("Joseph", "Albahari") }
            },
            new Magazine()
            {
                Title = @"The Journal of Object Technology", 
                Year = 2024,
                Volume = 23,
                Number = 3 
            }
        );
    }

    /// <summary>
    /// Demonstrates how to search and sort publications
    /// </summary>
    /// <param name="bookshelf">the bookshelf for which the work is demonstrated</param>
    public static void HandleBookshelf(Bookshelf bookshelf)
    {
        Console.WriteLine("\nInitial state:");
        Console.WriteLine(bookshelf);
        Console.WriteLine("\nTitles that contain \"The\"");
        var result = BookHandle.ContainsCharacters(bookshelf, "The");
        foreach (var publication in result) 
        {
            Console.WriteLine(publication.Title);
        }
        //Console.WriteLine(result.ToArray().Length > 0 ? string.Join("\n", result) : "No");
        Console.WriteLine("\nAlphabetically ignoring case:");
        BookHandle.SortByTitles(bookshelf);
        Console.WriteLine(bookshelf);
    }

    /// <summary>
    /// Outputs the exception information
    /// </summary>
    /// <param name="ex">the exception for which data is output</param>
    internal static void ShowException(Exception ex)
    {
        Console.WriteLine("-----------Exception:-----------");
        Console.WriteLine(ex.GetType());
        Console.WriteLine("------------Content:------------");
        Console.WriteLine(ex.Message);
        Console.WriteLine("----------Stack Trace:----------");
        Console.WriteLine(ex.StackTrace);
    }

    /// <summary>
    /// Demonstrates working with a file, as well as methods 
    /// for adding and removing authors and publications
    /// </summary>
    /// <param name="fileName">fileName</param>
    public static void AdditionalProcessing(string fileName)
    {
        try
        {
            Console.WriteLine("("Reading from the file :" + fileName);
            Bookshelf bookshelf = FileUtils.ReadFromFile(fileName);
            Console.WriteLine(bookshelf);
            Console.WriteLine("Adding and removing the author:");
            if (bookshelf[0] is Book book)
            {
                book.AddAuthor("Elon", "Musk");
                Console.WriteLine(bookshelf[0]);
                book.RemoveAuthor("Elon", "Musk");
                Console.WriteLine(bookshelf[0]);
            }
            Console.WriteLine("Remove Java book	");
            bookshelf.Remove("Thinking in Java");
            Console.WriteLine(bookshelf);
        }
        catch (IOException)
        {
            Console.WriteLine("Error reading from file " + fileName);
            ShowException(ex);
        }
    }

    /// <summary>
    /// The starting point of the console application
    /// </summary>
    static void Main()
    {
        Console.OutputEncoding = System.Text.Encoding.UTF8;
        Bookshelf bookshelf = CreateBookshelf();
        HandleBookshelf(bookshelf);
        FileUtils.WriteToFile(bookshelf, "publications.txt");
        AdditionalProcessing("books.txt"); // No such file
        AdditionalProcessing("publications.txt");
    }
}

4 Exercises

  1. Create a class hierarchy "Book" and the "Handbook". Implement constructors and access methods. Override ToString() method. In the Main() function create an array that contains elements of different types. Display elements on the screen.
  2. Create a class hierarchy "Movie" and "Serial". Implement constructors and access methods. Override ToString() method. In the Main() function create an array that contains elements of different types. Display elements on the screen.
  3. Create a class hierarchy of classes "City" and "Capital". Implement constructors and access methods. Override ToString() method. In the Main() function create an array that contains elements of different types. Display elements on the screen.
  4. Create a hierarchy of classes "Pet" and "Cat". Override ToString() method. In the Main() function create an array that contains items of different types. Display items on the screen.
  5. Create a class hierarchy "Planet" and "Satellite". Override ToString() method. In the Main() function create an array that contains elements of different types. Display elements on the screen.
  6. Read all lines from a text file to a list, and write these lines into another text file in reverse order.
  7. Read all lines of some text file to a list, and write even-numbered lines to another text file.

5 Quiz

  1. What is the purpose of inheritance?
  2. What is the difference between single and multiple inheritance?
  3. What elements of the base class are not inherited?
  4. How do you initialize the base class?
  5. Where and what you can use base keyword?
  6. How to override method with sealed modifier?
  7. Can you implicitly cast base class reference to a derived class reference?
  8. What are the opportunities of polymorphism?
  9. What is the difference between virtual and non-virtual methods?
  10. How to define virtual methods?
  11. What is the compiler reaction on the lack of override or new keywords before the overridden virtual method?
  12. How to describe abstract methods and classes? Can abstract classes contain non-abstract methods?
  13. What are the advantages of interfaces versus abstract classes?
  14. How to define and implement an interface?
  15. What is the purpose of explicit interface implementation?
  16. What are the features of methods that explicitly implement interfaces?
  17. How to define and call an interface method with a default implementation?
  18. What is the idea behind the pattern matching mechanism?
  19. What is the purpose of exceptions mechanism?
  20. How to create an exception object?
  21. How to get the message concerned with some exception in C#?
  22. How to get stack trace in C#?
  23. Is it possible to call a function that throws an exception, without checking this exception? What will be the reaction of the system?
  24. How to work with text files in .NET applications?

 

up