Tech Point Fundamentals

Sunday, July 10, 2022

C# Interview Questions and Answers - Part 08

C# Interview Questions and Answers - Part 08

csharp-interview-questions-and-answers-part1

Are you preparing for the C# Interview? If yes, then you are at the right place. This is the C# Interview Questions and Answers article series. Here we will see the top 150+ C# Interview Questions with Answers. 

Please visit our YouTube Channel for Interviews and other videos by below link:



Please read the complete Design Patterns Interview Questions and Answers series here.


Introduction


This is the 8th part of this C# Interview Questions and Answers article series. Each part contains 10 C# Interview Questions with Answers. Please read all the C# Interview Questions list here.

I will highly recommend you to please read the previous parts over here before continuing the current part:





C# Interview Questions and Answers - Part 08


Q071. What is the difference between Sealed Class and Static Class in C#?

Although both a sealed class and a static class cannot be inherited by any class or struct, both have subtle differences. Please read more about the static class here and sealed class here.

Following are the key difference between them:

  1. A static class cannot be instantiated while a sealed class can be instantiated.
  2. A static class cannot define any indexer while a sealed class can define the indexers.
  3. A static class cannot have any destructor while a sealed class can have a destructor.
  4. A static class cannot have any instance constructor while a sealed class can have both static and instance constructors.
  5. A static class cannot inherit from any class or interface, but a sealed class can inherit from a class or interface.
  6. A static class can only contain static members, but a sealed class can have both static and non-static members.
  7. Members of a static class are accessed by the class name itself while sealed class members can only be accessed by the object of the class.
  8. Only a single global instance is created by the runtime which remains in the memory for the lifetime of the app domain but, it is not true for the sealed class.

Please read about the C# Sealed Class vs Static Class here








Q072. How a sealed class is different from a class having a private constructor in C#?

Normally we use the "sealed" modifier to prevent the inheritance in C#. In VB.Net "NotInheritable" keyword serves the purpose of sealing. In Java "final" keyword serves the same purpose. If the class is sealed there is no trivial way to extend that class including the nested class as well.

On the other hand, if a normal instance class has only a private constructor, it cannot be inherited or instantiated due to its protection level. But it can not prevent extending and instantiation in the nested class. If there is any public constructor along with the default parameterless private constructor, then it can be instantiated, but still cannot be inherited.

The singleton class is also usually marked as sealed, though it contains only a private constructor. Please read the sealed class article here and the singleton class article here for more details. 








Q073. What is a destructor? How does it affect the garbage collection process? When should we define destructor in C#?

A destructor is a method with the same name as the name of the class but starting with the character tilde (~) character. A destructor runs only after a class becomes unreachable.

A destructor is used to write the code that needs to be executed while an instance is destroyed i.e garbage collection process. Destructors are also known as Finalizers. A destructor is a very special member function of a class that is executed whenever an object of its class goes out of scope. 

In C# destructors are methods inside the class which is used to destroy the instances of that class when they are no longer needed. The .NET framework has an inbuilt mechanism called Garbage Collection to de-allocate memory occupied by the unused objects.  The GC internally uses the destruction method to clean up the unused objects. 



Destructor Fundamental Points:

  1. A destructor has the same name as the class name with the "~" sign. The tilde sign distinguished it from the constructor.
  2. A destructor has no return type even void. A destructor cannot have any parameters as well.
  3. A destructor cannot be overloaded or inherited.
  4. Only one destructor is allowed i.e. a class can only have one destructor.
  5. Destructor is not allowed in static classes.
  6. Structures cannot define destructors but constructors are allowed in the structures. Structures are the value type, not a reference type.
  7. By default all the destructors are private, we cannot apply any access modifier explicitly. But Finalize() method of the base Object class is protected.
  8. A destructor cannot be called explicitly, they are invoked automatically after GC.  A C# destructor automatically calls the destructor of its base class.
  9. Unlike constructors, the destructor of the child class is called before the parent class, i.e. the destructors are called in the reverse order of the constructor calls. The destructor method is called recursively for all instances in the inheritance chain, from the most derived to the least derived.
  10. All the explicit destructors override the Finalize method of the base class implicitly. Internally, the destructor is also called the Finalize method of the base Object class. We cannot call the base class Finalize() method explicitly.



When To Use Destructor:

Since C# is a garbage-collected language, meaning that the .Net framework will free the objects that you no longer use, but still there may be some situations where we need to do some manual cleanup. For that destructors are used explicitly. 

One should define a destructor (override Finalize) for a class that uses unmanaged resources, such as file handles or database connections that must be released when the managed object that uses them is discarded during garbage collection. 

All the unmanaged resources require explicit cleanup. The most common type of unmanaged resource is an object that wraps an operating system resource, such as a file handle, window handle, or network connection.

Although the garbage collector is able to track the lifetime of a managed object that encapsulates an unmanaged resource, it doesn't have specific knowledge about how to clean up the resource.

If your class implements a finalizer, you are guaranteeing that it will stay in memory even after the collection that should have killed it. This decreases overall GC efficiency and ensures that your program will dedicate CPU resources to cleaning up your object.

Please read the complete destructor here for more details.



Q074. When the destructor is called? Can you pass parameters to destructors? How many destructors can be defined in a class?

In .NET Framework, finalizers are called when the program exits or only after a class becomes unreachable. But it is also possible to force garbage collection by calling GC.Collect(), but most of the time, this call should be avoided because it may create performance issues. 

A programmer cannot invoke the destructor explicitly because destructors are private by default. Destructors are invoked automatically after GC. So we cannot pass any parameter to the destructor. Hence destructors are always parameterless.

The Garbage Collector of the .NET framework calls this destructor method prior to the garbage collection of the Objects class. When an object is eligible for destruction, the garbage collector runs the Finalize () method of that object.

If your class implements a finalizer, you are guaranteeing that it will stay in memory even after the collection that should have killed it. This decreases overall GC efficiency and ensures that your program will dedicate CPU resources to cleaning up your object.

Only one destructor is allowed in a class. But destructor is not allowed in static classes. Structures also cannot have a destructor. Please read the complete C# Destructor article here



Q075. What is garbage collection in C#? What are the different generations of GC?

The garbage collector manages the allocation and de-allocation of memory for our application. Every time we create a new object, the common language runtime allocates memory for the object from the managed heap. In the CLR, the garbage collector (GC) serves as an automatic memory manager. 

As long as address space is available in the managed heap, the runtime continues to allocate space for new objects. But, memory is not infinite, so the garbage collector must perform a collection in order to free some memory.

GC keeps track of all the objects and ensures that each object gets destroyed once. It ensures that objects, which are being referenced, are not destroyed. GC destroys the objects only when necessary. 

The garbage collector's optimizing engine determines the best time to perform a collection, based on the allocations being made.  Garbage collector checks for objects that are no longer being used by the application, if it found an object eligible for destruction, it calls the destruction and reclaims the memory used to store the object.

Because garbage collection is non-deterministic, you do not know precisely when the garbage collector performs finalization. To release resources immediately, you can also choose to implement the dispose pattern and the IDisposable interface.



Garbage Collector Generations:

After the garbage collector is initialized by the CLR, it allocates a segment of memory to store and manage objects. This memory is called the managed heap, as opposed to a native heap in the operating system. There is a managed heap for each managed process. All threads in the process allocate memory for objects on the same heap.

When a garbage collection is triggered, the garbage collector reclaims the memory that's occupied by dead objects. The reclaiming process compacts live objects so that they are moved together, and the dead space is removed, thereby making the heap smaller. 



Why Different GC Generations Required?

Garbage collection usually occurs with the reclamation of short-lived objects. To optimize the performance of the garbage collector, the managed heap is divided into three generations, 0, 1, and 2, so it can handle long-lived and short-lived objects separately. 

The garbage collector stores new objects in generation 0. Objects created early in the application's lifetime that survive collections are promoted and stored in generations 1 and 2. 

Because it's faster to compact a portion of the managed heap than the entire heap, this scheme allows the garbage collector to release the memory in a specific generation rather than release the memory for the entire managed heap each time it performs a collection.

Garbage collections occur on specific generations as conditions warrant. Collecting a generation means collecting objects in that generation and all its younger generations.
Objects that are not reclaimed in a garbage collection are known as survivors and are promoted to the next generation.



GC Generation 0:

  1. The garbage collector stores new objects in generation 0. This is the first generation and contains short-lived objects like temp variables. Garbage collection occurs most frequently in this generation.
  2. Most objects are reclaimed for garbage collection in generation 0 and don't survive to the next generation.
  3. All the newly allocated short objects form a new generation of objects and fall in the generation 0 collections. However, if they are large objects, they go on the large object heap (LOH), which is sometimes referred to as generation 3.
  4. If an application attempts to create a new object when generation 0 is full, the garbage collector performs a collection in an attempt to free address space for the object. 
  5. The garbage collector starts by examining the objects in generation 0 rather than all objects in the managed heap. A collection of generation 0 alone often reclaims enough memory to enable the application to continue creating new objects.
  6. Objects that survive a generation 0 garbage collection are promoted to generation 1.



Generation 1:

  1. This generation contains short-lived objects and serves as a buffer between short-lived objects and long-lived objects.
  2. After the garbage collector performs a collection of generation 0, it compacts the memory for the reachable objects and promotes them to generation 1.
  3. The garbage collector doesn't have to reexamine the objects in generations 1 and 2 each time it performs a collection of generation 0.
  4. If a collection of generation 0 does not reclaim enough memory for the application to create a new object, the garbage collector can perform a collection of generation 1, then generation 2. Objects in generation 1 that survive collections are promoted to generation 2.
  5. Because objects in generations 0 and 1 are short-lived, these generations are known as the ephemeral generations.



GC Generation 2:

This is the last generation of GC. This generation contains long-lived objects for example static data. 
Objects in generation 2 that survive a collection remain in generation 2 until they are determined to be unreachable in a future collection.
Objects on the large object heap (which is sometimes referred to as generation 3) are also collected in generation 2.
A generation 2 garbage collection is also known as a full garbage collection because it reclaims objects in all generations (that is, all objects in the managed heap).
Objects that survive a generation 2 garbage collection remain in generation 2 itself. They are never promoted further.



GC Generation 3:

  1. This is not any automatic generation. It is a physical generation.
  2. All large objects always go on the large object heap (LOH), which is sometimes referred to as generation 3. 
  3. Generation 3 is a physical generation that's logically collected as part of generation 2.
  4. This generation is not reclaimed by the GC from here directly, it is cleaned in Generation 2 itself.
  5. Ordinarily, the large object heap (LOH) is not compacted, because copying large objects imposes a performance penalty. 

When the garbage collector detects that the survival rate is high in a generation, it increases the threshold of allocations for that generation. 

The CLR continually balances two priorities: not letting an application's working set get too large by delaying garbage collection and not letting the garbage collection run too frequently.

Please read about the Finalizer here and the IDispsable interface here for more detail.



Q076. What is the difference between Destructor and Finalizer in C#?

There is technically no difference between destructor and finalizer. Destructors are also known as Finalizers. 

In C# all the classes are implicitly derived from the super base class Object. This Object class contains a special virtual method, Finalize(), which every class can override.

A destructor is a method with the same name as the name of the class but starting with the character tilde (~) character. A destructor runs only after a class becomes unreachable. 

When we provide an explicit destructor in a class, during the compilation time, the compiler automatically translates the equivalent Finalize() code. That means that a destructor and overridden Finalize() method cannot co-exist in a class. The Finalize method is called when an object that overrides Finalize is destroyed.

The Garbage Collector of the .NET framework calls this destructor method prior to the garbage collection of the Objects class. When an object is eligible for destruction, the garbage collector runs the Finalize () method of that object.



Every explicit destructor overrides the Finalize() method of the base Object class. The Object.Finalize() method does nothing by default, but you should override Finalize only if necessary, and only to release unmanaged resources such as windows, files, network connections,  database connections, etc. 

The Object class provides no implementation for the Finalize method, and the garbage collector does not mark types derived from Object for finalization unless they override the Finalize method. The C# compiler does not allow you to override the Finalize method explicitly. Instead, you provide a finalizer by implementing a destructor for your class.

A destructor can be defined as:

~MyClass()
{
Console.WriteLine("Class custom destructor is called");
}

The call to a destructor is implicitly translated to the following equivalent code by the compiler: 

protected override void Finalize()  
{  
try  
{  
Console.WriteLine("Class custom destructor is called"); 
finally  
{  
base.Finalize();  

Please read the complete finalizer article here for more details.



Q077. What is the difference between IDisposable and Finalizer? or What is the difference between dispose and finalize method in C#?

Finalizers are used to release unmanaged resources in C#. All the finalizers are invoked by the Garbage Collector and Garbage Collection is a non-deterministic process in C#. So to ensure the deterministic release of resources for instances of the class we have to use the Dispose() method or using-dispose pattern.

IDisposable is an interface that contains only a single method i.e. Dispose(), for releasing unmanaged resources. IDisposable is defined in the System namespace. It provides a mechanism for releasing unmanaged resources immediately when they are not required.

Implementing the IDisposable interface only forces the implementation of the Dispose() method for any class. The consumer of an object can call this method explicitly when the object is no longer needed. If the client forgets to call the Dispose() method the finalizer calls it automatically at the end.

When your application or class library encapsulates unmanaged resources such as files, fonts, streams, database connections, etc, they should implement the IDisposable interface or the IAsyncDisposable interface.

Please read about the C# IDisposable Interface article here



Q078. Can a static class contain a destructor? Why?

A static class can not have a destructor. Since a static class can not implement an interface, it cannot implement IDisposable as well. 

However, the static resources are allocated in a high-frequency heap which is beyond the access of Garbage Collector because they are available till the app domain or program exits.

Please read the complete static class article here for more details.








Q079. What is the purpose of using statement or using block in C#? or What is the using dispose pattern?

The using statement defines a scope at the end of which an object will be disposed. It provides a convenient syntax that ensures the correct use of IDisposable objects.

When the lifetime of an IDisposable object is limited to a single method, you should declare and instantiate it in the using statement. The using statement calls the Dispose() method on the object in the correct way and it also causes the object itself to go out of scope as soon as Dispose() is called. 

The using statement ensures that Dispose() method is called on the object created within it. The compiler translates the using statement into a try-finally block internally. In the finally part Dispose() method is called on the object’s instance. This way the compiler ensures that even if there’s any exception thrown in the using block, the object will be disposed. 



Within the using block, the object is read-only and can't be modified or reassigned. The using statement ensures that Dispose (or DisposeAsync) is called even if an exception occurs within the using block. 

Multiple instances of a type can be declared in a single using statement by using a comma separator. But you cannot use implicitly typed variables (var) when you declare multiple variables in a single statement.

using(var client = new HttpClient())
{
 client.BaseAddress = newUri("http://localhost:8081/");
 client.DefaultRequestHeaders.Accept.Clear();           
 client.DefaultRequestHeaders.Accept.Add(newMediaTypeWithQualityHeaderValue("application/json"));
  
HttpResponseMessage response = awaitclient.GetAsync("api/user/1");

if (response.IsSuccessStatusCode)
{
     return  await response.Content.ReadAsAsync<User>();        
 }
else
{
return null;
}
}

Please read about the C# Using Statement Dispose Pattern article here



Q080. What is the use of the Checked and UnChecked keywords in C#?

Both checked and unchecked are blocked code markers. C# statements can execute in either checked or unchecked context. In a checked context, arithmetic overflow raises an exception. In an unchecked context, arithmetic overflow is ignored and the result is truncated by discarding any high-order bits that don't fit in the destination type.

C# provides checked and unchecked keywords to handle integral type exceptions.  Checked and unchecked keywords specify checked context and unchecked context respectively. In a checked context, arithmetic overflow raises an exception whereas, in an unchecked context, arithmetic overflow is ignored and the result is truncated.

The checked keyword is used to explicitly check the overflow and conversion of integral type values at compile time. It is used to throw the Arithmetic OverflowException exception if the max size is exceeded in any mathematical operation. On the other hand, the Unchecked keyword ignores the integral type arithmetic exceptions. It does not check explicitly and produces results that may be truncated or wrong.



The following operations are affected by the overflow checking:

  1. Expressions using the following predefined operators on integral types: ++, --, unary -, +, -, *, /
  2. Explicit numeric conversions between integral types, or from float or double to an integral type.

checked  
{  
int maxValue = int.MaxValue;  
Console.WriteLine(maxValue + 10);  
 
// Error: Unhandled Exception: System.OverflowException: Arithmetic operation resulted in an overflow.

unchecked  
{  
int maxValue = int.MaxValue;  
Console.WriteLine(maxValue + 10);  
}  
// Output: -2147483647

If neither checked nor unchecked is specified, the default context for non-constant expressions is defined by the value of the CheckForOverflowUnderflow compiler option. By default the value of that option is unset and arithmetic operations are executed in an unchecked context.

For constant expressions (expressions that can be fully evaluated at compile time), the default context is always checked. Unless a constant expression is explicitly placed in an unchecked context, overflows that occur during the compile-time evaluation of the expression cause compile-time errors.


To Be Continued Part-09...


Recommended Articles






Thanks for visiting this page. Please follow and join us on LinkedIn, Facebook, Telegram, Quora, YouTube, Twitter, Pinterest, Tumbler, and VK for regular updates.


No comments:

Post a Comment

Please do not enter any HTML. JavaScript or spam link in the comment box.