If you do not understande what is .NET ,what is C#, Compilation of C#, execution of C#, Data types,Operatord, type casting, Strings, Conditional statments, Controll statments, Loop's, Array's and Method's. Then first understand this then come hear, hear is the link of learn this things: Bigginner lavel learn.
OOP (Object Oriented programming):
OOP means object-oriented programming system/structure.
OOP is a paradigm/methodology/way of programming, a way of creating real-world applications, providing security, and building business applications.
C language is a Structured Programming Language, but the concept of Classes was added to C language features and named C with classes. Later, it was renamed C++.
C++ is an object-oriented programming language. Java is also an object-oriented programming language from Sun Microsystems (Oracle).
C# is an object-oriented programming language from Microsoft.
Class and Object is a bulding block of OOP's because without creating class and object we cannot create realword object in programming world.
The variable inside the method called local/auto variable.
Why we need OOP?
Because early Programming Language like C Follow Step by Step Procedure for Programming.
That is Caled Procedural Programming.
It is Good for Simple Application, but hard for Complex application.
Benefit of OOP's
Improved software-developments productivity.
Improve software maintainability.
Faster developments.
Lower cost of development.
Hogher-quality software.
Disadvantages:
Steep learning curve.
Larger program size.
Object-Oriented Programming Features (six pillars of OOP's)
Class & Objects
Methods
Encapsulation
Abstraction
Inheritance
Polymorphism
Class & Object
Every object should have the following characteristics.
State
Identity
Behaviour
Class | Object |
Class is Blueprint/Template | Object is Instance of Class |
Class is Not a Real World Entity | Onject is a Real World Entity |
Class do Not Occupy Memory | Object Occupy Memory |
Class has Attributes & Behavior.
Based on Class Object has Same Attributes and Behavior.
For example: Create a class for Car and Dog to understand.
Syntax of Class:
<access modifier> class <identifier>{}
public class Car { //Body of class }
Example:
public class Car { int num1; int num2; static void Main() { Car c = new Car();//Create Object } }
Syntax of Object/instance: Car c = new Car();
Car | c | \= | new | Car() |
Class name | refrence name (Declear object) | Assignment operator | Allocate memory (Object create) | Constructor name |
Car c
-> Refrence variable.
new Car()
-> Instance.
Example:
using System;
namespace AccessModifiers
{
internal class SimpleObj
{
int num1;
int num2;
int retult;
void Add()
{
retult = num1 + num2;
Console.WriteLine(retult);
}
void Sub()
{
retult = num1 - num2;
Console.WriteLine(retult);
}
static void Main()
{
SimpleObj obj = new SimpleObj();
obj.num1 = 10;
obj.num2 = 30;
obj.Add();
obj.Sub();
}
}
}
Object has the same attributes and behavior as defined in the class, allowing us to easily call all variables and methods. When you create an object, the object contains the same attributes and behavior as the class. A class can have multiple objects.
What is class?
Class is type of an object.
Class is a template to an object.
Class is a blue print to an object.
Class is a collection of similar type of objects.
What is an Object?
Object is an instance of a class.
With one class we can create n no. of objects.
Objects may interact with each other.
Diffrence bitween Object and Instance.
Constructor
OOP (Object Oriented Programming) means all types of code are inside class. A class contains many things like fields, methods, constructors, and more.
Constructor Defination: A Method which is responsible for initializing the variable inside the class and create instance of the class.
Instance mins create object and object name is same name of class.
I told you constructor is method it mins it's take name, and the name shood be exactly same as class name is mandatory another wize you got error.
Constructor does not return any value.
Constructor is invoked implicitly as soon as an object of the class is created.
We can pass parameters to a constructors.
We can also give access modifier (public).
Each & every class requires this constructor if you want to create the instance of the class inside the class then you need constructor. Syntex of creating explicit constructor:-
[<modifiers>] <Name of Class> (<parameter list>){//Code}
.Example:-
using System; namespace Learn { class Program { // Explicit constructor public Program() { Console.WriteLine("Explicit constructor called"); } /* - 'public' is a access modifier. - 'Program' is the name of the class. - We do not pass any parameters here. - Console.WriteLine("Explicit constructor called"); is the code inside the constructor. */ static void Main(string[] args) { // Creating multiple instances of the class Program obj = new Program(); // Create the first instance of the class. Program obj1 = new Program(); // Create the second instance of the class. Program obj2 = new Program(); // Create the third instance of the class. } } }
Here,
Program obj = new Program();
is an instance, meaning I created an object (a real entity) of the class (blueprint). If you create an object, the syntax is<Class name> <Variable name> = new <Constructor name>()
. When you write only<Class name> <Variable name> = new
, it means memory space is created. We will learn about objects and classes later.If you use
static
with a variable or method, it means you can call it without creating an object. If you don't usestatic
, then you need to create an object to call it.If you not create the constructor inside the class then compiler create the implicit constructor inside the class. And all variable you decleared inside the class automatically inslized by implicit constructor.
class MyClass
{
int i; string s; bool b;
public MyClass() //create automatimplicit constructor by compilercannot see this constructobyDefault it's public
{
i = 0;
s = null;
b = false;
}
}
using System;
namespace Learn
{
class Program
{
int i; string s; bool b;
static void Main(string[] args)
{
//implicit constructor:-
Program p = new Learn.Program(); // Calling the implicit constructor. Syntex:-<Class Name> <variable> = new <namespacename>.<constructor>
Console.WriteLine("Value of i: "+ p.i + " Value of s: "+ p.s + " Value of b: "+ p.b); //Automaticaly inslize the value by default implicit constructor, You get the output like that :- Value of i: 0 Value of s: null Value of b: false
}
}
}
Implicit constructor are parameter less construtor this type of constructor also know as default constructor.
The implicit constructor's access level depends on the class's access modifier.
Those constructor whic is define by you which is inside the class are called explicit constructor.
Explicit constructor are parameteriz also.
Difference bitween defining a constructor and calling a constructor:- Difference is two type
Implicit
&Explicit
but calling in only oneExplicit
like that callingProgram p = new Program();
.Syntex of calling constructor :-
<Class Name> <variable> = new <constructor>
If you want to
create only instance
withoutcreating constructor
, you can create it like that but outside of class.
class MyClass
{
int i;
}
MyClass obj = new MyClass(); //Like that you create instance of class without creating any constructor.
Types of Constructor
Default or Parameter less constructor.
Parmetrized Constructor.
Copy Constructor.
Static Constructor.
Private Constructor.
1. Default or Parameter less constructor :-
If a constructor method cannot take any parameters then we call default or parameter less constructor.
If you define the constructor like that public MyClass(); in inside the class, that is also called as a Implicit Constructor or Default Constructor or Parameter less Constructor.
2. Parmetrized Constructor :-
If a constructor method is defined with parameter by programmer are called parametrize constructor. And the constructor is define explicit constructor not implicit constructor.
Example:-
using System;
namespace Learn
{
class Program
{
public Program(int i)
{
Console.WriteLine("parametrize constructor "+ i);
}
static void Main(string[] args)
{
Program pc = new Program(15); //Output is :- parametrize constructor 10
}
}
}
using System;
namespace Learn
{
class Program
{
int x;
// This is a parameterized constructor for the `Program` class.
public Program(int i)
{
Console.WriteLine("parametrize constructor "+ i);
}
// This is a normal method of the `Program` class.
public void Display()
{
Console.WriteLine("Vale of x is: "+ x);
}
static void Main(string[] args)
{
// Creates a instance of the Program class and passing the value `15` to the parameterized constructor.
Program pc = new Program(15); //Output is :- parametrize constructor 15
//Calls the Display method on the `pc object`(which is instance of the `Program class`).
//If you not inslize the value in `x` then compiler get the responsiblity to inslize the valu in `x` by `Implicitly`.
pc.Display(); //Output is :- Vale of x is: 0 // pc is the instance of class and inside the class i am calling Display method.
}
}
}
- If you not inslize the value in
x
then compiler get the responsiblity to inslize the valu inx
byImplicitly
.
using System;
namespace Learn
{
class Program
{
int x;
public Program(int x)
{
this.x = x;// By define constructor it inslize the value.
Console.WriteLine("parametrize constructor "+ i);
}
public void Display()
{
Console.WriteLine("Vale of x is: "+ x);
}
static void Main(string[] args)
{
Program pc = new Program(15); //you send the value in constructor. Output is :- parametrize constructor 15
//pc is is the object which is instance of class.
pc.Display(); //Output is :- Vale of x is: 15
}
}
}
this keyword:
Refers to the current instance of the class. It acts as a reference to the current object, allowing access to the object's members (fields, properties, methods) and constructors. It is used to differentiate between instance variables and parameters or local variables with the same name.
Invoking Other Constructors:
In constructor chaining, the
this
keyword can be used to call another constructor in the same class. This helps in reducing code duplication and organizing initialization logic.
class A { public A() : this("mritunjay"){ } // Calls the two-parameter constructor public A(string nam) { Console.WriteLine(nam); } } class Test { static void Main() { A a = new A(); } }
Passing the Current Object:
this
can be passed as an argument to methods or used to return the current object from a method.
using System; namespace Demo { class A { int x = 10; public A GetP() { return this; } } class Test { static void Main() { A a = new A(); Console.WriteLine(a.GetP());//Out: Demo.A } } }
Key Points:
Scope: The
this
keyword can only be used within non-static methods and constructors, as it refers to an instance of the class. Static methods do not have an instance context and therefore cannot usethis
.Contextual Clarity: Using
this
is not mandatory when referring to instance members unless there is a naming conflict with parameters or local variables. However, some developers prefer usingthis
consistently for clarity.
- Hear allocate two place value(Get two coppy or instace of x in pc and pc1).
using System;
namespace Learn
{
class Program
{
int x;
public Program(int i)
{
x = i;// By your define constructor it inslize the value.
Console.WriteLine("parametrize constructor "+ i);
}
public void Display()
{
Console.WriteLine("Vale of x is: "+ x);
}
static void Main(string[] args)
{
// Hear allocate two place value(Get two coppy or instace of x in pc and pc1). And also create two memory space.
Program pc = new Program(15); //Output is :- parametrize constructor 15
Program pc1 = new Program(19); //Output is :- parametrize constructor 19
pc.Display(); //Output is :- Vale of x is: 15
pc1.Display(); //Output is :- Vale of x is: 19
}
}
}
3. Copy Constructor :-
If we want to create multiple instances with the same value then we use these copy constructors, in a copy constructor the constructor take the same class as a parameter to it.
The copy constructor is used to create a new instance by copying the values from an existing instance.
using System;
namespace Learn
{
class Program
{
int x;
public Program(int i) // Parametrize Constructor
{
x = i;
Console.WriteLine("parametrize constructor "+ i);
}
public Program(Program other) // Copy constructor: Program is class name and other is refrence
{
x = other.x; // Copy the value from another instance
Console.WriteLine("Copy constructor " + other.x);
}
public void Display()
{
Console.WriteLine("Vale of x is: "+ x);
}
static void Main(string[] args)
{
// Calls parameterized constructor
Program pc = new Program(15); //Output is :- parametrize constructor 15
// Calls copy constructor
Program pc1 = new Program(pc); //Output is :- parametrize constructor 15
pc.Display(); //Output is :- Vale of x is: 15
pc1.Display(); //Output is :- Vale of x is: 15
}
}
}
If you are having trouble to understanding this code, let me clear up:-
public Program(Program other)
{
x = other.x; // Copy the value from another instance
Console.WriteLine("Copy constructor " + other.x);
}
The parameter of the copy constructor is of the same type as the class (
Program
in this case).By convention, we name this parameter
other
(or sometimessource
,original
, etc.).other.x
refers to thex
field of the existing instance (the one being copied).Using
other.x
allows you to access the value ofx
from the existing instance and assign it to the new instance.Create an instance like
Program pc1 = new Program(pc);
. This makes a new instance (pc1) by copying the value of x from the existing instance (pc).The line
x = other.x;
ensures thatpc1.x
gets the same value aspc.x
.The output “Copy constructor 15” indicates that the value of
x
from the existing instance (pc
) was successfully copied to the new instance (pc1
).Why
other.x
andProgram other
?
Static Constructor:
Static constructor are responsible in initializing static variable and these constructors are never called explicitly they are implicitly called and more over these constructor are first to execute under any class.
using System; namespace Learn { class Program { static Program() //This is static constructor. { Console.WriteLine("Static constructer executed!"); } static void Main(string[] args) { } } }
Main method runs first, but just before it runs, the static constructor is executed automatically.
Static constructors cannot be parameterized, so overloading static constructors (passing parameters in a static constructor) is not possible.
Q. Why is an explicit constructor required in the program? And what is the need for defining a constructor explicitly again?
Ans: We require an explicit constructor because if we do not define a constructor, the compiler creates an implicit constructor and initializes default values in variables which is declear in class. That's why we need the explicit constructor to initialize our own values or pass parameters to change the values by creating an instance of the class and also make multiple instance of class (reusability approach apply) .
//First.cs
using System;
namespace Learn
{
class First
{
public int x = 100;
}
class Second
{
public int x;
}
class TestClasses
{
static void Main()
{
First f1 = new First();
First f2 = new First();
First f3 = new First();
Console.WriteLine(f1.x +" "+ f2.x + " "+ f3.x);//Out:- 100 100 100
Second s1 = new Second();
Second s2 = new Second();
Second s3 = new Second();
Console.WriteLine(s1.x + " " + s2.x + " " + s3.x);//Out:- 0 0 0
Second ss1 = new Second(10); //Show Error when you pass the argument Second(10)
Second ss2 = new Second(15); //Show Error when you pass the argument Second(10)
Second ss3 = new Second(25); //Show Error when you pass the argument Second(10)
Console.WriteLine(ss1.x + " " + ss2.x + " " + ss3.x);
//Out:- 0 0 0
}
}
}
That's way we need constructor to inslize own value.
using System;
namespace Learn
{
class First
{
public int x = 100;
}
class Second
{
public int x;
public Second(int x) //This is a constructor
{
this.x = x;
}
}
class TestClasses
{
static void Main()
{
First f1 = new First();
First f2 = new First();
First f3 = new First();
Console.WriteLine(f1.x +" "+ f2.x + " "+ f3.x);//Out:- 100 100 100
Second s1 = new Second(); //Show Error when you pass the argument Second(10) because explicit constructor define
Second s2 = new Second(); //Show Error when you pass the argument Second(10) because explicit constructor define
Second s3 = new Second(); //Show Error when you pass the argument Second(10) because explicit constructor define
Console.WriteLine(s1.x + " " + s2.x + " " + s3.x);
Second ss1 = new Second(10);
Second ss2 = new Second(15);
Second ss3 = new Second(25);
Console.WriteLine(ss1.x + " " + ss2.x + " " + ss3.x); //Out:- 10 15 25
}
}
}
'this.x = x' mins this refer to the class variable x
and 'x' refer to the parameter of constructor.
When you define a class, first identify if the class variable is required. If it is, then create an explicit constructor and pass the value through that constructor. This way, every time an instance of the class is created, we get a chance to pass a new value. This is the main advantage of explicit constructor.
Nots : Generaly every class requires some value for execution and the value that are required for a class to execute are always sent to that class by using the constructor only.
Private Constructor :
A private constructor is a special type of constructor that can't be accessed from outside the class it's defined in. This means you can't create instances of the class directly from outside. Also, you can't inherit a class that has a private constructor. Private constructors are usually used to:
Prevent Instantiation: Stop the creation of objects from a class.
class Example { // Private constructor private Example() { } public static void DisplayMessage() { Console.WriteLine("Hello, world!"); } } class Program { static void Main() { // Example e = new Example(); // This will cause a compile-time error Example.DisplayMessage(); // Output: Hello, world! } }
Implement Singleton Pattern: Make sure only one instance of a class is created.
using System; class Singleton { // This field holds the single instance of the Singleton class private static Singleton instance; // Private constructor prevents instantiation from other classes private Singleton() { } // Public property to provide access to the single instance of the class public static Singleton Instance { get { // If no instance exists, create one if (instance == null) { instance = new Singleton(); } return instance; } } // Method to demonstrate functionality public void ShowMessage() { Console.WriteLine("Singleton instance"); } } class Program { static void Main() { Singleton s1 = Singleton.Instance; Singleton s2 = Singleton.Instance; s1.ShowMessage(); // Output: Singleton instance // Verify both instances are the same Console.WriteLine(s1 == s2); // Output: True } }
The
Singleton
class has a private constructor to prevent creating new instances from outside.A static property
Instance
makes sure only one instance ofSingleton
is created.The
ShowMessage
method shows how to use the singleton instance.In the
Main
method, two referencess1
ands2
are obtained from theInstance
property, and both point to the same instance.Provide Static Members: Hold only static members and offer utility or helper methods.
Diffrence bitween Static constructors and Non-static Constructors :
- Static Constructors: If the constructor is declared using the static modifier, we call it a static constructor. All other constructors are non-static.
//code:
using System;
namespace Practical_Exercises
{
class Program
{
static Program()
{
Console.WriteLine("Static constructor is called!");
}
public Program()
{
Console.WriteLine("Non-Static constructor is called!");
}
static void Main()
{
}
}
}
Constructors are responsible for initializing fields/variables of a class, so static fields are initialized by static constructors and non-static fields are initialized by non-static constructors.
class Program { int x;//By default x value is 0, Because initialized by non-static constructor static int y;//By default y value is 0, Because initialized by static constructor static Program() { y = 20; } public Program() { this.x = 10; } }
By default
x
value is0
, Because initialized by Non-Static constructor &x
is a non-static variable.By default
y
value is0
, Because initialized by Static constructor &y
is an static variable.
Static constructors are implicitly called, whereas non-static constructors are called explicitly.
Static constructors execute immediately once the execution of a class starts and, moreover, they are the first block of code to run in a class. Non-static constructors, on the other hand, execute only after creating an instance of the class and every time an instance of the class is created.
class Program { int x; static int y; static Program() { Console.WriteLine("Static constructor is called!"); } public Program() { Console.WriteLine("Non-Static constructor is called!"); } static void Main() { Program program = new Program(); Program program1 = new Program(); } } //Output is:- //Static constructor is called! //Non-Static constructor is called!
In the life cycle (life cycle means from the start of execution to the end of execution.) of a class, the static constructor executes only once, whereas the non-static constructor executes zero times if no instances are created and 'n' times if 'n' instances are created.
Static members can be accessed directly, whereas non-static members cannot be accessed directly. This is because static members are shared among all instances of a class, while non-static members belong to individual instances. When you create multiple instances of a class, each instance has its own copy of the non-static members. However, there is only one copy of the static members, which is shared by all instances. This is why static members can be accessed directly using the class name, but non-static members require an instance of the class to be accessed.
using System; namespace Practical_Exercises { class Program { int x; // Non-static member static int y; // Static member // Static constructor static Program() { Console.WriteLine("Static constructor is called!"); } // Non-static (instance) constructor public Program() { Console.WriteLine("Non-Static constructor is called!"); } static void Main() { // Create an instance of the Program class Program program = new Program(); // Access the static member directly Console.WriteLine(y); //Access the non static member Console.WriteLine(program.x); Console.ReadLine(); } } } //Output:- //Static constructor is called! //Non-Static constructor is called! //0 //0
Non-static constructor can be parametrized but static constructor can't have parametrized. Because of static constrctor is implicitly call & wo will pass the parameter. Remember it's a 1st block of code to run the class.
using System; namespace Practical_Exercises { class Program { int x; // Non-static member static int y; // Static member // Static constructor static Program(int x)//give error: static constructor must be parameterless { Console.WriteLine("Static constructor is called!"); } // Non-static (instance) constructor public Program(int x) { this.x=x; Console.WriteLine("Non-Static constructor is called!"); } static void Main() { // Create an instance of the Program class Program program = new Program(10); Program program1 = new Program(20); Program program2 = new Program(30); // Access the static member directly Console.WriteLine(y); //Access the non static member Console.Write(program.x + " "+ program.x + " " + program.x); Console.ReadLine(); } } } //Output:- //Static constructor is called! //Non-Static constructor is called! //Non-Static constructor is called! //Non-Static constructor is called! //0 //10 20 30
Non static constructor can be overloaded where as static constructor can't be overloaded.
using System; namespace Practical_Exercises { class Program { int x; // Non-static member static int y; // Static member // Static constructor static Program() { Console.WriteLine("Static constructor is called!"); } // Non-static (instance) constructor public Program(int x) { this.x=x; Console.WriteLine("Non-Static constructor is called!"); } // Non-static (instance) constructor overloaded public Program(int x, int z) { Console.WriteLine("Non-Static constructor is called!"); } static void Main() { Program program = new Program(10); Program program1 = new Program(20,10); Console.ReadLine(); } } }
Every class contains an implicit constructor if not defined explicitly, and those implicit constructors are defined based on the following criteria:
Every class except a static class contains an implicit non-static-constructor if not defined with an explicit constructor.
Static Classes: Static classes are special in C#. You can't create an instance of them, and they only have static members. They can't have instance constructors, whether implicit or explicit. However, they can have static constructors to initialize static members if needed.
public static class StaticExample { static StaticExample() {/* Static constructor logic*/} public static void DoSomething() {/* Static method logic*/} }
An instance constructor (also called a constructor) is a special method that runs when you create an object of a class. Its main job is to set up the new object.
Implicit non-static-constructor: If you create a class and don't add any constructor, the C# compiler automatically makes a default non-static constructor. This constructor has no parameters and only allows you to create an instance of the class.Clear the concept of Static and instace.
Static constructors are implicitly defined only if that class contains any static members or else that constructor will be present at all.
Destructor
A destructor in C# is a special method used to clean up an object before the garbage collector reclaims it. It runs automatically when the object is no longer needed. The destructor's name is the same as the class name, but with a tilde (~
) at the beginning.
Key Characteristics of a Destructor:
No Access Modifiers: Destructors do not have access modifiers and are implicitly
private
.No Parameters: Destructors cannot take parameters.
No Overloading: A class can only have one destructor.
Automatic Invocation: Destructors are called automatically by the garbage collector when an object is no longer needed.
Difference Between Constructor and Destructor
Constructor | Destructor |
The constructor's name is the same as the class name. It is called as soon as an object is created. | The destructor's name is the same as the class name, preceded by ~ . It is used to perform cleanup activities. |
It is used to initialize the object. | It is used to clean up resources when an object goes out of scope. |
Constructor overloading is possible. | Destructor overloading is not possible. |
We can pass arguments to constructors. | We cannot pass arguments to destructors. |
Access modifiers can be used with constructors. | Access modifiers cannot be used with destructors. |
Example of Constructor and Destructor in C#
using System;
class SampleClass
{
// Constructor
public SampleClass()
{
Console.WriteLine("Constructor: Initializing object.");
}
// Destructor
~SampleClass()
{
Console.WriteLine("Destructor: Cleaning up object.");
}
}
class Program
{
static void Main()
{
SampleClass obj = new SampleClass(); // Constructor called here
// Forcing garbage collection to see the destructor message
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
In this example:
Constructor: The
SampleClass
constructor is called when an object ofSampleClass
is created, initializing the object.Destructor: The destructor is called automatically by the garbage collector to clean up resources when the object is no longer in use.
Variable, Instance & Refrence of class
To learn about variables, instances, and references, first understand what a class is:
Class: A class is a user-defined type or data type. For example, string
is a data type in C#, but it was created as a class. string
is a predefined class in the .NET library, and we now call it a data type. Every class, whether predefined, user-defined or any type of class, is a data type or type in C#.
How to consume class: Can we do something like int = 100;
? No, because int
is a data type or type, which is just a blueprint for data but doesn't have a memory location. To locate memory, we write int i = 100;
. Similarly, string = "Hello"
is invalid because string
is a class, and a class is a user-defined type or data type. You cannot consume it directly. If you want to use it, write string s = "Hello"
. Then, what is s
? s
is a copy of type string
, and i
is a copy of type int
.
Understande by Example:- Can you build a house without a map? If you have a map, you can build the house. Similarly, int = 100
or string = "Hello"
is like a house map or blueprint. Writing int i = 100
or string s = "Hello"
is like actually building the house or allocating memory. You live in a constructed house, not in a map of the house. Similarly, values stay in memory, not in the blueprint.
So, a data type is always a blueprint, and the copy of the data type is an implementation of that blueprint.
class Program
{
int x = 10;
static void Main()
{
//Give error, because non static member of class cannot be axcess by the static block.
Console.WriteLine(x);
//Then how to axcess the `x` value. By creating instance/copy of class
Program f = new Program();//`f` is the instance/copy of class
Console.WriteLine(f.x);
Console.ReadLine();
}
}
Here, what is
f
?f
is a copy of the class. Similarly, in this examplestring s = "Hello"
,s
is a copy of the type string, and string is a class. And that copy is called a copy of the class.Program f = new Program();
Here,f
is a copy of the classPro gram
.Program
is a class, andf
is the copy of the class. And that copy is called an instance of the class. Memory is allocated for the instance only, and thenew
keyword is used to create the instance of the class.
class Program
{
int x = 10;
static void Main()
{
Program f;//now that time `f` is the copy of class which is not inslize.
Console.WriteLine(f.x);// GIve error:- Use of unassigned local variable 'f'
Console.ReadLine();
}
}
Program f
; now at that time f
is the copy of the class which is not initialized, or you could say it is a variable of the class. Without using new
, it's only a variable.
class Program
{
int x = 10;
static void Main()
{
Program f;//now that time `f` is the copy of class which is not inslize.
f = new Program();//Declear the valu, which is calss.
Console.WriteLine(f.x);// Give error:- Use of unassigned local variable 'f'
Console.ReadLine();
}
}
Both are the same:
Program f = new Program();
andProgram f; f = new Program();
.Here,
Program f = new Program();
'f' is an instance of the class.Program f;
'f' is a variable of the class which has a defaultnull
value.f = new Program();
'f' is an instance of the class, and memory allocation is initialized when you create the instance. The instance is created when you use thenew
keyword, andx
is initialized.
Variable of a class : A copy of the class that is not initialized.
Instance of a class: A coppy of class that is initialized by using the new
keyword which has it's own memory and never shared with another instance.
class Program
{
int x = 10;
static void Main()
{
Program f1 = new Program();//`f` is the instance of class
Program f2 = new Program();//`f` is the instance of class
Console.WriteLine(f1.x + " "+ f2.x);//Output is: 10 10
f1.x = 20;
Console.WriteLine(f1.x + " "+ f2.x);//Output is: 20 10
f2.x = 30;
Console.WriteLine(f1.x + " "+ f2.x);//Output is: 20 30
Console.ReadLine();
}
}
You create n number of instances of a class, and each instance has a separate memory location. Any instance can never reflect another instance, meaning the f1
instance never reflects the f2
instance. If you modify f1.x
, it does not modify the f2.x
value.
class Program
{
int x = 10;
static void Main()
{
Program f1 = new Program();//`f` is the instance of class
Program f2 = f1;//`f2` is the refrence of class or you say f2 is the pointer of `f1`.
Console.WriteLine(f1.x + " "+ f2.x);//Output is: 10 10
f1.x = 20;
Console.WriteLine(f1.x + " "+ f2.x);//Output is: 20 20
f2.x = 30;
Console.WriteLine(f1.x + " "+ f2.x);//Output is: 30 30
Console.ReadLine();
}
}
In that example, f2
is the reference of f1
, or you can say the pointer of f1
. f2
does not have a separate memory. f1
and f2
share the same memory location. A reference is a copy of a class created by an existing instance of the class. A reference does not have its own memory but is used like an instance of the class.
Refrence of class: A coppy of the class that is initialized by using an existing instance and references of the class will not have any memory allocation. They will be sharinge the same memory of the instance that was assigned for initializing the variable.
Reference of a class can be called a pointer to the instance, and every modification we perform on the members using the instance reflects when we access those members through the reference and vice-versa.
Access Specifiers:
It's a special kind of modifers using which we can define the scope of a type and it's members.
In C# 7, there are access specifiers, but mainly only 4:
Private: accessible only to the current class.
Internal: to all the classes in the current assembly only.
Protected: to the current class and to it’s child classes.
Public: accessible to all the classes in all the assemblies.
Protected internal: if protected or internal access.
Private protected: if private or internal access.
File: within the file scope.
A member of a class that is defined with any scope is accessible within that class. If there are any restrictions, they start outside of this class.
The default access modifier inside a namespace is internal (first level).
The default access modifier inside a class is private (second level).
By using inharitance:
Example:-
using System;
namespace Practical_Exercises
{
//Case1: Consumimg members of a class from same class
public class Program //Axcess from anywhere
{
private void Test1()
{
Console.WriteLine("Private Method");
}
internal void Test2()
{
Console.WriteLine("Internal Method");
}
protected void Test3()
{
Console.WriteLine("Protected Method");
}
protected internal void Test4()
{
Console.WriteLine("Protected Internal Method");
}
public void Test5()
{
Console.WriteLine("Public Method");
}
static void Main()
{
Program p = new Program();
//All member can be accessable hear.
p.Test1();p.Test2();p.Test3();p.Test4();p.Test5();
}
}
}
/*
Output:-
Private Method
Internal Method
Protected Method
Protected Internal Method
Public Method
*/
Hear all 5 method are executable, But if you want to access from another class then you getting problom. Like that i am creating another class in my project name is Progrem2
.
new is a keyword that is used to allocate memory for any object, variable, array, etc.
use . (dot) operator in order to call the members of the class.
All the class names in C# should follow Pascal Case. Means every word’s first letter should be in Capital.
using System;
namespace Practical_Exercises
{
//Case2: Consumimg members of a class from child class from same project
//According to Inharit, parents class access the all member of child class except private class like owner.
internal class Program2: Program//Inharit the Program class
{
static void Main()
{
Program2 p = new Program2();
p.Test1();//Only Test1() method give error
p.Test2(); p.Test3(); p.Test4();p.Test5();//Other are accessable
}
}
}
/*
Output:-
Internal Method
Protected Method
Protected Internal Method
Public Method
*/
Remember:
1: All members of a class are private by default.
2: We cannot declare a class as private, protected, or protected internal. If you do not define a class as public, it is internal by default.
3: A member declared as private is only accessible within the class.
If you run the Program2 class, all 4 methods from the Program class will be called.
By using making instance of class:
See make a third class name is Program3
:
using System;
namespace Practical_Exercises
{
//Case3: Consumimg members of a class from non-child class from same project
internal class Program3
{
static void Main()
{
Program p = new Program();
p.Test1();p.Test3();//Only Test1() and Test3 method give error
p.Test2();p.Test4();p.Test5();
}
}
}
/*
Output:-
Internal Method
Protected Internal Method
Public Method
*/
Remember: When we create an instance of another class, we cannot access the protected and private methods. Only a child class can access the protected methods, not other classes.
private: Can be accessed within the class, cannot be accessed outside the class.
protected: It can be accessed within the class or within the child class, and non-child classes cannot access it.
public: Not any restriction, it can be accessible anywhere.
To understand Internal and Protected Internal, create another project namedPractical_Exercises2
withen the same solution:
- Rename the
Program
class toProgram4
inPractical_Exercises2
just for clear understanding.
My intention is to access the Program class members in Program4 class by inheritance. Both the Program and Program4 classes are in different projects. To do that, get the reference of the Practical_Exercises
Assembly to the Practical_Exercises2
project.
Steps: Right-click on Practical_Exercises2
> click on Add
> next click on Reference
> a window named Reference manager
will open > go to Browse
and select the Practical_Exercises
project, go to the bin folder, and then select the .exe file
, which is the reference of the assembly, and click on the ok
button.
You consume the reference of another project (Practical_Exercises).
Hear example:-
If you not able to see the example click hear : https://cdn.hashnode.com/res/hashnode/image/upload/v1719894605538/4d0c41f3-dd96-4b03-8dda-07578f0dfbae.gif
Now, there are two ways to consume the method: the first is inheritance, and the second is object creation. But here I use inheritance:
Here we can consume only three methods: protected, protected internal, and public. We cannot consume internal outside the project. The default scope of a class is internal, which is why every class is accessible inside the project only unless it's public. see hear:
We can access only one class name is Program
, but in the Practical_Exercises
project, there are three class Program, Program2, and Program3, because Program is public.
internal: We cannot consume internal outside the project only we can conume in the same project.
protected internal: If either protected or internal is accessible, then protected internal is also accessible. If neither of these is accessible, protected internal cannot be accessible.
Summary:
Cases | Private | Internal | Protected | Protected Internal | Public |
Case 1 (Same class) | True | True | True | True | True |
Case 2 (Child class in same project) | False | True | True | True | True |
Case 3 (None child class in same project) | False | True | False | True | True |
Case 4 (Child class in diffrent project) | False | False | True | True | True |
Case 5 (Consume any where) | False | False | False | False | True |
Difference kinds of Variables:
There are 4 kinds of variable:-
Non-static variable (Instance variable)
Static variable
Constant variable
ReadOnly variable
Non-static variable (Instance variable) and Static variable:
Also known as non-static variables.
Each instance (object) of a class has its own copy of instance variables.
They are declared within a class but outside any method, constructor, or property.
Instance variables are initialized when an object of the class is created and destroyed when the object is destroyed.
class Program { //Non-static variable or instance variable int x; public int y; static void Main() { Console.WriteLine(x);//Error } }
Also known as class variables.
There is only one copy of a static variable that is shared by all instances (objects) of a class.
They are declared using the
static
keyword.Static variables are initialized only once, at the start of the execution, and remain in memory until the program ends
class Program
{
static int x = 200;//Static variable
static void Main()
{
int y; //Static variable
Console.WriteLine(x);//200
}
}
If a variable is explicitly declared using the static modifier or is declared inside a static block, then that variable is static. Another all other variables are non-static.
using System;
class Program
{
int x = 100; // Non-static variable or instance variable
static int y = 200; // Static variable
static void Main()
{
Program obj = new Program(); // Creating an instance of the Program class
Console.WriteLine(x); //Error
Console.WriteLine(obj.x); // Accessing the instance variable through the instance
Console.WriteLine(y); // Accessing the static variable directly
}
}
You can directly access a static variable, but you cannot directly access a non-static variable. To access a non-static variable, you need to create an instance of the class. Static variables are initialized and allocated memory when the class is loaded, while non-static variables are initialized when an instance is created.
Static members of a class do not require an instance for initialization or execution, whereas non-static members do. Static variables are initialized when the class is loaded, while instance variables are initialized each time a new instance is created.
using System;
class Program
{
int x = 100; // Non-static variable or instance variable
static void Main()
{
Program obj = new Program(); //1st instance created & 1st time memory is allocated for x
Program obj1 = new Program(); //2st instance created & 2nd time memory is allocated for x
Console.WriteLine(obj.x);
Console.WriteLine(obj1.x);
}
}
2 times memory is allocated for x because 2 instances are created.
In the life cycle of a class, a static variable is initialized only one time, whereas instance variables are initialized 0 times if no instances are created and n times if n time instances are created.
Initialization of instance variable is associalte with instance creation & constructor calling, so instance variables can be initialize thru the constructor also.
Create two diffrent instance variable:
using System;
class Program
{
int x = 100; // Non-static variable or instance variable
public Program(int x)
{
this.x = x;
}
static void Main()
{
Program obj = new Program(50); //1st instance created & 1st time memory is allocated for x
Program obj1 = new Program(150); //2st instance created & 2nd time memory is allocated for x
Console.WriteLine(obj.x);
Console.WriteLine(obj1.x);
}
}
In that program,
x
has two copies because we create two instances, buty
has only one copy because a static variable is initialized only once in the class's life cycle.A static variable is initialized only once when the class is first loaded into memory. It is not re-initialized for each instance of the class and can be modified after initialization. This means that no matter how many objects of the class are created, the static variable retains its value and can be modified, but it is never reset.
Example:
using System;
class Example
{
// Static variable
public static int staticVar = 10;
// Instance constructor
public Example()
{
// Modify the static variable
staticVar++;
}
static void Main()
{
// Display the initial value of the static variable
Console.WriteLine("Initial value of staticVar: " + Example.staticVar); // Output: 10
// Create the first instance of the Example class
Example obj1 = new Example();
Console.WriteLine("Value of staticVar after creating obj1: " + Example.staticVar); // Output: 11
// Create the second instance of the Example class
Example obj2 = new Example();
Console.WriteLine("Value of staticVar after creating obj2: " + Example.staticVar); // Output: 12
// Create the third instance of the Example class
Example obj3 = new Example();
Console.WriteLine("Value of staticVar after creating obj3: " + Example.staticVar); // Output: 13
}
}
Constant variable:
If a variable is declared using the keyword const
, we call it a constant variable. This constant variable can't be modified after its declaration, so it must be initialized at the time of declaration. Ex: const float pi = 3.14f;
. Decimal values are treated as double; if you want to represent the value as float, add the suffix f
. If you don't add the suffix, a compile-time error will occur. If you use const the it must be inslize variable.
using System;
class Program
{
const int x = 100;
const float pi = 3.14f;
static void Main()
{
Console.WriteLine(pi);
}
}
Constants have only one copy, no matter how many instances of the class are created. Even if we create 10 instances of the class, there will still be only one copy of the constant x
. This saves memory because constant values cannot be changed.
The behavior of a constant is similar to that of a static variable. That is initialized one and only one time in the life cycle of the class and doesn't require an instance of the class.
The difference between static and constant variables is that a static variable can be modified, but a constant variable cannot be modified.
ReadOnly variable:
If a variable is declared using the readonly keyword, we call that variable a readonly variable. These variables also can't be modified, like constants, but only after initialization.It's not compulsory to initialize a readonly variable at the time of declaration; it can also be initialized in the constructor. see example:
using System;
namespace Class
{
internal class Call
{
readonly float x; //Readonly variable
int z;
public Call(float x)
{
this.x = x;
}
static void Main()
{
Call obj = new Call(50.5f);
Call obj1 = new Call(150.45f);
Console.WriteLine(obj.x);//out: 50.5
obj.z = 20;// No erroe
obj.x = 20;//Error give
Console.WriteLine(obj1.x);//Out: 150.45
}
}
}
x
has two copies created because the behavior of a readonly variable is the same as a non-static variable. It is initialized only after creating an instance of the class and once for each instance of the class created.
The difference between a readonly variable and a instance variable is that an instance variable can be modified, but a readonly variable cannot.
The difference between a readonly variable and a constant variable is that a constant is a single copy for the whole class, but a readonly variable has a copy for each instance. A constant variable is fixed for the entire class, while a readonly variable is specific to an instance of the class.
Non-static variable (Instance variable): Maintain one copy for each instance of the class that is initialize only if the instance is created. If there are n instances, there are n copies; if there are zero instances, there are zero copies.
Static variable: Maintain only one copy for the whole class. It can be modified but cannot create multiple copies. It can be initialized only one time.
Constant variable: Cannot be modified after declaration, so initialize the value at the time of declaration and maintaining only one copy throughout the class.
ReadOnly variable: Cannot be modified after initialization, maintains a copy for each instance.
Static
Static is a keyword used for:
Static Data Members
Static Methods
Static Constructors
Static Classes
Static Data Member
Definition:
A static data member (or static field) belongs to the class itself, not to any specific instance.
Only one copy of the static data member exists, no matter how many instances of the class are created.
Usage:
Shared among all instances of the class.
Commonly used to store values that every instance shares.
class Student
{
public int a, b; // Instance variables
public static int c; // Static variable
public Student(int a, int b)
{
this.a = a;
this.b = b;
}
}
class Program
{
static void Main()
{
Student.c = 100; // Setting static variable
Student s1 = new Student(1, 2);
Student s2 = new Student(3, 4);
Console.WriteLine(Student.c); // Output: 100
}
}
Static method
Definition:
A static method belongs to the class itself rather than any specific instance.
It can be called without creating an instance of the class.
Usage:
Used for operations that do not require any data from instance variables.
Commonly used for utility or helper functions.
class MathHelper
{
//Another class static method
public static int Add(int x, int y)
{
return x + y;
}
}
class Program
{
//Same class Static method
static void Hello()
{
Console.WriteLine("Hello method");
}
static void Main()
{
int result = MathHelper.Add(5, 3);
Console.WriteLine(result); // Output: 8
Hello();
}
}
A static method is preceded by the static keyword, and we can call a static method without creating an object if it is in the same class.
Static Constructors
Definition:
A static constructor is used to initialize static data members.
It is called automatically before any static members are accessed or any instance of the class is created.
It is created only once for the entire application. As soon as we access a static data member, the static constructor is called.
We cannot give access modifiers to a static constructor.
We cannot pass arguments to a static constructor.
Usage:
- Used to initialize static fields or perform actions that only need to be done once.
class Example
{
public static int x;
public static int y;
// Static constructor
static Example()
{
x = 10;
y = 20;
Console.WriteLine("Static constructor called");
}
}
class Program
{
static void Main()
{
Console.WriteLine(Example.x); // Output: 10
Console.WriteLine(Example.y); // Output: 20
}
}
Static Classes
Definition:
A static class cannot be instantiated, meaning you cannot create objects of a static class.
It can only contain static members (methods, properties, fields, etc.).
Usage:
Static classes are used to group methods that don't need to use or change the data stored in an instance of the class.
They are often used to create utility or helper functions that perform common tasks and can be accessed without creating an object.
static class Utility
{
public static void PrintMessage(string message)
{
Console.WriteLine(message);
}
}
class Program
{
static void Main()
{
Utility.PrintMessage("Hello, World!"); // Output: Hello, World!
}
}
Summary:
Static Data Members:
Shared by all instances.
Only one copy exists.
Static Methods:
Belong to the class.
Called without an instance.
Static Constructors:
Initialize static data.
Called automatically.
Static Classes:
Cannot be instantiated.
Contain only static members.
Inheritance
It's a way to use the members of one class in another class by creating a parent/child relationship between the classes. Which provides reusability.
The main aim of inheritance is "Code reusability." Inheritance is also called "Extending classes."
The existing class from which we are inheriting is called the “Base class,” “Super class,” or “Parent class.” The new class that is created from the existing class is called the “Derived class,” “Sub class,” or “Child class.”
Syntax: <modifiers> class <child class> : <parent class>
class A
{
- Members
}
class B:A
{
- Consuming the members of A from heare
}
Class A is the Parent, Base, or Super class.
Class B is the Child, Derived, or Sub class.
Note: In inheritance, the child class can consume members of its parent class as if it is the owner of those members, except for the private members of the parent.
Class2 calls the Class1 method as if it were its own property, but internally the method belongs to Class 1.
using System;
namespace Inheritance
{
class Class1
{
public void Test1()
{
Console.WriteLine("Hello 1");
}
public void Test2()
{
Console.WriteLine("Hello 2");
}
}
class Class2:Class1
{
public void Test3()
{
Console.WriteLine("Hello 3");
}
static void Main()
{
Class2 c = new Class2();
c.Test1(); // Hello 1
c.Test2(); // Hello 2
c.Test3(); // Hello 1
}
}
}
Currently, Class2 has 3 methods: all the properties that come from Class1 (Parent's property) and all those in Class2 (my property) are Class2's property.
Points:
Parent class constructor must be accessible to the child class; otherwise, inheritance will not be possible.
using System; class Class1 { public Class1 () { Console.WriteLine("Class 1 constructor call"); } } class Class2:Class1 { Class2() { Console.WriteLine("Class 2 constructor call"); } static void Main() { Class2 c = new Class2(); } } /*Out: Class 1 constructor call Class 2 constructor call */
If you don't specify access modifiers or use private access modifiers for class members, they can't be inherited because, by default, all methods in the class are private, and private members can't be accessed. Also, if the constructor is private, inheritance not possible.
If a class is public, the default constructor is public. If a class is internal, the default constructor is internal. The default access modifier for a class in C# is internal.
Execution always starts from top(parents) to bottom(child).
In inheritance, a child class can access the parent class's members, but the parent class cannot access members that are defined only in the child class.
using System; class Parent { public void ParentMethod() { Console.WriteLine("Method in Parent class"); } } class Child : Parent { public void ChildMethod() { Console.WriteLine("Method in Child class"); } } class Program { static void Main() { Child child = new Child(); child.ParentMethod(); // Accessible child.ChildMethod(); // Accessible Parent parent = new Parent(); parent.ParentMethod(); // Accessible parent.ChildMethod(); // Not accessible, will cause a compile-time error } }
We can inslize a parent class variable by using the child class instance to make it as a refrence.
using System; class Parent { public void Display() { Console.WriteLine("Parent class method"); } } class Child : Parent { public void Show() { Console.WriteLine("Child class method"); } } class Program { static void Main() { Parent p; // p is a variable of class1 Child c = new Child(); // c is instance of child class p = new Parent(); //Inslize the normaly p = c; // Assigning child class instance to parent class reference p.Display(); //Out: Parent class method //p.Show(); //Out: Error Class Parent not contain show member. } }
p = c;
:- Assigning child class instance to parent class reference, or you can say that p is a reference of the parent class created using the child class instance (Reference does not take memory: References of a class do not have memory; they will consume the memory of the assigned instance of the class. Memory is consumed by the instance, not the reference).Even though both
p
andc
refer to the same memory, we cannot access child class methods from parent class refrence.Every class we define or that is predefined in the language's libraries has a default parent class called the
Object
class, which is in theSystem
namespace. TheObject
class has four methods:Equals
,GetHashCode
,GetType
, andToString
.
using System; class Program { static void Main() { Object obj = new Object(); // Calling ToString method: Returns a string that represents the current object. string str = obj.ToString();//Return type of object Console.WriteLine("ToString: " + str); // Calling Equals method: Determines whether the current object is equal to another object. bool isEqual = obj.Equals(new Object()); Console.WriteLine("Equals: " + isEqual); // Calling GetType method: Gets the type of the current instance. Type type = obj.GetType(); Console.WriteLine("GetType: " + type); // Calling GetHashCode method: Returns a hash code for the object, which is used in hash-based collections. int hashCode = obj.GetHashCode(); Console.WriteLine("GetHashCode: " + hashCode); } } /*OUTPUT:- ToString: System.Object Equals: False GetType: System.Object GetHashCode: 46104728 */
These four methods are accessible anywhere.
using System; class Parent { public void Display() { Console.WriteLine("Parent class method"); } } class Program { static void Main() { Object obj = new Object(); Parent p = new Parent(); p.GetHashCode(); //We can access those 4 method any where. } }
- If a class doesn't explicitly inherit from another class, it automatically inherits the
Object
class, which is the base class for all classes in C#.
Object
class is the default parent class of C# .NET .
Object
class is present at the top of the hierarchy.
Object
class is always at the top.
Types of inheriance:
The type of inheritance is determined by the number of parent classes a child class has or the number of child classes a parent class has.
Single Inheritance: A class (child) inherits from one parent class.
Multilevel Inheritance: A class inherits from another class, which then inherits from another class, forming a chain of inheritance.
Hierarchical Inheritance: Multiple classes (children) inherit from a single parent class.
Hybrid Inheritance: A combination of two or more types of inheritance (Note: Not directly supported in C#, but can be achieved using interfaces).
Multiple Inheritance: A class (child) inherits from more than one parent class(Note: Not directly supported in C#, but can be achieved using interfaces).
In C#, we can't use multiple inheritance with classes. We can only use single inheritance with classes. It means Hybrid and Multiple inheritance are not supported.
Points:
In C#, multiple inheritance with classes is not supported. We can only use single inheritance with classes.
In the first point, we learned that whenever a child class instance is created, the child class constructor will implicitly call its parent class's constructor, but only if the constructor is parameterless. However, if the parent class's constructor is parameterized, the child class constructor can't implicitly call its parent's constructor. To overcome this problem, it is the programmer's responsibility to explicitly call the parent class's constructor from the child class constructor and pass values to those parameters. To call the parent's constructor from the child class, we need to use the
base
keyword.Error code (Why error come):-
using System; class Parent { public Parent(string message) { Console.WriteLine("Parent class parameterized constructor called with message: " + message); } } class Child : Parent { public Child() // Error: No parameterless constructor in Parent class { Console.WriteLine("Child class constructor called."); } } class Program { static void Main() { Child child = new Child(); } }
Solution Code:-
using System; class Parent { public Parent(string message) { Console.WriteLine("Parent class parameterized constructor called with message: " + message); } } class Child : Parent { public Child() : base("Message from Parent") { Console.WriteLine("Child class constructor called: "); } } class Program { static void Main() { Child child = new Child(); } }
One more example:-
using System; class Parent { public Parent(string message) { Console.WriteLine("Parent class parameterized constructor called with message: " + message); } } class Child : Parent { public Child(string childMessage) : base("Message from Parent") { Console.WriteLine("Child class constructor called with message: " + childMessage); } } class Program { static void Main() { Child child = new Child("Message from Child"); } }
How to use inheritance in our application:
Entity: It's a living or non-living object associated with a set of attributes.
Step 1: Identify the entities that are associated with the application we are developing.
E.g.:School Application: Student, TeachingStaff, NonTeachingStaff.
Step 2: Identify the attributes of each and every entity.
E.g.:Student: Id, Name, Address, Phone, Class, Marks, Grade, Fees.
E.g.:TeachingStaff: Id, Name, Address, Phone, Designation, Salary, Qualification, Subject.
E.g.:NonTeachingStaff: Id, Name, Address, Phone, Designation, Salary, DepartmentName, ReportingManager.
Step 3: Identify Common Attributes: From the attributes listed above, we can see that Id
, Name
, Address
, and Phone
are common across all entities.
Step 4: Create a Base/parent Class for Common Attributes: Create a base class called Person
that includes the common attributes. Take only generic names for the base class.
using System;
namespace SchoolApplication
{
// Base class with common attributes
public class Person
{
public int Id;
public string Name, Address, Phone;
public Person(int id, string name, string address, string phone)
{
Id = id;
Name = name;
Address = address;
Phone = phone;
}
public void DisplayBasicInfo()
{
Console.WriteLine($"Id: {Id}, Name: {Name}, Address: {Address}, Phone: {Phone}");
}
}
// Derived class for Student (take 8 attribute)
public class Student : Person
{
public string Class, Grade;
public double Marks, Fees;
public Student(int id, string name, string address, string phone, string studentClass, double marks, string grade, double fees)
: base(id, name, address, phone)
{
Class = studentClass;
Marks = marks;
Grade = grade;
Fees = fees;
}
public void DisplayStudentInfo()
{
DisplayBasicInfo();
Console.WriteLine($"Class: {Class}, Marks: {Marks}, Grade: {Grade}, Fees: {Fees}");
}
}
// Staff Base class with common attributes (take 6 attribute)
public class Staff : Person
{
public string Designation;
public double Salary;
public Staff(int id, string name, string address, string phone, string designation, double salary)
: base(id, name, address, phone)
{
Designation = designation;
Salary = salary;
}
public void DisplayStaffInfo()
{
DisplayBasicInfo();
Console.WriteLine($"Designation: {Designation}, Salary: {Salary}");
}
}
// Derived class for Teaching (take 8 attribute)
public class Teaching : Staff
{
public string Qualification, Subject;
public Teaching(int id, string name, string address, string phone, string designation, double salary, string qualification, string subject)
: base(id, name, address, phone, designation, salary)
{
Qualification = qualification;
Subject = subject;
}
public void DisplayTeachingStaffInfo()
{
DisplayStaffInfo();
Console.WriteLine($"Qualification: {Qualification}, Subject: {Subject}");
}
}
// Derived class for NonTeaching (take 8 attribute)
public class NonTeaching : Staff
{
public string DepartmentName, ReportingManager;
public NonTeaching(int id, string name, string address, string phone, string designation, double salary, string departmentName, string reportingManager)
: base(id, name, address, phone, designation, salary)
{
DepartmentName = departmentName;
ReportingManager = reportingManager;
}
public void DisplayNonTeachingStaffInfo()
{
DisplayStaffInfo();
Console.WriteLine($"DepartmentName: {DepartmentName}, ReportingManager: {ReportingManager}");
}
}
class Program
{
static void Main(string[] args)
{
Student student = new Student(1, "John Doe", "123 Elm St", "555-1234", "10th Grade", 90, "A", 1000.00);
Teaching teacher = new Teaching(2, "Jane Smith", "456 Oak St", "555-5678", "Professor", 50000.00, "PhD", "Mathematics");
NonTeaching staff = new NonTeaching(3, "Bob Brown", "789 Pine St", "555-9012", "Clerk", 30000.00, "Administration", "Mr. Green");
student.DisplayStudentInfo();
Console.WriteLine();
teacher.DisplayTeachingStaffInfo();
Console.WriteLine();
staff.DisplayNonTeachingStaffInfo();
}
}
}
Overriding: It’s a concept of hiding a base class method with a derived class method when we have the same name in both base and derived classes.
// Base class
public class Animal
{
// Base class method
public void MakeSound()
{
Console.WriteLine("Animal makes a sound");
}
}
// Derived class
public class Dog : Animal
{
// Hide the base class method
public new void MakeSound()
{
Console.WriteLine("Dog barks");
}
}
class Program
{
static void Main(string[] args)
{
Animal myAnimal = new Animal();
Dog myDog = new Dog();
Animal myNewDog = new Dog();
myAnimal.MakeSound(); // Output: Animal makes a sound
myDog.MakeSound(); // Output: Dog barks
myNewDog.MakeSound(); // Output: Dog barks (because myNewDog is a Dog type reference)
Console.ReadLine();
}
}
Method Overloading
You can have multiple methods with the same name but different parameters. This is useful when you need the same method to handle different types or numbers of inputs.
using System;
class Program
{
// Method to calculate the area of a rectangle
static double CalculateArea(double length, double width)
{
return length * width;
}
// Overloaded method to calculate the area of a circle
static double CalculateArea(double radius)
{
return Math.PI * radius * radius;
}
// Overloaded method to calculate the area of a circle
static double CalculateArea(int radius)
{
return Math.PI * radius * radius;
}
// Overloaded method to calculate the area of a triangle
static double CalculateArea(double baseLength, double height, bool isTriangle)
{
return 0.5 * baseLength * height;
}
// Overloaded method to calculate the area of a triangle
static double CalculateArea( bool isTriangle, double baseLength, double height)
{
return 0.5 * baseLength * height;
}
static void Main(string[] args)
{
double rectangleArea = CalculateArea(10, 5); // Area of rectangle
double circleArea = CalculateArea(7); // Area of circle
int TypecircleArea = CalculateArea(7); // Area of circle
double triangleArea = CalculateArea(10, 5, true); // Area of triangle
double OrdertriangleArea = CalculateArea(true, 10, 5); // Area of triangle
Console.WriteLine("Area of Rectangle: " + rectangleArea);
Console.WriteLine("Area of Circle: " + circleArea);
Console.WriteLine("Area of Circle: " + TypecircleArea);
Console.WriteLine("Area of Triangle: " + triangleArea);
Console.WriteLine("Area of Triangle: " + OrdertriangleArea);
}
}
/*
Area of Rectangle: 50
Area of Circle: 153.93804002589985
Area of Circle: 153
Area of Triangle: 25
Area of Triangle: 25
*/
Advantages of Method Overloading
Code Readability: Overloading lets you use the same method name with different parameters, making the code easier to read and understand. It groups related operations under one name.
Improved Code Maintenance: Grouping similar methods under one name makes it easier to maintain the code and reduces errors during updates.
Polymorphism: Overloading is a type of compile-time polymorphism. It allows you to perform similar operations in different ways based on the parameters, making the code more flexible.
Enhanced Functionality: By having multiple versions of a method, you can handle different types or numbers of inputs without changing the method name.
Ease of Use: Overloading makes the API simpler for users by letting them use the same method name with different arguments, instead of remembering multiple names for similar tasks.
Code Reusability: Overloading promotes reusability by allowing the same method to work with different types of data or different numbers of parameters.
Consistency: It ensures consistent method naming, making the codebase more uniform and easier to navigate.
Disadvantages of Method Overloading
Complexity: Overloading too many methods can make the code harder to read and maintain.
Ambiguity: If not managed properly, it can cause confusion in method calls, especially with similar parameter types.
Method overloading is a powerful feature in C# that allows you to define multiple methods with the same name but different parameters. This enhances code readability, reduces duplication, and simplifies method calls, although it must be used carefully to avoid complexity and ambiguity.
Note: If you show difference in return type of method, it doesn’t come under Method Overloading.
That example give error:-
using System;
namespace Method
{
internal class OverLiading
{
static void Sum(int a, int b)
{
Console.WriteLine(a+b);
}
static int Sum(int a, int b)//give error
{
Console.WriteLine(a + b);
return a + b;
}
static double Sum(int a, int b)//give error
{
Console.WriteLine(a + b);
return a + b;
}
static void Main(string[] args)
{
Sum(10,20);
Console.WriteLine(Sum(10, 20));
Console.WriteLine(Sum(10, 20));
}
}
}
That is correct:
using System;
namespace Method
{
internal class OverLiading
{
static void Sum(int a, int b)
{
Console.WriteLine(a+b);
}
static int Sum(int a, int b, int c)
{
return a + b +c;
}
static double Sum(int a, int b, int c, int d)
{
return a + b + c + d;
}
static void Main(string[] args)
{
Sum(10, 20);
Console.WriteLine(Sum(10, 20, 30));
Console.WriteLine(Sum(10, 20, 30, 40));
}
}
}
It means method overloading does not depend on the return type of the method; it depends on the signature of the method, which means the parameters of the method, like this signature (int a, int b), (int a, int b, int c), or etc types.
Predefined method example:
string s = "Hello World";
s.IndexOf('o'); //Out: 4 //Return 1st occurance
s.IndexOf('o',5); //Out: 7 //Return next occurance
s.IndexOf("ll"); //Out: 2 //Return occurance
Here we use the same method with different behavior. We pass a string and a character, and we also pass one or two parameters.
It is a process of write a method with same name but with different function signature. The function signature can be different in any one of the following.
Definition of Method Overloading: It is an approach of defining a method with multiple behaviors where the behavior changes based on the parameter . If the input changes, the output changes based on the type of parameter, number of parameters, or order of parameters.
Polymorphism
Definition: Polymorphism is the ability in programming to use the same interface(same method) for different data types.
Types of Polymorphism:
Polymorphism can be implemented in two different ways.
Compile-Time Polymorphism (Static Polymorphism):
Achieved through method overloading and operator overloading.
The method to be executed is determined at compile time.
Example of Method Overloading:
class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
public double Add(double a, double b)
{
return a + b;
}
}
class Program
{
static void Main()
{
Calculator calc = new Calculator();
Console.WriteLine(calc.Add(2, 3)); // Calls Add(int, int)
Console.WriteLine(calc.Add(2.5, 3.5)); // Calls Add(double, double)
}
}
Run-Time Polymorphism (Dynamic Polymorphism):
Achieved through method overriding and Interfaces where a method in a derived class overrides a method in the base class.
The method to be executed is determined at runtime.
Example of Method Overriding:
class Animal
{
public virtual void Speak()
{
Console.WriteLine("Animal sound");
}
}
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Bark");
}
}
class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("Meow");
}
}
class Program
{
static void Main()
{
Animal myDog = new Dog();
Animal myCat = new Cat();
myDog.Speak(); // Outputs "Bark"
myCat.Speak(); // Outputs "Meow"
}
}
Key Points About Polymorphism:
Method Overloading: Same method name with different signatures within the same class. This is compile-time polymorphism.
Method Overriding: A derived class provides a specific implementation of a method that is already defined in its base class. This is run-time polymorphism.
Interfaces and Inheritance: Polymorphism is often used in conjunction with interfaces and inheritance, allowing for flexible and reusable code.
Why is Polymorphism Important?
Flexibility: Polymorphism allows for writing more flexible and maintainable code. For example, you can write code that works with a superclass, and it will automatically work with any subclass without modification.
Code Reusability: It promotes code reusability by allowing you to use the same method or interface to work with different types of objects.
Simplification: It simplifies code, as a single method can work with objects of different types.
Polymorphism is one of the fundamental principles of object-oriented programming, along with encapsulation, inheritance, and abstraction.
Method Overriding:
Definition: It's an approach of re-implementing a parent class's method in the child class with the same signature and changing the behavior.
Difference between Method Overloading and Method Overriding:
Difference 1:
class Class1
{
public void Show(){} // Method Overloading
public void Show(int i){} // Method Overloading
public virtual void Test(){} // Method intended for overriding
}
class Class2 : Class1
{
public void Show(string i){} // Method Overloading
// Correct method overriding
public override void Test(){} // Method overriding
}
Overloading | Overriding |
In this case we define multiple methods with the same name by changing their parameters. | In this case we define multiple methods with the same name and same parameters. |
Difference 2:
Overloading | Overriding |
This can be performed either within a class or between parent and child classes. | This can be performed only between parent and child classes and can never be performed within the same class. |
Difference 3:
Note:
If we want to override a parent's method in the child class, that method should first be declared using the virtual modifier in the parent class. By using the
virtual
keyword, the parent class gives permission to override the method.Any virtual method of the parent class can be overridden by the child class if needed, using the
override
modifier.
using System;
class Class1
{
// Method Overloading
public void Show()
{
Console.WriteLine("Parent show method...");
}
// Method Overloading
public void Show(int i)
{
Console.WriteLine("Parent show parameterized method...");
}
// Method overriding
public virtual void Test()
{
Console.WriteLine("Parent Test method...");
}
}
class Class2 : Class1
{
// Method Overloading
public void Show(string s)
{
Console.WriteLine("Child show parameterized method with string...");
}
// Method overriding
public override void Test()
{
Console.WriteLine("Child Test method...");
}
}
class Program
{
static void Main(string[] args)
{
Class1 c1 = new Class1();
c1.Test(); // Output: Parent Test method...
Class2 c2 = new Class2();
c2.Show(); // Output: Parent show method...
c2.Show(5); // Output: Parent show parameterized method...
c2.Show("example"); // Output: Child show parameterized method with string...
c2.Test(); // Output: Child Test method...
Console.ReadLine();
}
}
Overloading | Overriding |
While overloading a parent class method under the child class, child doesn't require to take any permission from the parent class. | Whild Overriding a parent's method under child class, child class requires a permission from it's parent class. |
Difference 4:
Overriding is all about changing the behavior of a parent's method in the child class.
Example of method overriding:
class ParentClass
{
public virtual int Test()
{
Console.WriteLine("Run parent class Test method!!");
return 100;
}
}
class Program : ParentClass
{
public override int Test()
{
Console.WriteLine("Run child class Test method!!");
return 20;
}
static void Main(string[] arg)
{
Program p = new Program();
p.Test();
}
}
Method Hiding/shadowing
Definition of method overriding: Method overriding is a technique or an approach of re-implementing a parent class method in the child class with exactly the same name and same signature.
Definition of method hiding/shadowing: Method hiding is a technique or an approach of re-implementing a parent class method in the child class with exactly the same name and same signature.
But the difference between Method overriding and Method hiding is that in Method overriding, the child class re-implements its parent's methods that are declared as virtual, whereas in Method hiding, the child class can re-implement any parent's method even if the method is not declared as virtual.
//Parent class:-
class ParentClass
{
public virtual void Test()
{
Console.WriteLine("Run parent class Test method!!");
}
public void Test1()
{
Console.WriteLine("Run parent class Test 1 method!!");
}
}
Case 1: Normal Inheriance
internal class Program : ParentClass
{
static void Main(string[] args)
{
Program p = new Program();
p.Test();
p.Test1();
}
}
/* Out:
Run parent class Test method!!
Run parent class Test 1 method!!
*/
Case 2: Method overriding:- Change the behavior of the parent class method if I don't require the parent class behavior.
internal class Program : ParentClass
{
public override void Test()
{
Console.WriteLine("Run child class Test method!!");
}
static void Main(string[] args)
{
Program p = new Program();
p.Test();
p.Test1();
}
}
/* Out:
Run child class Test method!!
Run parent class Test 1 method!!
*/
In method overriding, we can change the behavior by using the override
keyword if the parent class methods are declared as virtual
; otherwise, we cannot change the behavior.
Case 2: Method Hiding:- The child class can re-implement any parent's method even if the method is not declared as virtual.
internal class Program : ParentClass
{
public new void Test()
{
Console.WriteLine("Run child class Test method!!");
}
public new void Test1()
{
Console.WriteLine("Run child class Test 1 method!!");
}
static void Main(string[] args)
{
Program p = new Program();
p.Test();
p.Test1();
}
}
/* Out:
Run child class Test method!!
Run child class Test 1 method!!
*/
Without the new keyword, it also works. See it.
internal class Program : ParentClass
{
public void Test()
{
Console.WriteLine("Run child class Test method!!");
}
public void Test1()
{
Console.WriteLine("Run child class Test 1 method!!");
}
static void Main(string[] args)
{
Program p = new Program();
p.Test();
p.Test1();
}
}
/* Out:
Run child class Test method!!
Run child class Test 1 method!!
*/
If you do not use the new
keyword, the compiler will give a warning: "Use the new keyword if hiding was intended." The new
keyword helps you remember that you are hiding the parent method, which can be useful in the future.
We can re-implement a parent class method under child class using 2 approaches:
Method Overriding,
Method Hiding / Shadowing,
After re-implementing the parent class's method in the child class, the child class instance will start calling the local method, which is the re-implemented method. However, if needed, we can also call the parent class's method from the child class using two approaches.
Approach 1: By creating the instance of the parent class in the child class, we can call the parent's class method in the child class.
class Program : ParentClass
{
public override void Test()
{
Console.WriteLine("Run child class Test method!!");
}
public new void Test1()
{
Console.WriteLine("Run child class Test 1 method!!");
}
static void Main(string[] args)
{
Program p = new Program();
p.Test();
p.Test1();
ParentClass pc = new ParentClass();
pc.Test();
pc.Test1();
}
/*
Run child class Test method!!
Run child class Test 1 method!!
Run parent class Test method!!
Run parent class Test 1 method!!
*/
}
Approach 2: By using the base
keyword, we can also call the parent's method from the child class, but keywords like this
and base
can't be used from static blocks.
class Program : ParentClass
{
public override void Test()
{
Console.WriteLine("Run child class Test method!!");
}
public new void Test1()
{
Console.WriteLine("Run child class Test 1 method!!");
}
public void ParentChild()
{
base.Test();//Use base keyword to call the parent class method.
base.Test1();
}
static void Main(string[] args)
{
//base.Test();//Give error: Becouse inside the static block not work base & this keyword
//base.Test1();//Give error
Program p = new Program();
p.ParentChild();
}
/*
Run parent class Test method!!
Run parent class Test 1 method!!
*/
}
Difference between Overriding and Hiding:
Inheritance rule no. 3: A parent class reference, even if created using the child class instance,can't access any members that are purely defined in the child class but can call overridden members of the child class, because overridden members are not considered pure child class members, but members re-implemented using the hiding approach are considered pure child class members and are not accessible to the parent's reference.
Example imp d/f Overriding and Hiding:-
class Program : ParentClass { public override void Test() { Console.WriteLine("Run child class Test method!!"); } public new void Test1() { Console.WriteLine("Run child class Test 1 method!!"); } static void Main(string[] args) { Program c = new Program(); //c is the instance of child class ParentClass p = c; // p is refrence of parent class created by using child's class instance p.Test();//Calling the child class method not parent class method p.Test1();//Calling the parent class method not child class method } /* Run child class Test method!! Run parent class Test 1 method!! */ }
p.Test(); :- Calling the child class method not parent class method (invoke child class method).
p.Test1(); :- Calling the parent class method not child class method (invoke parent class method).
Operator Overloading
Method overloading is an approach of defining multiple behaviors for a method, and those behaviors will vary based on the parameters of that method.
String s = "Hello how are you";
s.Substring(14); //Out:you
s.Substring(10); //Out:are you
s.Substring(10, 3); //Out:are
Operator overloading allows an operator to have different behaviors based on the types of operands . For example, the + operator adds two numbers together, but it combines two strings when used with them.
Number + Number => Addition
String + String => Concatenation
int x = 12; int y = 13; int z = x + y;
Here is the method:
public static int <method name>(int a, int b)
.Operator overloading:
public static int operator + (int a, int b)
. For example,z = int a + int b
meansz
is an operator that takes two integer values and returns an integer value. The+
is used to add the two numbers. These types of methods are implemented in the base class library. Operator overloading must be static.Syntax: [<modifiers>] static <return type> operator <opt>(<operand type>){-logic}
operator: Always lowercase.
Example: Create operator for addition the Matrix**.**
using System;
namespace Demo
{
// Define the Matrix class within the Demo namespace
internal class Matrix
{
// Declare non-static variables for the matrix elements
int a, b, c, d;
// Define the constructor to initialize matrix values
public Matrix(int a, int b, int c, int d)
{
this.a = a;
this.b = b;
this.c = c;
this.d = d;
}
// Define the addition operator for the Matrix class
public static Matrix operator +(Matrix obj1, Matrix obj2)
{
return new Matrix(obj1.a + obj2.a, obj1.b + obj2.b, obj1.c + obj2.c, obj1.d + obj2.d);
}
// Define the subtraction operator for the Matrix class
public static Matrix operator -(Matrix obj1, Matrix obj2)
{
return new Matrix(obj1.a - obj2.a, obj1.b - obj2.b, obj1.c - obj2.c, obj1.d - obj2.d);
}
}
// Define the TestMatrix class to test the Matrix operations
class TestMatrix
{
static void Main()
{
// Initialize two Matrix objects with specific values
Matrix m1 = new Matrix(20, 18, 16, 14);
Matrix m2 = new Matrix(10, 8, 6, 4);
// Perform addition and subtraction of the matrices
Matrix m3 = m1 + m2;//If you do this without Define the addition operator for the Matrix class then come error
Matrix m4 = m1 - m2;//If you do this without Define the subtraction operator for the Matrix class then come error
// Print the results (current implementation will print the class name)
Console.WriteLine(m1); // Output: Demo.Matrix
Console.WriteLine(m2); // Output: Demo.Matrix
Console.WriteLine(m3); // Output: Demo.Matrix
Console.WriteLine(m4); // Output: Demo.Matrix
}
}
}
Overloading WriteLine Method
The
WriteLine
method in theConsole
class can handle different types of parameters likeint
,float
,bool
, and objects. When you pass an object toWriteLine
, it calls theToString
method of that object.When you use
WriteLine
with an instance of a class, it calls theWriteLine
method that takes anobject
as a parameter. This method then calls theToString
method on the object you passed.How WriteLine Works with Objects
Passing Object to WriteLine:
Console.WriteLine(m1);
Here,m1
is an instance of theMatrix
class.Internal Handling: The
WriteLine
method for objects is:public static void WriteLine(object value);
When you passm1
toWriteLine
, it callsvalue.ToString()
wherevalue
ism1
.Default
ToString
Behavior: By default,ToString
returns the class name if not overridden. So, callingConsole.WriteLine(m1)
without overridingToString
in theMatrix
class will printDemo.Matrix
.Overriding
ToString
: To print useful information about the matrix, we should override theToString
method in theMatrix
class.Overriding ToString
To override the
ToString
method, we need to inherit from theObject
class (which all classes do by default) and provide our own implementation:
public override string ToString() { return $"{a} {b}\n {c} {d}"; }
This way, when
Console.WriteLine(m1)
is called, it will print the matrix elements instead of the class name.
Here's the updated Matrix
class with the ToString
method overridden:
internal class Matrix
{
int a, b, c, d;
public Matrix(int a, int b, int c, int d)
{
this.a = a; this.b = b;
this.c = c; this.d = d;
}
public static Matrix operator +(Matrix obj1, Matrix obj2)
{
return new Matrix(obj1.a + obj2.a, obj1.b + obj2.b, obj1.c + obj2.c, obj1.d + obj2.d);
}
public static Matrix operator -(Matrix obj1, Matrix obj2)
{
return new Matrix(obj1.a - obj2.a, obj1.b - obj2.b, obj1.c - obj2.c, obj1.d - obj2.d);
}
public override string ToString()
{
return $"{a} {b}\n {c} {d}";
}
}
Now, when you run the TestMatrix
class, it will print the elements of the matrices.
NOTS: If you want to the instance of all value print as it is simply go to override the ToString() method.
Abstract
Abstraction is a process of hiding the implementation details and showing only functionality to the user. Another way, it shows only essential things to the user and hides the internal details.
Abstraction can be achieved by two ways:
Abstract class
Interface.
Abstract class
Abstract Method: A method without any body is called an abstract method.
Abstract Class: A class that has any abstract members is called an abstract class.
- We cannot create object of abstract class.
Example: Consider a scenario with geometric shapes where different shapes share some common characteristics but also have their own specific attributes.
Entities: Rectangle, Circle, Triangle, Cone.
Attributes:
Rectangle: Width, Height
Circle: Radius, Pi
Triangle: Width, Height
Cone: Radius, pi, Height
Common attributes are: Width, Height, Radius, Pi.
According to the example, create a parent class for the attributes of all four classes: Rectangle, Circle, Triangle, and Cone.
// Abstract base class representing a geometric figure
public abstract class Figure
{
// Common attributes for all figures
public double Width, Height, Radius;
public const double Pi = 3.14;
//Hear we cannot define the formula because each Entity having diffrent formula
//Hear come the Abstract method, Method can declare only hear but not consume hear
// Abstract method to calculate the area of the figure
// Must be overridden in derived classes
public abstract double GetArea();
// The advantage of declaring an abstract method is that all derived classes
// will have to provide their own implementation for calculating the area.
}
// Rectangle class inheriting from Figure
public class Rectangle : Figure
{
public Rectangle(double width, double height)
{
this.Width = width;
this.Height = height;
}
// Override GetArea to calculate the area of a rectangle
public override double GetArea()
{
return Width * Height;
}
}
// Circle class inheriting from Figure
public class Circle : Figure
{
public Circle(double radius)
{
this.Radius = radius;
}
// Override GetArea to calculate the area of a circle
public override double GetArea()
{
return Pi * Radius * Radius;
}
}
// Triangle class inheriting from Figure
public class Triangle : Figure
{
public Triangle(double width, double height)
{
this.Width = width;
this.Height = height;
}
// Override GetArea to calculate the area of a triangle
public override double GetArea()
{
return 0.5 * Width * Height;
}
}
// Cone class inheriting from Figure
public class Cone : Figure
{
public Cone(double radius, double height)
{
this.Radius = radius;
this.Height = height;
}
// Override GetArea to calculate the surface area of a cone
public override double GetArea()
{
// Surface area of a cone: πr(r + √(r² + h²))
return Pi * Radius * (Radius + Math.Sqrt(Radius * Radius + Height * Height));
}
}
// Testing the classes
class AbstractFigure
{
static void Main()
{
// Initializes an array of Figure objects with instances of different derived classes (Rectangle, Circle, Triangle, Cone).
Figure[] figures = new Figure[]
{
new Rectangle(10, 5),
new Circle(7),
new Triangle(6, 8),
new Cone(3, 4)
};
foreach (Figure figure in figures)
{
Console.WriteLine($"Area: {figure.GetArea()}");
}
}
}
INTERFACE:
Class:
A user-defined data type that can contain both non-abstract methods (methods with a body) and abstract methods (methods without a body, if the class is abstract). Classes can be created.
Abstract Class:
A user-defined data type that cannot be created directly. It can have bothnon-abstract methods (with a body) and abstract methods (without a body). Abstract classes are meant to be subclassed.
Interface:
A user-defined data type that contains only abstract methods (methods without a body). Interfaces cannot be created and are used to define a contract that classes can implement.
NOTE: Every abstract method in an interface must be implemented by the class that inherits from the interface.
Parent class has an interface with have some rules and regulations, and the child class fulfills the own requirements with follow the rules and regulations which provided by the parent class.
If you have any abstract method, it is implemented only by the child class.
- Generally, a class inherits from another class to consume the members of its parent, whereas if a class is inheriting from an interface, it is to implement the members of its parents.
Note: A class can inherit from a class and interface at a time.
Create Interface:
Go to Visual Studio and press Ctrl + Shift + A
, select Interface shown in the Add New Item
panel, give it a name, and press OK
.
Name Create: Make it a habit when you create an interface to place "I" at the beginning of the name to indicate that it is an interface.
How to define Interface:
[<modifiers>] Interface <Name>{-Abstract Member Declaration hear}
Create Method in Interface:
public abstract void Add(int a, int b);
//Or Only
void Add(int a, int b);
NOTES:
The default scope of a member of an interface is public. But in the case of a class, it is private.
By default, every member of an interface is abstract, so we don't need to use the abstract modifier again like we do with an abstract class.
We can't declare any fields/variables under an interface.
internal interface ITestInterface { int x; //It's Give error }
If required, an interface can inherit from another interface; it is possible.
internal interface ITestInterface1 { void Add(int a, int b); } internal interface ITestInterface2:ITestInterface1 { void Sub(int a, int b); } //ITestInterface2 having two method add and Sub
Every member of an interface should be implemented in the child class of the interface without fail, but while implementing, we don't need to use the override modifier as we do with an abstract class.
Abstract class modifiers can't be changed at initialization time.
Example: Initialization abstract method
internal interface ITestInterface1 { void Add(int a, int b); } class ImplementationClass:ITestInterface1 { //Use public because the default scope of a class is private, and interface members are by default public. public void Add(int a, int b){} }
OR you also Initialization abstract method like that.
internal interface ITestInterface1 { void Add(int a, int b); } class ImplementationClass:ITestInterface1 { void ITestInterface1.Add(int a, int b){} }
internal interface ITestInterface1 { void Add(int a, int b); void Sub(int a, int b); } class ImplementationClass:ITestInterface1 { public void Add(int a, int b) { Console.WriteLine(a+b); } void ITestInterface1.Sub(int a, int b) { Console.WriteLine(a - b); } static void Main() { ImplementationClass obj = new ImplementationClass(); obj.Add(100, 300); obj.Sub(300, 100); //Give error } }
obj.Sub(300, 100);
gives an error because you can't callobj.Sub(300, 100)
directly. This happens becauseSub
is implemented explicitly for the interfaceITestInterface1
. In explicit interface implementation, the method is not part of the public interface of the class and can only be accessed through an instance of the interface. To call theSub
method, you need to cast to the interface like this:
internal interface ITestInterface1 { void Add(int a, int b); void Sub(int a, int b); } class ImplementationClass : ITestInterface1 { public void Add(int a, int b) { Console.WriteLine(a + b); } void ITestInterface1.Sub(int a, int b) { Console.WriteLine(a - b); } static void Main() { ImplementationClass obj = new ImplementationClass(); obj.Add(100, 300); // To call the Sub method, you need to cast to the interface ITestInterface1 interfaceObj = obj; //Create interface interfaceObj.Sub(300, 100); interfaceObj.Add(100, 300); } }
internal interface ITestInterface1 {void Add(int a, int b);} class ImplementationClass : ITestInterface1 { void Add(int a, int b) //GIve error, because you not use public { Console.WriteLine(a + b); } static void Main() { ImplementationClass obj = new ImplementationClass(); obj.Add(100, 300);//GIve error } }
Multiple Inheritance with Interface:
As we know, there are 5 types of inheritance:
Single, Multilevel, Hierarchical, Hybrid, and Multiple
. Even though multiple inheritance is not supported through classes in C#, it is still supported through interfaces.A class can have only one immediate parent class, meaning one class can inherit only one other class. Whereas the same class can have any number of interfaces as its parent, i.e., multiple inheritance is supported in C# through interfaces.
A class can have only one inheritance, but it can implement any number of interfaces.
Q: Why is multiple inheritance not supported through classes, and how is it supported through interfaces?
Ans: Ambiguity Issue: When a class inherits from multiple classes, there can be a problem if both parent classes have methods with the same name. This causes confusion because the compiler won't know which method to call. For example, if
ClassA
andClassB
both have a methodShow()
, andClassC
inherits from both, the compiler won't know whichShow()
method to use whenClassC
callsShow()
.class ClassA { public void Show() { Console.WriteLine("ClassA Show"); } } class ClassB { public void Show() { Console.WriteLine("ClassB Show"); } } // This will cause ambiguity if C# allowed multiple inheritance. class ClassC : ClassA, ClassB { // Compiler confusion: which Show() to inherit? }
Diamond Problem: In multiple inheritance, the diamond problem occurs when a class inherits from two classes that both inherit from the same class. This can cause confusion in the inheritance chain.
Multiple Inheritance in Interfaces:
No Implementation Conflict: Interfaces do not provide method implementations, only declarations. Therefore, when a class implements multiple interfaces, there is no risk of ambiguity because the implementing class must provide the specific implementation for each method. This eliminates the confusion that arises from multiple inheritance in classes.
interface IInterfaceA
{
void Show();
}
interface IInterfaceB
{
void Show();
}
class ImplementationClass : IInterfaceA, IInterfaceB
{
public void Show()
{
Console.WriteLine("Implementation of Show");
}
}
Explicit Implementation(separately implimentation): If you need to separate methods from different interfaces, a class can explicitly implement the interface methods. This helps to avoid confusion.
interface IInterfaceA
{
void Show();
}
interface IInterfaceB
{
void Show();
}
class ImplementationClass : IInterfaceA, IInterfaceB
{
void IInterfaceA.Show() //separately Implement method
{
Console.WriteLine("IInterfaceA Show");
}
void IInterfaceB.Show() //separately Implement method
{
Console.WriteLine("IInterfaceB Show");
}
}
class Program
{
static void Main()
{
ImplementationClass obj = new ImplementationClass();
// Requires casting to call the explicit implementations
//Calling
IInterfaceA i1 = obj;
i1.Show(); // Outputs: IInterfaceA Show
IInterfaceB i2 = obj;
i2.Show(); // Outputs: IInterfaceB Show
//Or we call like that
((IInterfaceA)obj).Show(); // Outputs: IInterfaceA Show
((IInterfaceB)obj).Show(); // Outputs: IInterfaceB Show
}
}
Consuming will cause ambiguity, but implementing does not cause ambiguity.
There are two ways of implementing interfaces:
Implicit: All interfaces assume my method is implemented.
Explicitly: Clearly implement a particular interface.
interface A { void show(); } interface B { void show(); } //Select 'A' and right click press 'Quick actions and refectoring' next open automaticly quick action class Face: A, B { public void show() { } //Option one: Implement interface void A.show() { } //Option two: Implement member explicitly static void Main(){} }
Abstract Class and Abstract Method:
Abstract Method: A method without a body is called an abstract method. It only contains the method's declaration.
Non-abstract method:
public void show() { }
Abstract method:
public abstract void show();
Abstract Class: If a class contains any abstract member, it is called an abstract class. To define an abstract class, we need to use the abstract
keyword. If you try to run this class without adding the abstract
keyword, you will get an error.
Non-abstract class:
class Math { // Non-abstract members }
Abstract class:
abstract class Math { // Non-abstract members with abstract members }
If a method is declared as abstract in any class, the child class of that class must implement the method without fail. It is mandatory to impliment abstract method in child class.
The concept of an abstract method is nearly similar to the concept of method overriding, but the difference is that abstract methods must be implemented in derived classes, while overriding a parent class's virtual method is optional.
If an abstract class contains both abstract and non-abstract methods, then the child class must implement every abstract method of the parent class. Only then can the child class use the non-abstract methods of the parent class.
It is not possible to create an instance of an abstract class because abstract classes are designed to be incomplete and serve as a base for other child classes. Abstract classes can contain abstract methods (methods without a body) that must be implemented by derived classes. Since the abstract class itself may have incomplete implementations, it cannot be instantiated directly. Instead, it must be subclassed, and the subclass must provide concrete implementations for all the abstract methods. This ensures that any object created will have fully defined behavior.
You cannot create an instance of an abstract class, but you can create a reference to it.
abstract class Aob
{
public abstract void show();
}
class Face: Aob
{
// Override the abstract method from the base class
public override void show()
{
Console.WriteLine("Ok");
}
static void Main()
{
Face f = new Face(); //Instance of own class
//Aob a = new Aob();//Give error: you cannot create instance of abstract class
Aob a = f; //pass refrence of abstract class
f.show();
}
}
Structures:
A structure is a user-defined data type, similar to a class.
In C Language: Structures contain only fields.
In C#: Structures can contain fields, methods, constructors, properties, indexers, operator methods, and more.
Syntax:
[<modifiers>] struct <Name> { // Define members (fields, methods, etc.) }
Creating a Structure in Visual Studio:
Press
Ctrl + Shift + A
to open the "Add New Item" dialog.Select "Code File" from the list. : This code file is an empty file; it's just a .cs file.
Enter a name for your file.
Press "OK" to add the file.
Example:
using System; namespace Demo { struct StructTest//Declare structure { public void show() { Console.WriteLine("Method in struct"); } static void Main() { StructTest st = new StructTest(); st.show(); } } }
Difference between Class and Structure:
Class:
Reference Type: Classes are reference types.
Memory Allocation: Instances of classes are allocated memory on the managed heap.
Use Case: Classes are used to represent entities with larger volumes of data.
Instantiation:
new
keyword is mandatory for creating the instance.Test t = new Test();
Field Initialization: Fields of a class can be initialized at the time of declaration.
class Test { int x = 5; // Field initialization }
Example of initializing field value:
class Test { int x; public void Display() { Console.WriteLine(x); } static void Main() { Test t = new Test(); t.x = 10; t.Display(); } }
We can define any constructor in the class that is either parameterless or parameterized. If no constructor is defined, there will be an implicit constructor will be called which is default constructor.
If no constructors are defined in a class, there will be one implicit constructor after compilation. If we define "n" constructors in a class, there will be "n" constructors after compilation.
Class can be inherited by another class.
A class can implement an interface.
Structure:
Value Type: Structures are value types.
Memory Allocation: Instances of structures are allocated memory on the stack.
Use Case: Structures are used to represent smaller volumes of data.
Note:
All pre-defined data types in our language that are reference types, such as
String
andObject
, are classes.All pre-defined data types that are value types, such as
int
(Int32
),float
(Single
), andbool
(Boolean
), are structures.
To see the implementation of
string
orint
, follow these steps: declare astring
or any other data type, then select and right-click it and select "Go to Implementation." You will see the source code like that://For int: public struct Int32 : IComparable, IFormattable, IConvertible, IComparable<int>, IEquatable<int> {/* Implementation*/} //For string: public sealed class String : IComparable, ICloneable, IConvertible, IEnumerable, IComparable<string>, IEnumerable<char>, IEquatable<string> {/* Implementation*/}
Instantiation:
new
keyword is optional for creating the instance.Test t; // Without new keyword Test t = new Test(); // With new keyword
Field Initialization: Fields of a struct cannot be initialized at the time of declaration.
struct Test { int x; // Field cannot be initialized here }
Example of initializing field value:
struct Test { int x; public void Display() { Console.WriteLine(x); } static void Main() { Test t; //Or Test t = new Test(); t.x = 10;//Inslization is mendatory t.Display(); } }
Struct example: When you call the constructor
new Test();
in a struct, it initializes the default values. If you do not call the constructor and only declare the struct instance without using thenew
keyword, likeTest t;
, you will get an error when trying to access its members.using System; struct Test { public int x; // Field needs to be public to be accessible public void Display() { Console.WriteLine(x); } static void Main() { // Case 1: Without using new keyword (will cause an error if we try to access members without initialization) Test t; // t.Display(); // This line will cause a compile-time error: "Use of unassigned local variable 't'" // Case 2: Using new keyword Test t2 = new Test(); t3.Display(); // Output: 0 // Case 3: Using new keyword Test t1; t1.x = 10; t1.Display(); // Output: 10 // Case 4: Using new keyword Test t4 = new Test(); t4.x = 10; // Initialization is mandatory t4.Display(); // Output: 10 } }
Note:If a structure contains any fields, you need to initialize those fields either by explicitly calling the default constructor using the
new
keyword, or by explicitly assigning values to the fields after creating the instance without usingnew
.For example:Using
new
keyword:Test t = new Test();
This initializes all fields to their default values.Without using
new
keyword:Test t;
You must manually initialize each field before accessing it:t.x = 10;
Whereas in the case of a structure, a parameterless or default constructor is always implicit and can't be defined explicitly again, we can only define a parameterized constructor.
struct Test { int x; // Field declaration // Parameterized constructor public Test(int x) { this.x = x; } // Method to display the value of x public void Display() { Console.WriteLine(x); } static void Main() { // Case 1: Creating an instance without using the 'new' keyword // We manually assign a value to the field 'x' Test t; t.x = 10; t.Display(); // Output: 10 (since we explicitly assigned 10 to t.x) // Case 2: Creating an instance using the 'new' keyword // This calls the default constructor, which initializes 'x' to its default value (0 for int) Test t1 = new Test(); t1.Display(); // Output: 0 (since the default constructor initializes 'x' to 0) // Case 3: Creating an instance using the parameterized constructor // This initializes 'x' with the provided value (e.g., 5) Test t2 = new Test(5); t2.Display(); // Output: 5 (since the parameterized constructor sets 'x' to 5) } }
In a structure, if we define "0" constructors, then after compilation there will be 1 constructor (implicit). If we define "n" constructors, after compilation there will be "n" + 1 constructors.
Structure can't be inherited by other Structures (Structures do not support inheritance).
Structure can also implement an interface.
Enumeration or Enum Types:
An enum is a user-defined type, so it is always better to define an enum directly under the namespace, but it is also possible to define an enum under a class or structure.
Characteristics:
Type Safety: Enums provide type safety, ensuring that only valid values (those defined in the enum) can be assigned to enum variables.
Value Type: Enums are value types, and their underlying type can be any integral type except
char
.
Syntax:
[<modifiers>] enum <Name> [: <type>]
{
//List of named constant values
}
Basic Example:
public enum Days
{
Monday, Tuesday, Wednesday, Thursday, Friday,Saturday, Sunday
}
Underlying Type:
By default, the underlying type of enum members is int
, and and the values start from 0 and increment by 1.
public enum Days
{
Monday, // Default value 0
Tuesday, // Default value 1
Wednesday, // Default value 2
Thursday, // Default value 3
Friday // Default value 4
}
You can specify a different underlying type and custom values.
Custom Underlying Type and Values:
public enum Days : byte
{
Monday = 1,
Tuesday = 2,
Wednesday = 3,
Thursday = 4,
Friday = 5,
Saturday = 6,
Sunday = 7
}
Enum supports only: byte, short, int, long, uint, ushort, ulong, and sbyte.
Supported underlying types:byte
,short
,int
,long
,uint
,ushort
,ulong
, andsbyte
.
Enum with Explicit Values Example:
public enum Days
{
Monday = 1,
Tuesday = 52,
Wednesday = 21,
Thursday = 41,
Friday = 18
}
Using Enums in Code:
//Declaring and Assigning Enum Variables:
Days today = Days.Monday;
//Printing Enum Values:
Console.WriteLine(today); // Output: Monday
//Getting the Underlying Value:
Console.WriteLine((byte)today); // Output: 1
//Type Casting:
Days tomorrow = (Days)2;
Console.WriteLine(tomorrow); // Output: Tuesday
// Get integer representation of enum value
Days d4 = Days.Friday;
Console.WriteLine((int)d4); // Output: 4 (default value)
// Working with custom enum values
CustomDays cd1 = CustomDays.Monday;
Console.WriteLine(cd1); // Output: Monday
CustomDays cd2 = (CustomDays)52;
Console.WriteLine(cd2); // Output: Tuesday
CustomDays cd3 = CustomDays.Wednesday;
Console.WriteLine(cd3); // Output: Wednesday
Console.WriteLine((int)cd3); // Output: 21 (custom value)
// Use foreach to get values
// `Enum.GetValues()` retrieves an array of the values in the enum
// `Enum.GetNames()` retrieves an array of the names in the enum
// `typeof` keyword is used to get the type of the enum
Console.WriteLine("Days Enum Values:");
foreach (int i in Enum.GetValues(typeof(Days)))
Console.WriteLine(i);
Console.WriteLine("Days Enum Names:");
foreach (string s in Enum.GetNames(typeof(Days)))
Console.WriteLine(s);
// Print name and values
Console.WriteLine("Days Enum Names and Values:");
foreach (int i in Enum.GetValues(typeof(Days)))
Console.WriteLine(i + " : " + (Days)i);
Working with Custom Enum Values:
public enum Days
{
Monday = 1,
Tuesday = 52,
Wednesday = 21,
Thursday = 41,
Friday = 18
}
class TestClass
{
static void Main()
{
Days cd1 = Days.Monday;
Console.WriteLine(cd1); // Output: Monday
Days cd2 = (Days)52;
Console.WriteLine(cd2); // Output: Tuesday
Days cd3 = Days.Wednesday;
Console.WriteLine(cd3); // Output: Wednesday
Console.WriteLine((byte)cd3); // Output: 21 (custom value)
// Use foreach to get values
Console.WriteLine("Days Enum Values:");
foreach (int i in Enum.GetValues(typeof(Days)))
Console.WriteLine(i);
Console.WriteLine("Days Enum Names:");
foreach (string s in Enum.GetNames(typeof(Days)))
Console.WriteLine(s);
// Print name and values
Console.WriteLine("Days Enum Names and Values:");
foreach (int i in Enum.GetValues(typeof(Days)))
Console.WriteLine(i + " : " + (Days)i);
}
}
public enum Days : byte
{
Monday = 1,
Tuesday = 52,
Wednesday = 21,
Thursday = 41,
Friday = 18
}
class TestClass
{
static void Main()
{
// Use foreach to get values
Console.WriteLine("Days Enum Values:");
foreach (int i in Enum.GetValues(typeof(Days))) //Give error because now enum is byte type
Console.WriteLine(i);
foreach (byte i in Enum.GetValues(typeof(Days))) //thats correct
Console.WriteLine(i);
Console.WriteLine("Days Enum Names:");
foreach (string s in Enum.GetNames(typeof(Days)))
Console.WriteLine(s);
}
}
Useing:
using System;
namespace Demo
{
// Define an enum named Days with specific byte values
public enum Days : byte
{
Monday = 1,
Tuesday = 52,
Wednesday = 21,
Thursday = 41,
Friday = 18
}
class TestClass
{
// Define a static property of type Days with a default value
public static Days MeetingDate
{
get; set; // get; set; are used to access and modify the value
} = 0; // Set a valid default value from the enum
// Define another static property with a default value cast from an integer
public static Days MeetingDate2
{
get; set; // get; set; are used to access and modify the value
} = (Days)52; // Casting 52 to Days, which corresponds to Days.Tuesday
// Define a static property with a default value directly set from the enum
public static Days MeetingDate3
{
get; set; // get; set; are used to access and modify the value
} = Days.Monday; // Set to a valid named constant from the enum
static void Main()
{
// Print the current values of the static properties
Console.WriteLine(MeetingDate); // Output: 0
Console.WriteLine(MeetingDate2); // Output: Tuesday
Console.WriteLine(MeetingDate3); // Output: Monday
// Change the meeting date:
// MeetingDate3 = "Saturday"; // Error: Cannot assign a string to a Days enum property.
// MeetingDate3 = Days.Saturday; // Error: 'Saturday' is not defined in the Days enum.
MeetingDate3 = Days.Friday; // Correct assignment, using a defined value in the enum
Console.WriteLine(MeetingDate3); // Output: Friday
}
}
}
If you set a default value for an enum property to
0
and0
is not defined in the enum, you will get0
as output.public enum Days : byte { Monday = 1, Tuesday = 52, Wednesday = 21, Thursday = 41, Friday = 18 } public static Days MeetingDate { get; set; } = 0; // Output is: 0
If
0
is defined in the enum (likeMonday = 0
or like only writtenMonday
not inslize), then the output will be the corresponding enum name.public enum Days : byte { Monday, //Or //Monday = 0, Tuesday = 52, Wednesday = 21, Thursday = 41, Friday = 18 } public static Days MeetingDate {get; set; } = 0; // Output is: Monday
If you assign a default value except
0
, it will cause a compile-time errorpublic enum Days : byte { Monday = 1, Tuesday = 52, Wednesday = 21, Thursday = 41, Friday = 18 } public static Days MeetingDate { get; set; } = 1; // Output: You got error
Key Points:
Type Safety: Enums ensure that only valid named constants are used, which makes the code more robust and less error-prone.
Readability: Using enums improves code readability by replacing numeric values with meaningful names.
Scope: Enums can be declared directly within a namespace, class, or struct.
Values: Enum members can have explicit values, and the underlying type can be specified.
Notes:
Default Values: If no underlying type is specified,
int
is used by default, starting from 0.Unique Values: Enum member names must be unique within the same scope.
Compatibility: Enums can be easily used with switch statements and for loops.
Sealed classes:
In C#, a sealed class is a class that cannot be inherited by other classes. This means that you cannot use a sealed class as a base class, ensuring that the class's behavior remains unchanged and that no further subclasses can alter its implementation.
When to Use Sealed Classes
Preventing Inheritance: If you want to ensure that a class's functionality is not extended or overridden, you can declare it as sealed.
Performance Optimization: Sealed classes can be more optimized at runtime because the compiler knows that the class won't have subclasses, allowing for more efficient method calls.
How to Define a Sealed Class
To declare a class as sealed, use the sealed
keyword in the class definition. Here's an example:
public sealed class FinalClass
{
public void DisplayMessage()
{
Console.WriteLine("This is a sealed class.");
}
}
In this example, FinalClass
is sealed, so no other class can inherit from it.
Example of Sealed Class Usage
public class BaseClass
{
public virtual void ShowMessage()
{
Console.WriteLine("BaseClass method");
}
}
public sealed class DerivedClass : BaseClass
{
public override void ShowMessage()
{
Console.WriteLine("DerivedClass method");
}
}
// The following code will result in a compile-time error because DerivedClass is sealed.
// public class AnotherClass : DerivedClass
// {
// }
class Program
{
static void Main()
{
DerivedClass derived = new DerivedClass();
derived.ShowMessage(); // Outputs: DerivedClass method
}
}
In this example:
BaseClass
is a normal class with a virtual methodShowMessage
.DerivedClass
inherits fromBaseClass
and overrides theShowMessage
method.DerivedClass
is marked as sealed, preventing any further inheritance from it.
Key Points
Sealed Classes Prevent Inheritance: Once a class is sealed, it cannot be used as a base class.
Use Cases: Sealed classes are often used when a class is designed to be immutable or when you want to provide a clear and unalterable implementation.
Performance: There can be slight performance benefits in terms of method invocation speed because the runtime doesn't need to check for potential overrides.
Sealed classes are a way to control the inheritance hierarchy and ensure that certain classes maintain a specific, unchangeable implementation.
Properties:
- Property is a member of class using to expose values associated with a class to the outside environment.
Way to access the fields value in diffrent class.
public class Circle//Circle represent is entity
{
double Radius = 12.35; //Radius represent is a attribute
}
public class Test
{
static void Main()
{
Circle cir = new Circle();
Console.WriteLine(cir.Radius);
//Error, you cannot access the Radius value. Because Radius is a private.
}
}
- If you make field as public, any one can access and modify value:
public class Circles//Circle represent is entity
{
public double Radius = 12.35; //Radius represent is a attribute
}
public class Test
{
static void Main()
{
Circles cir = new Circles();
Console.WriteLine(cir.Radius);//Get the old value. Out: 12.35
cir.Radius = 19.52;//Set the new value
Console.WriteLine(cir.Radius);//Get the new value. Out: 19.52
}
}
But the problom is any one can get the value and set the value.
But if I want to allow only one action, like either getting the value or setting the value, or both, you can't do that in the above example. The first thing you should do is never declare the variables or fields as public.Then we use method.
public class Circles
{
double Radius = 12.35;
//If you want to use ony giv the value then use only this method
public double GetRadius()//Use only get access
{
return Radius;
}
//If you want to use ony set the value then use only this method
public void SetRadius(double radius) //use only set value
{
Radius = radius;
}
}
public class Test
{
static void Main()
{
Circles cir = new Circles();
Console.WriteLine(cir.GetRadius());//Out: 12.35
double radius = 19.52;
cir.SetRadius(radius);
Console.WriteLine(cir.GetRadius());//Out: 19.52
}
}
Make more easy using Property:
Syntax:
[<modifiers>] <type> <Name>
{
[get {<stmt's>}] //Get Accessor
[set {<stmt's>}] //Set Accessor
}
Advantage:
Only one name is require.
No parameter require.
Only block we need.
Example:
public class Circles
{
double Radius = 12.35;
//Use property:
public double AccessRadius
{
get { return Radius; }
set { Radius = value; }//The value keyword represents the new value being assigned
}
}
public class Test
{
static void Main()
{
Circles cir = new Circles();
Console.WriteLine(cir.AccessRadius);//Out: 12.35
cir.AccessRadius = 19.52;//Hear we not use bracess '()'
Console.WriteLine(cir.AccessRadius);//Out: 12.35
}
}
If you use only get accesser then you can only get the property vlaue
get { return Radius; }
.get { return Radius; }
it represent value return method without parameter.If you use only set accesser then you can only set the property vlaue
set { Radius = value; }
.set { Radius = value; }
it represent a non-value return method with parameter.
Notes:common practice of naming convention
The name of the field and the property should not be the same because you cannot declare a class member with the same name. To make it clear and easier to understand you need the same name, then add an
_
underscore at the beginning of the field name. Giving different names to fields and properties can make you confuse in large programs.Example:
double _Radius = 12.35; public double Radius { get { return _Radius; } set { _Radius = value; } }
Add Condition in inside the geter and setter:
public class Circles
{
double _Radius = 12.35;
public double Radius
{
get { return _Radius; }
set
{
if(value > _Radius) _Radius = value;
}
}
}
If the condition is false in the getter or setter, the old value remains.
What is the difference between a public field and using both get and set accessors in a property: The difference is that with a public field, you cannot write conditions, but you can with a property.
We can also use access modifier with get and set access.Example:
// Property with different access levels public int Value { get { return _value; } internal set { _value = value; } }
Q: A class has one field, or you can say variable. And the class wants to give permission to class one class to change and modify the value, but class another should only be able to see the value.
Ans: You can use properties with different access modifiers to control the visibility and mutability of the field.
Class Circle have a private field that it wants to control.
Class A want to modify the value.
Class B want to only read the value.
using System; namespace Demo { public class Circles { double _Radius = 12.35; public double Radius { get { return _Radius; }//That one can access by any one protected set { _Radius = value; } //That one can modify by only own class or child class } } class A : Circles { public void Change() { Radius = 10; } } class B { public void View() { Circles cir = new Circles(); //cir.Radius = 10;//You got error Console.WriteLine("From B Class: "+ cir.Radius); } } class Test { static void Main() { Circles cir = new Circles(); Console.WriteLine("From Main Class: "+cir.Radius); //cir.Radius = 19.52;//GIve error you cannot change the value because seter is protected //Set the value:- A a = new A(); a.Change(); Console.WriteLine("From A class: "+a.Radius); B b = new B(); b.View(); } } } /*Out: From Main Class: 12.35 From A class: 10 From B Class: 12.35 */
Add condition in property:
public class Circles { double _Radius = 12.35; int _amount = 500; public double Radius { get { return _Radius; } set { if(_amount >= 500) _Radius = value; } } }
Q. Assume you have a class with a field that is a string, and it can only take six state names. Also, show how to ensure it only takes those six state names.
using System; namespace Demo { public class Circles { // Define a public enum so it can be accessed outside the class public enum StateName { Assam, Bihar, Chhattisgarh, Jharkhand, Odisha, Rajasthan } // Private field to store the state name private StateName _State = StateName.Chhattisgarh; // Public property to get and set the state name public StateName State { get { return _State; } set { _State = value; } } } class Test { static void Main() { Circles cs = new Circles(); Console.WriteLine(cs.State);//Out: Chhattisgarh //cs.State = cs.StateName.Assam;//you got error: cs.State = Circles.StateName.Assam;//Assign data Console.WriteLine(cs.State);//Out: Assam } } }
Why shood we use like that
Circles.StateName.Assam;
because enums are scoped by the type or namespace they are declared in. When you declare an enum inside a class, the enum type becomes part of that class's definition. Therefore, to access an enum value, you use the syntaxClassName.EnumType.EnumValue
.Enum Scope and Access: Enums are not instance members of a class; they are static members, meaning they belong to the type itself, not to any specific instance of the type. This is why you access them using the class name (
Circles
) rather than an instance name (cs
).Static Context: Since enums do not change per instance and are constant values associated with the type, they are accessed in a static context. Thus, you refer to them using the class name.
But if you use an enum outside the class, you can directly access and use it like this, but the enum should be in the same namespace:
using System; namespace Demo { // Define a public enum so it can be accessed outside the class public enum StateName { Assam, Bihar, Chhattisgarh, Jharkhand, Odisha, Rajasthan } public class Circles { // Private field to store the state name private StateName _State = StateName.Chhattisgarh; // Public property to get and set the state name public StateName State { get { return _State; } set { _State = value; } } } class Test { static void Main() { Circles cs = new Circles(); Console.WriteLine(cs.State);//Out: Chhattisgarh cs.State = StateName.Assam;//Assign data //StateName is a datatype, Assam is a value. Console.WriteLine(cs.State);//Out: Assam } } }
All value showing hear.
Auto-Implemented or Automatic Property:
Introduced in C# 3.0.
In C#, auto-implemented (or automatic) properties provide an easy way to declare properties without needing a separate field for storage. The compiler automatically creates a private, hidden field to hold the data, so you can just define the property's get and set accessors.
Syntax:
public class MyClass { public int MyProperty { get; set; } }
Implicit Backing Field: The compiler automatically creates a private field for the property. You can't access or change this field directly. It stores the property's value.
Default Implementation: If you don't need extra logic in the getter or setter, auto-implemented properties make the code simpler by removing the need for a separate field and manual property code.
Encapsulation: Even though you can't access the backing field directly, the property still provides encapsulation, letting you control how the data is accessed and changed.
Normal example:
using System; namespace Demo { public class Circles { public string State { get; set; } public Circles() { this.State = "Assam"; } } class Test { static void Main() { Circles cs = new Circles(); Console.WriteLine(cs.State);//Out: Assam cs.State = "Chhattisgarh"; Console.WriteLine(cs.State);//Out: Chhattisgarh } } }
Using Enum Example:
using System; namespace Demo { enum StateName { Assam, Bihar, Chhattisgarh, Jharkhand, Odisha, Rajasthan } public class Circles { public string State { get; set; } public Circles() { this.State = StateName.Assam.ToString(); } } class Test { static void Main() { Circles cs = new Circles(); Console.WriteLine(cs.State);//Out: Assam cs.State = StateName.Chhattisgarh.ToString(); Console.WriteLine(cs.State);//Out: Chhattisgarh } } }
StateName.Chhattisgarh
refers to a specific member of theStateName
enum.Integer Value: Each member of an enum has an integer value behind the scenes. By default, it starts at 0 and goes up by 1 for each next member, unless you set it differently. For example, in
StateName
,Assam
is 0,Bihar
is 1, and so on up toRajasthan
, which would be 5 if not specified otherwise.Enum to String Conversion: The
ToString()
method is called on the enum value (StateName.Assam.ToString()
) to convert the enum name to its string representation, which can be stored in theState
property.In auto-implemented (or automatic) properties before C# 3.0, both get and set were mandatory. But after C# 6.0, you can use only get, only set, or both.
Set default value:
// Auto-implemented property with a default value public string State { get; set; } = "Assam";
You cannot use any condition in Auto-implemented property.
A class can be considered an entity in object-oriented programming, and its fields (or properties) define the attributes of the objects created from the class.
INDEXERS:
Indexers in C# are a feature that allows an object to be indexed like an array. They provide a way to access elements in an object using an index, enabling syntax similar to accessing elements in an array or a list. Indexers use the this
keyword along with a parameter list to define the index and are equipped with get
and set
accessors to manage how values are retrieved or assigned.
An indexer allows you to access an object's properties like an array.
Syntax:
// Indexer declaration
[<modifiers>] <type> this[<parameter list>]
{
[get{<stmts>}] //Get Accessor
[set{<stmts>}] //Set Accessor
}
Example for get the value by indexers:
using System;
namespace Demo
{
public class Circles
{
int Eno;
double Salary;
string Ename, Job, Dname, Location;
// Constructor to initialize the fields
public Circles(int Eno, double Salary, string Ename, string Job, string Dname, string Location)
{
this.Eno = Eno;
this.Salary = Salary;
this.Ename = Ename;
this.Job = Job;
this.Dname = Dname;
this.Location = Location;
}
//if you write object as a type it can return any type of value
//this use to define current class
public object this[int index]//int index parameter come in inder form this is int
{
get
{
// Returns the appropriate field based on the index
if (index == 0) return Eno;
else if (index == 1) return Salary;
else if (index == 2) return Ename;
else if (index == 3) return Job;
else if (index == 4) return Dname;
else if (index == 5) return Location;
// Returns null if the index is out of range
return null;
}
}
}
class Test
{
static void Main()
{
// Create an instance of the Circles class with sample data
Circles cs = new Circles(101, 1000, "Mritunjay", "IT", "Manager", "Assam");
// Access and print properties using the indexer
Console.WriteLine("Eno: " + cs[0]); // Output: Eno: 101
Console.WriteLine("Salary: " + cs[1]); // Output: Salary: 1000
Console.WriteLine("Ename: " + cs[2]); // Output: Ename: Mritunjay
Console.WriteLine("Job: " + cs[3]); // Output: Job: IT
Console.WriteLine("Dname: " + cs[4]); // Output: Dname: Manager
Console.WriteLine("Location: " + cs[5]); // Output: Location: Assam
}
}
}
[int index]
:The parameter list for the indexer. In this case, it is a single parameter of typeint
, which represents the index used to access different values within the class.
Example for set the value by indexers:
using System;
namespace Demo
{
public class Circles
{
int Eno;
double Salary;
string Ename, Job, Dname, Location;
public Circles(int Eno, double Salary, string Ename, string Job, string Dname, string Location)
{
this.Eno = Eno;
this.Salary = Salary;
this.Ename = Ename;
this.Job = Job;
this.Dname = Dname;
this.Location = Location;
}
public object this[int index]
{
get
{
if (index == 0) return Eno;
else if(index == 1) return Salary;
else if(index == 2) return Ename;
else if(index == 3) return Job;
else if(index == 4) return Dname;
else if(index == 5) return Location;
else return null;// Returns null for unsupported indices
}
//'value' is a implicit variable, that provide access to the value assign by user
set
{
//if (index == 0) Eno = value;//If you assign only value you got error the error is object cant assign to integer. If you want to convert refrence type to value type you go to unboxing.
if (index == 0) Eno = (int)value;
else if (index == 1) Salary = (double)value;
else if (index == 2) Ename = (string)value;
else if (index == 3) Job = (string)value;
else if (index == 4) Dname = (string)value;
else if (index == 5) Location = (string)value;
}
}
}
class Test
{
static void Main()
{
Circles cs = new Circles(101, 1000.00, "Mritunjay", "IT", "Manager", "Assam");
Console.WriteLine("Eno: " + cs[0] + "Salary: " + cs[1] + "Ename: " + cs[2] + "Job: " + cs[3] + "Dname: " + cs[4] + "Location: " + cs[5]);
cs[0] = 102; cs[1] = 2000.00; cs[2] = "Amit"; cs[3] = "IT"; cs[4] = "Employe"; cs[5] = "Jharkhand";
Console.WriteLine("Eno: " + cs[0] + "Salary: " + cs[1] + "Ename: " + cs[2] + "Job: " + cs[3] + "Dname: " + cs[4] + "Location: " + cs[5]);
}
}
}
If Eno = value;
is used, you will get an error because you cannot assign an object to an integer. To convert a reference type to a value type, you need to use unboxing.
By defining the indexers, the array behaves like a virtual array.
Example send string in parameter:
using System;
namespace Demo
{
public class Circles
{
int Eno;
double Salary;
string Ename, Job, Dname, Location;
public Circles(int Eno, double Salary, string Ename, string Job, string Dname, string Location)
{
this.Eno = Eno;
this.Salary = Salary;
this.Ename = Ename;
this.Job = Job;
this.Dname = Dname;
this.Location = Location;
}
public object this[string name]
{
get
{
if (name.ToUpper() == "ENO") return Eno;
else if(name.ToUpper() == "SALARY") return Salary;
else if(name.ToUpper() == "ENAME") return Ename;
else if(name.ToUpper() == "JOB") return Job;
else if(name.ToUpper() == "DNAME") return Dname;
else if(name.ToUpper() == "LOCATION") return Location;
else return null;
}
set
{
if (name.ToUpper() == "ENO") Eno = (int)value;
else if (name.ToUpper() == "SALARY") Salary = (double)value;
else if (name.ToUpper() == "ENAME") Ename = (string)value;
else if (name.ToUpper() == "JOB") Job = (string)value;
else if (name.ToUpper() == "DNAME") Dname = (string)value;
else if (name.ToUpper() == "LOCATION") Location = (string)value;
}
}
}
class Test
{
static void Main()
{
Circles cs = new Circles(101, 1000.00, "Mritunjay", "IT", "Manager", "Assam");
Console.WriteLine("Eno: " + cs["Eno"] + "Salary: " + cs["Salary"] + "Ename: " + cs["Ename"] + "Job: " + cs["Job"] + "Dname: " + cs["Dname"] + "Location: " + cs["Location"]);
cs["Eno"] = 102; cs["Salary"] = 2000.00; cs["Ename"] = "Amit"; cs["Job"] = "IT"; cs["Dname"] = "Employe"; cs["Location"] = "Jharkhand";
}
}
}
Use
ToUpper()
because C# is a case-sensitive language. If you enter lowercase or any other case, it automatically converts to uppercase before comparing it. You can use another case also.
Example (use swith-case in indexers):
using System;
namespace Demo
{
public class Circles
{
int Eno;
double Salary;
string Ename, Job, Dname, Location;
public Circles(int Eno, double Salary, string Ename, string Job, string Dname, string Location)
{
this.Eno = Eno;
this.Salary = Salary;
this.Ename = Ename;
this.Job = Job;
this.Dname = Dname;
this.Location = Location;
}
// Indexer to get and set fields by index
public object this[int index]
{
get
{
return index switch
{
0 => Eno,
1 => Salary,
2 => Ename,
3 => Job,
4 => Dname,
5 => Location,
_ => null // Returns null for unsupported indices
};
}
set
{
switch (index)
{
case 0:
Eno = (int)value; // Cast to int
break;
case 1:
Salary = (double)value; // Cast to double
break;
case 2:
Ename = (string)value; // Cast to string
break;
case 3:
Job = (string)value; // Cast to string
break;
case 4:
Dname = (string)value; // Cast to string
break;
case 5:
Location = (string)value; // Cast to string
break;
default:
Console.WriteLine("Index out of range");
break;
}
}
}
}
class Test
{
static void Main()
{
Circles cs = new Circles(101, 1000, "Mritunjay", "IT", "Manager", "Assam");
Console.WriteLine("Eno: " + cs[0] + "Salary: " + cs[1] + "Ename: " + cs[2] + "Job: " + cs[3] + "Dname: " + cs[4] + "Location: " + cs[5]);
cs[0] = 102;
cs[1] = 2000.0; // Must be a double since Salary is a double
cs[2] = "Amit";cs[3] = "IT";cs[4] = "Employee";cs[5] = "Jharkhand";
cs[6] = "Out of range"; // This index is not supported and should trigger an out-of-range message
Console.WriteLine("Eno: " + cs[0] + "Salary: " + cs[1] + "Ename: " + cs[2] + "Job: " + cs[3] + "Dname: " + cs[4] + "Location: " + cs[5]);
}
}
}
Delegates:
Definition: It's a user-defined type that is a type-safe function pointer.
A delegate holds the reference of a method and then calls the method for execution.
We already have two ways to call a method; now, there is one more way to call a method, which is called delegates.
using System; namespace ModifiersTest1 { class Program2 { int Sum(int x, int y) { return x + y; } static string Name(string name) { return "Hello " + name; } static void Main(string[] args) { //1st way of calling the method by creating Object of class: Program2 program2 = new Program2(); int x = program2.Sum(10, 20); Console.WriteLine("sum is " + x); //2nd way of calling the method if method is static //string name = Name("Codecomponents"); //Or string name = Program2.Name("Codecomponents"); Console.WriteLine(name); } } }
Classes, structures, and interfaces are user-defined types, and a delegate is also a user-defined type. The main difference between a class and a structure is that a class is a reference type, while a structure is a value type. Similarly, a delegate is a reference type.
Typically, types like classes, structures, and interfaces are defined within a namespace because a namespace is a logical container for types. Similarly, a delegate is also defined within a namespace. You can also define it within a class, but then it is called a nested type. However, the best practice is to define a delegate within a namespace.
Syntax for defining a delegate:
[<modifiers>] delegate <void or type> <Name>([<parameter list>]);
Rools:
The return type of the delegate should match the method's return type.
The signature of the delegate should match the method's signature. The name can be anything, but the type should match.
After defining the delegate, two more steps are required: first, create an instance of the delegate; second, call the delegate.
Example:
using System;
namespace ModifiersTest1
{
//Definning the delegate:
public delegate int SumDelegate(int a, int b);
public delegate string NameDelegate(string name);
class Program2
{
int Sum(int x, int y)
{
return x + y;
}
static string Name(string name)
{
return "Hello " + name;
}
static void Main(string[] args)
{
Program2 program2 = new Program2();
//Create instance of delegate:
SumDelegate sumDelegate = new SumDelegate(program2.Sum);
NameDelegate nameDelegate = new NameDelegate(Name);
//Call the delegate by passing the parameter whic is required so that the internaly the method which is bind with delegate is call.
int x = sumDelegate(10, 5);
string s = nameDelegate("Codecomponents");
Console.WriteLine(s + " " + x);
//Or you can call like that
Console.WriteLine(nameDelegate.Invoke("Codecomponants") + " " + sumDelegate.Invoke(25, 5));
}
}
}
SumDelegate sumDelegate = new SumDelegate(program2.Sum);
in this line, the SumDelegate
delegate is holding the method reference of the program2.Sum
method.
SumDelegate sumDelegate = new SumDelegate(program2.Sum);
this delegate holds the reference to only one method. But a delegate can hold the multiple methods references, which is called a Multicast Delegate.
We can also hold the refrence in delegate like that:
using System;
namespace ModifiersTest1
{
public delegate double AddNums1Delegate(int x, float y, double z);
class Program2
{
public static double AddNums1(int x, float y, double z)
{
return x + y + z;
}
static void Main(string[] args)
{
AddNums1Delegate obj1 = AddNums1;
double ret = obj1.Invoke(100, 34.5f, 193.456);
}
}
}
Multicast Delegate:
A delegate can hold the reference of more than one method. And we call the method with the help of delegate.
If a class has multiple methods with the same signature and type, we can call all these methods with the same delegate.
using System;
namespace ModifiersTest1
{
public delegate void PrintName(string FName, string LName);
class Program2
{
public void Teacher(string FName, string LName)
{
Console.WriteLine("Teacher: " + FName + " " + LName);
}
public void Strudent(string FName, string LName)
{
Console.WriteLine("Student: " + FName + " " + LName);
}
static void Main(string[] args)
{
Program2 program2 = new Program2();
//1st way by multicast (One single delegate bind with two or more method)
PrintName obj = program2.Teacher;
obj += program2.Strudent;
obj.Invoke("Mritunjay", "Kumar");
/*Out:
* Teacher: Mritunjay Kumar
* Student: Mritunjay Kumar
*/
obj.Invoke("Rahul", "Kumar"); //Overide the delegate
/*Out:
* Teacher: Rahul Kumar
* Student: Rahul Kumar
*/
//----------------------------------------------
//2nd way that is not a multicast
PrintName teacher = program2.Teacher;
PrintName student = program2.Strudent;
teacher("Mritunjay", "Kumar"); //Out: Teacher: Mritunjay Kumar
student("Rahul", "Kumar");// Out: Student: Rahul Kumar
}
}
}
In any situation where multiple methods require the same signature with the same value, it is needed.
Anonymous Method:
A method doesn't have a name; it only uses the delegate
keyword. We can also add a signature without needing to define the type of method.
An anonymous method is defined using a delegate.
Example:
using System;
namespace ModifiersTest1
{
public delegate string PrintName(string FName, string LName);
class Program2
{
static void Main(string[] args)
{
PrintName pn = delegate (string FName, string LName)
{
return FName + " " + LName;
};//This method is an anonymous method
string name = pn.Invoke("Mritunjay", "Kumar");//Calling
Console.WriteLine(name);
}
}
}
Anonymous method not segested it's only segested when code voluem is less like 10 line 20 line.
Use of
+=
**with delegates:-**The+=
operator is used with delegates in C# to combine or chain multiple methods together. This allows multiple methods to be called in sequence when the delegate is invoked. When you use+=
, you're adding another method to the invocation list of the delegate.Here's a brief example to illustrate the use of
+=
with delegates:
using System; namespace ModifiersTest1 { public delegate void PrintName(string FName, string LName); class Program2 { static void Main(string[] args) { PrintName pn = delegate (string FName, string LName) { Console.WriteLine("First method: " + FName + " " + LName); }; // This method is an anonymous method // Using += to add another anonymous method to the delegate pn += delegate (string FName, string LName) { Console.WriteLine("Second method: " + FName.ToUpper() + " " + LName.ToUpper()); }; // Invoking the delegate, which will call both methods pn("Mritunjay", "Kumar"); } } }
Explanation:
Initial Delegate Assignment:
pn
is initially assigned an anonymous method that prints the full name in a normal format.Using
+=
to Add a Method: The+=
operator adds another anonymous method to the delegatepn
. This second method converts the first and last names to uppercase before printing them.Invocation of the Delegate: When
pn
is invoked with"Mritunjay", "Kumar"
, it sequentially calls both methods in the order they were added.Key Points:
The methods added to a delegate must have the same signature (return type and parameters) as the delegate type.
You can use the
+=
operator to combine methods and the-=
operator to remove them.When the delegate is invoked, all methods in its invocation list are called in the order they were added.
This feature is particularly useful in scenarios like event handling, where multiple event handlers might need to respond to a single event.
If you do not have any parameters for an anonymous method, use it like this:
using System;
namespace ModifiersTest1
{
public delegate void PrintName();
class Program2
{
static void Main(string[] args)
{
PrintName pn = delegate
{
Console.WriteLine("Hello world");
};
pn.Invoke();
}
}
}
Anonymous methods are useful if you don't want to write an access modifier, method name, or specify static.
In C# 2.0 anonymous method introduce.
Lambda Expressions:
A lambda expression is a shorthand for writing anonymous methods, simplifying their syntax.
Lambda expression come in C# 3.0.
Lambda operator ' =>
' use to make more simplifying the anonymous methods syntax.
using System;
namespace ModifiersTest1
{
public delegate string PrintName(string FName, string LName);
class Program2
{
static void Main(string[] args)
{
//Use lambda expression '=>':-
PrintName pn = (FName, LName) =>
{
return FName + " " + LName;
};
//You can also write like that:-
/*PrintName pn = (string FName, string LName) =>
{
return FName + " " + LName;
};*/
//But no need to write
string name = pn.Invoke("Mritunjay", "Kumar");
Console.WriteLine(name);
}
}
}
Predefine Generic Delegates:
First, understand this example, then we will move on to predefined generic delegates:-
using System;
namespace ModifiersTest1
{
public delegate double AddNums1Delegate(int x, float y, double z);
public delegate void AddNums2Delegate(int x, float y, double z);
public delegate bool CheakLengthDelegate(string s);
class Program2
{
public static double AddNums1(int x, float y, double z)
{
return x + y + z;
}
public static void AddNums2(int x, float y, double z)
{
Console.WriteLine(x + y + z);
}
public static bool CheakLength(string str)
{
if (str.Length > 5)
return true;
else
return false;
}
static void Main(string[] args)
{
AddNums1Delegate obj1 = AddNums1;
double result1 = obj1.Invoke(100, 34.5f, 193.465);
Console.WriteLine(result1);
AddNums2Delegate obj2 = AddNums2;
obj2.Invoke(100, 34.5f, 193.465);
CheakLengthDelegate obj3 = CheakLength;
bool result2 = obj3.Invoke("Hello World");
Console.WriteLine(result2);
}
}
}
C# provides three predefined generic delegates in the base class library: Func
, Action
, and Predicate
. These delegates simplify the usage of methods as parameters, especially when working with collections and LINQ.
Func Delegate: Used when the method has a return value. It can have up to 16 input parameters, with the last type parameter representing the return type.
Action Delegate: Used when the method does not return a value (i.e., it returns
void
). It can take up to 16 input parameters.Predicate Delegate: Specifically used for methods that return a
bool
. It is often used in scenarios where a condition needs to be checked.
Func delegate has 16 inputs and one output.
But if we need 3 input and one output parameter, then use the 3rd one:
Example
Below is an example illustrating how to use these predefined delegates:
using System;
namespace ModifiersTest1
{
class Program2
{
// Method matching Func<int, float, double, double> signature
public static double AddNums1(int x, float y, double z)
{
return x + y + z;
}
// Method matching Action<int, float, double> signature
public static void AddNums2(int x, float y, double z)
{
Console.WriteLine(x + y + z);
}
// Method matching Predicate<string> signature
public static bool CheckLength(string str)
{
return str.Length > 5;
}
static void Main(string[] args)
{
// Using Func delegate for a method that returns a double
Func<int, float, double, double> funcDelegate = AddNums1;
double result1 = funcDelegate.Invoke(100, 34.5f, 193.465);
Console.WriteLine(result1);
// Using Action delegate for a method that returns void
Action<int, float, double> actionDelegate = AddNums2;
actionDelegate.Invoke(100, 34.5f, 193.465);
// Using Predicate delegate for a method that returns bool
Predicate<string> predicateDelegate = CheckLength;
bool result2 = predicateDelegate.Invoke("Hello World");
Console.WriteLine(result2);
// Alternatively, using Func<string, bool> for boolean-returning methods
Func<string, bool> funcPredicate = CheckLength;
bool result3 = funcPredicate.Invoke("Mritunjay");
Console.WriteLine(result3);
}
}
}
Key Points:
Func Delegate: The last type parameter specifies the return type. In the example,
Func<int, float, double, double>
means the method takes two integers, one float, and one double, and returns a double.Action Delegate: Used for methods that don't return a value. In the example,
Action<int, float, double>
means the method takes two integers and one double, and doesn't return anything.Predicate Delegate: Specifically for methods that return a
bool
.Predicate<string>
means the method takes a string and returns a boolean.
These delegates are particularly useful for lambda expressions, LINQ queries, and event handling, where methods can be passed as parameters or returned as values.
We can still simplify the code here using an anonymous method. Let's see:
using System;
namespace ModifiersTest1
{
class Program2
{
static void Main(string[] args)
{
Func<int, float, double, double> obj1 = (x, y, z) =>
{
return x + y + z;
};
double result1 = obj1.Invoke(100, 34.5f, 193.465);
Console.WriteLine(result1);
Action<int, float, double> obj2 = (x, y, z)=>
{
Console.WriteLine(x + y + z);
};
obj2.Invoke(100, 34.5f, 193.465);
Predicate<string> obj3 = (str) =>
{
if (str.Length > 5)
return true;
else
return false;
};
bool result2 = obj3.Invoke("Hello World");
Console.WriteLine(result2);
}
}
}
Make more simple code:-
class Program2
{
static void Main(string[] args)
{
Func<int, float, double, double> obj1 = (x, y, z) =>x + y + z;
double result1 = obj1.Invoke(100, 34.5f, 193.465);
Console.WriteLine(result1);
Action<int, float, double> obj2 = (x, y, z)=>Console.WriteLine(x + y + z);
obj2.Invoke(100, 34.5f, 193.465);
Predicate<string> obj3 = (str) => str.Length > 5? true : false;
bool result2 = obj3.Invoke("Hello World");
Console.WriteLine(result2);
}
}
Extension Methods:
Extension methods, introduced in C# 3.0, let you add new methods to existing types without changing the original source code or creating a new derived type. This is helpful for extending classes, structures, or interfaces you can't modify, like those in third-party libraries or the .NET framework.
Key Points:
Enhancing Existing Types: Extension methods let you add capabilities to existing types without needing their source code. This is useful for adding methods to classes or structures defined elsewhere, like in third-party libraries or system assemblies.
Non-Inheritable Types: Unlike inheritance, which is limited to classes and not applicable to structures or sealed classes, extension methods can be applied to any type, including sealed classes and structures. This overcomes the limitations of inheritance, which cannot be used with these types.
No Source Code Modification: Extension methods don't require changes to the original source code of the type being extended. This keeps your code separate from third-party or framework code.
No Recompilation Needed: Using extension methods doesn't need the original type to be recompiled, making it a convenient and non-intrusive way to add functionality.
Example: Suppose you have sent the source code for testing, and the testing is complete. Later, you need to add a method to that source code. Modifying the code to add the method is not a good ideal because the code has already been tested. In such cases, extension methods are important because they allow you to add the method without modifying the original source code.
To create an extension method, define a static method in a static class.
Example:-
using System;
namespace ModifiersTest1
{
class Calculator
{
public void Sum()
{
Console.WriteLine("22 + 5 => 27");
}
}
static class Calculator2
{
/*Normal method static method
public static void Sub()
{
Console.WriteLine("22 - 5 => 17");
}*/
/*How to bind this Sub method with the Calculator class:
If the parameter is 'int x', it means this Sub method
wants an 'x' int value, just like 'Calculator c' in
this parameter 'c' is a Calculator type value.
public static void Sub(Calculator c)
{
Console.WriteLine("22 - 5 => 17");
}*/
/*To bind the Sub method with the Calculator class,
we need the this keyword. 'this' means the Sub
method belongs to the Calculator class.*/
public static void Sub(this Calculator c)
{
Console.WriteLine("22 - 5 => 17");
}
}
class Program2
{
static void Main(string[] args)
{
Calculator cal = new Calculator();
cal.Sum();
cal.Sub();
}
}
}
You can use this with any type, class, structure, etc.
public static void Sub(this Calculator c)
{
Console.WriteLine("22 - 5 => 17");
}
You might wonder how we can call the Sub
method using an instance of the class when it is a static method. This is because extension methods are defined as static, but once they are bound to a class or structure, they turn non-static methods.
If an extension method is defined with the same name and signature as an existing method in the class, the extension method will not be called. The original method will always take precedence.
using System;
namespace ModifiersTest1
{
class Calculator
{
public void Sum()
{
Console.WriteLine("22 + 5 => 27");
}
}
static class Calculator2
{
public static void Sum(this Calculator c)
{
Console.WriteLine("22 - 5 => 17");
}
}
class Program2
{
static void Main(string[] args)
{
Calculator cal = new Calculator();
cal.Sum();//Out:- 22 + 5 => 27
}
}
}
If the extension method has only one parameter like this Calculator c
in this public static void Sub(this Calculator c){}
, you don't need to pass any arguments when calling it. This parameter is used for binding. You can also add more parameters like this: public static void Sub(this Calculator c, int x, string s){}
. In this case, you must pass both an integer and a string when you call the Sub method.
In short, the parameter prefixed with the this
keyword is not considered. All other parameters will be considered.
An extension method should have one and only one binding parameter, and it should be the first in the parameter list.
If an extension method is defined with n parameters, then while calling it, there will be n-1 parameters only because the binding parameter is excluded.
Lets do with struct:
Int32
is a data type and also astruct type
. If you want to see it, check the definition ofInt32
. Let's add an extension method to find the factorial of a value inInt32
struct.using System; namespace ModifiersTest1 { static class Calculator2 { public static int Fact(this Int32 x) // `x` is a value which is come fron `int i = 5`; { if(x == 1) return 1; if(x == 2) return 2; else return x * Fact(x-1); } } class Program2 { static void Main(string[] args) { int i = 5; Console.WriteLine(i.Fact());//Out:- 120 } } }
Lets do with sealed classes:
string
is a data type and also a sealed class
. If you want to see it, check the definition of string
.
using System;
namespace ModifiersTest1
{
static class Calculator2
{
public static int Len(this String s) // `x` is a value which is come fron `int x = 10`;
{
return s.Length;
}
}
class Program2
{
static void Main(string[] args)
{
string name = "Mritunjay";
Console.WriteLine(name.Len());//Out:-9
//Or
Console.WriteLine("Mritunjay".Len());//Out:-9
}
}
}
No need to get the permission from any where for make extension method.
Diffrence bitwwen String & StringBuilder:
String:
Strings in C# are immutable, meaning their values cannot be modified after they are created.
When you declare a string, such as string s = "Hello";
, the value "Hello" is stored in memory. If you then try to change the string by using an operation like s = s + " world";
, it may seem like you're modifying the original string, but what's actually happening is that a new string object is created in memory to hold the new value "Hello world". The original string "Hello" remains unchanged, and the variable s
now points to the new string.
Each time you modify a string, a new copy is made, and this new string is stored in heap memory. This can lead to increased memory usage if many string modifications are made, as each modification results in a new string object being created.
This is a drawback when large-scale string manipulations and also performance or memory efficiency is a decrease.
String is recommended when you want a static string value and only need to make a few modifications; otherwise, it is not recommended.
For situations where you need to modify strings frequently, consider using the StringBuilder
class, which is designed to handle dynamic string content efficiently.
StringBuilder:
StringsBuilder is mutable, meaning their values can be modified after they are created.
syntax:
StringBuilder sb = new StringBuilder("Hello");
Here, you allocate 5 characters, but internally it gives you 16 characters of memory space. In this case, 11 character spaces are empty.
If you need more space, StringBuilder can automatically increase its capacity. For example, if you add more than 16 characters, it will automatically increase by another 16 characters. So, StringBuilder sb = new StringBuilder("Hello world America");
will take 32 character spaces because the 19 character is there so StringBuilder adds more space.
Notes:-
String present in
System
name space.StringBuilder present in
System.Text
name space.
Example show the diffrence which one take how much time:
using System;
using System.Text;
using System.Diagnostics;
namespace ModifiersTest1
{
class Program2
{
static void Main(string[] args)
{
string str1 = "Mritunjay";
Stopwatch sw1 = new Stopwatch(); //Use to measer the time to take to preform task come from `System.Diagnostics`
sw1.Start();
for (int i = 0; i < 100000; i++)
{
str1 = str1 + i;
}
sw1.Stop();
StringBuilder str2 = new StringBuilder("Hello");
Stopwatch sw2 = new Stopwatch();
sw2.Start();
for (int i = 0; i < 100000; i++)
{
str2.Append(i);
}
sw2.Stop();
Console.WriteLine("Time taken by String " + sw1.ElapsedMilliseconds);//Give the result in millisecond
Console.WriteLine("Time taken by StringBuilder " + sw2.ElapsedMilliseconds);//Give the result in millisecond
}
}
}
/*Out:-
Time taken by String 42313
Time taken by StringBuilder 11
*/
Multithreading enables concurrent execution of code, improving performance and responsiveness. Collections manage groups of objects, like arrays and lists. LINQ provides a query language for data manipulation, enhancing data retrieval and processing.
Multitherading:
Before we go to into multithreading, let's discuss multitasking:
Multitasking:
Windows OS is a multitasking system, allowing multiple tasks to run at the same time. But how do all these tasks run at the same time?
To execute multiple programs, the OS uses processes. A process is a part of the OS responsible for executing a program.
If you look at the task manager, you'll see many processes running. Each process executes one program.
There are also many background processes, which are essentially Windows services.
Besides these, multiple applications can be running, with each process running one application.
Operating System and Threads:
To run an application, a process uses threads.
A thread is the smallest unit of execution within a process.
Every application has logic to be executed, and threads are responsible for executing this logic.
Every application must have at least one thread to execute, which is known as the main thread. This means every application is by default a single-threaded model.
Main Thread:
Every application by default contains one thread to execute the program, known as the main thread.
By default, every program runs in a single-threaded model.
Multithreading in Detail:
Multithreading allows an application to perform multiple operations concurrently by creating multiple threads within the same process.
This can significantly improve the performance of an application, especially on multi-core processors where threads can run on different cores simultaneously.
Key Points
Processes: High-level entities used by the OS to manage the execution of programs.
Threads: Low-level units of execution within processes.
Single-threaded: By default, applications run on a single main thread.
Multi-threaded: Applications can create additional threads to run multiple tasks simultaneously.
Example 1: Single-threaded Program
using System;
class ThreadProgram
{
static void Main()
{
Console.WriteLine("Hello world");
}
}
// Output: Hello world
This program contains a thread, and with the help of this thread, the program executes. This thread is known as the main thread.
Example 2: Naming the Main Thread
using System;
using System.Threading;
class ThreadProgram
{
static void Main()
{
Thread t = Thread.CurrentThread;
t.Name = "MyThread";
Console.WriteLine("Now current thread is: " + t.Name);
}
}
// Output: Now current thread is: MyThread
In this example, we demonstrate that there is a thread running the program. Every program by default runs with a single thread.
Explanation
In the above examples:
Example 1: The program runs using the main thread by default.
Example 2: We explicitly name the main thread to show that the program runs on a single thread, which we've named "MyThread".
Single-threaded Disadvantage:
In a single-threaded program, all logic runs in one thread.
If your program contains multiple methods, and all methods are called by the main method, the main thread is responsible for executing all methods.
This means the main thread executes each method one by one, sequentially.
Example to Illustrate the Disadvantage:
using System;
class ThreadProgram
{
static void Main()
{
Method1();
Method2();
Method3();
}
static void Method1()
{
Console.WriteLine("Executing Method1");
}
static void Method2()
{
Console.WriteLine("Executing Method2");
}
static void Method3()
{
Console.WriteLine("Executing Method3");
}
}
// Output:
// Executing Method1
// Executing Method2
// Executing Method3
In this example:
The main thread calls
Method1
,Method2
, andMethod3
sequentially.The main thread is responsible for executing all methods, one after the other.
This sequential execution can be a disadvantage if you have tasks that could be performed concurrently to save time or improve performance.
Issue with Single-threaded Programs
In a single-threaded program, if any method takes a long time to finish, the whole program has to wait for that method before moving to the next task. This can waste time and resources. For example, if a method is waiting for a response from a busy database, the entire program is stuck waiting for that response.
using System;
class ThreadProgram
{
static void LongRunningMethod()
{
//Thread going to sleep
// Simulate a long-running task, such as a database call
Console.WriteLine("Starting LongRunningMethod...");
System.Threading.Thread.Sleep(5000); // main thread going to sleep 'Sleep is a ststic method'
Console.WriteLine("LongRunningMethod completed.");
}
static void Method2()
{
Console.WriteLine("Executing Method2");
}
static void Method3()
{
Console.WriteLine("Executing Method3");
}
static void Main()
{
//Main thread execution start
LongRunningMethod();
Method2();
Method3();
//Main thread execution end
}
}
// Output:
// Starting LongRunningMethod...
// (Waits for 5 seconds)
// LongRunningMethod completed.
// Executing Method2
// Executing Method3
To overcome this issue, we can use multiple threads to allow other parts of the program to continue running while waiting for the long-running method to complete. This is called Multithreading.
In multithreading, when we use multiple threads in a program, the OS distributes CPU time for each thread to execute. Based on time-sharing, all the threads execute equally. In the above example, if you apply multithreading, all methods execute together. Multithreading maximizes CPU resource utilization.
All the threads do not execute in parallel; they execute simultaneously. Where the OS allocates small time slices to each thread, allowing them to run as if they are executing at the same time.
Time-Sharing in Multithreading
Time-Sharing: The operating system splits the CPU time among all active threads. Each thread gets a small time slice, called a quantum, to run.
Thread Scheduling: The OS uses a scheduler to decide which thread runs at any moment. This makes sure all threads get a fair amount of CPU time.
Simultaneous Execution: Threads may not run in parallel (unless on a multi-core processor), but they switch quickly between each other, giving the impression of parallel execution.
How CPU Time Distribution Works
Imagine there are three methods (
Method1
,Method2
, andMethod3
) running on separate threads.The CPU gives a specific amount of time (e.g., 2 seconds) to each thread.
During its time, a thread runs its task. If it doesn't finish in that time, the CPU pauses it and moves to the next thread.
This process repeats, with the CPU cycling through the threads, giving each one time until all tasks are done.
The operating system, not the program, decides the length of the time slice.
This means all methods are given equal priority to execute.
using System;
using System.Threading;
class ThreadProgram
{
static void Method1()
{
for(int i= 1; i <= 20; i++)
{
Console.WriteLine("Test1: " + i);
}
}
static void Method2()
{
for(int i= 1; i <= 20; i++)
{
Console.WriteLine("Test2: " + i);
}
}
static void Method3()
{
for(int i= 1; i <= 20; i++)
{
Console.WriteLine("Test3: " + i);
}
}
static void Main()
{
// Create threads that will execute the methods
Thread T1 = new Thread(Method1);//Pass method name
Thread T2 = new Thread(Method2);
Thread T3 = new Thread(Method3);
// We do not need to call the methods explicitly because the threads will automatically call them.
// However, we need to start the threads to begin their execution.
T1.Start();
T2.Start();
T3.Start();
}
}
/*
CPU share the time: Som time Test1 start some time test2 or sum time test3 like sharing the time:-
Out:-
Test1: 1
Test3: 1
Test2: 1
Test1: 2
Test1: 3
Test1: 4
Test1: 5
Test1: 6
Test1: 7
Test1: 8
Test2: 2
Test2: 3
Test2: 4
Test2: 5
Test2: 6
Test2: 7
Test2: 8
Test2: 9
Test2: 10
Test2: 11
Test3: 2
Test3: 3
Test3: 4
Test3: 5
Test3: 6
Test3: 7
Test3: 8
Test3: 9
Test3: 10
Test3: 11
Test3: 12
Test3: 13
Test3: 14
Test3: 15
Test3: 16
Test3: 17
Test3: 18
Test3: 19
Test3: 20
Test2: 12
Test2: 13
Test2: 14
Test2: 15
Test2: 16
Test2: 17
Test1: 9
Test1: 10
Test1: 11
Test1: 12
Test1: 13
Test1: 14
Test1: 15
Test1: 16
Test1: 17
Test1: 18
Test1: 19
Test1: 20
Test2: 18
Test2: 19
Test2: 20
*/
- Every time you got diffrence output.
Explanation
Method Definitions:
Method1
,Method2
, andMethod3
are simple methods that print out "Test1", "Test2", and "Test3" respectively.Creating Threads:
Thread T1 = new Thread(Method1);
: This creates a new thread that will executeMethod1
.
Thread T2 = new Thread(Method2);
: This creates a new thread that will executeMethod2
.
Thread T3 = new Thread(Method3);
: This creates a new thread that will executeMethod3
.Starting Threads:
T1.Start();
: This starts the execution ofT1
, which will callMethod1
.
T2.Start();
: This starts the execution ofT2
, which will callMethod2
.
T3.Start();
: This starts the execution ofT3
, which will callMethod3
.Stopping the Thread:
- If you want to stop or terminate a thread, use the
Abort()
method. For example, to terminate theT1
thread, useT1.Abort();
.Key Points
Thread Creation: Threads are created by passing the method to be executed to the
Thread
constructor.Automatic Method Call: When you start a thread using the
Start
method, the thread will automatically call the method specified during its creation.Starting Threads: To begin the execution of the threads, you must call the
Start
method on each thread instance. This does not require you to call the methods directly; the threads handle that automatically.
One more example to better clarify that when something happens with one method, it does not affect another method.
using System;
using System.Threading;
class ThreadProgram
{
static void Method1()
{
for(int i= 1; i <= 20; i++)
{
Console.WriteLine("Test1: " + i);
if(i == 10)
{
Console.WriteLine("Thread 2 is going to sleep.");
Thread.Sleep(1000);
Console.WriteLine("Thread 2 is wokep now.");
}
}
}
static void Method2()
{
for(int i= 1; i <= 20; i++)
{
Console.WriteLine("Test2: " + i);
}
}
static void Method3()
{
for(int i= 1; i <= 20; i++)
{
Console.WriteLine("Test3: " + i);
}
}
static void Main()
{
Thread T1 = new Thread(Method1);
Thread T2 = new Thread(Method2);
Thread T3 = new Thread(Method3);
T1.Start();
T2.Start();
T3.Start();
}
}
/*Out:-
Test2: 1
Test3: 1
Test3: 2
Test3: 3
Test3: 4
Test3: 5
Test3: 6
Test3: 7
Test3: 8
Test3: 9
Test3: 10
Test3: 11
Test3: 12
Test3: 13
Test3: 14
Test1: 1
Test3: 15
Test3: 16
Test2: 2
Test2: 3
Test2: 4
Test2: 5
Test1: 2
Test2: 6
Test2: 7
Test2: 8
Test3: 17
Test3: 18
Test3: 19
Test3: 20
Test1: 3
Test1: 4
Test1: 5
Test1: 6
Test2: 9
Test2: 10
Test2: 11
Test2: 12
Test2: 13
Test2: 14
Test2: 15
Test2: 16
Test2: 17
Test2: 18
Test2: 19
Test2: 20
Test1: 7
Test1: 8
Test1: 9
Test1: 10
Thread 2 is going to sleep.
Thread 2 is wokep now.
Test1: 11
Test1: 12
Test1: 13
Test1: 14
Test1: 15
Test1: 16
Test1: 17
Test1: 18
Test1: 19
Test1: 20
*/
using System;
using System.Threading;
class ThreadProgram
{
static void Method1()
{
for(int i= 1; i <= 10; i++)
{
Console.WriteLine("Test1: " + i);
}
Console.WriteLine("Test1 thread execute");
}
static void Method2()
{
for(int i= 1; i <= 10; i++)
{
Console.WriteLine("Test2: " + i);
}
Console.WriteLine("Test2 thread execute");
}
static void Method3()
{
for(int i= 1; i <= 10; i++)
{
Console.WriteLine("Test3: " + i);
}
Console.WriteLine("Test3 thread execute");
}
static void Main()
{
Thread T1 = new Thread(Method1);
Thread T2 = new Thread(Method2);
Thread T3 = new Thread(Method3);
T1.Start();
T2.Start();
T3.Start();
Console.WriteLine("Main thread execute");
}
}
/*Out:-
Main thread execute
Test3: 1
Test1: 1
Test1: 2
Test1: 3
Test1: 4
Test1: 5
Test1: 6
Test1: 7
Test1: 8
Test1: 9
Test1: 10
Test1 thread execute
Test2: 1
Test3: 2
Test3: 3
Test2: 2
Test2: 3
Test2: 4
Test2: 5
Test2: 6
Test2: 7
Test2: 8
Test2: 9
Test2: 10
Test2 thread execute
Test3: 4
Test3: 5
Test3: 6
Test3: 7
Test3: 8
Test3: 9
Test3: 10
Test3 thread execute
*/
Execution Flow:
The Main
method is executed by the main thread of the application. The main thread executes first, and then the child threads start executing because the main thread's job is done.
Child Threads:
T1
, T2
, and T3
are started almost simultaneously. The operating system's scheduler manages the execution of these threads.
Constructor of the Thread Class:
In the Thread
class, there are four constructors available. Let's take a look at them, focusing on the ParameterizedThreadStart
and ThreadStart
delegates, and briefly mentioning the other two constructors.
Thread Class Constructors
Thread(ThreadStart start):
- Initializes a new instance of the
Thread
class, specifying a delegate that represents the method to be executed.
- Initializes a new instance of the
Thread(ParameterizedThreadStart start):
- Initializes a new instance of the
Thread
class, specifying a delegate that represents the method to be executed, and allowing an object to be passed to the method.
- Initializes a new instance of the
Thread(ThreadStart start, int maxStackSize):
- Initializes a new instance of the
Thread
class with a specified stack size.
- Initializes a new instance of the
Thread(ParameterizedThreadStart start, int maxStackSize):
- Initializes a new instance of the
Thread
class with a specified stack size, allowing an object to be passed to the method.
- Initializes a new instance of the
ThreadStart Delegate
ThreadStart
is a delegate, which is a type-safe function pointer that can be used to call a method. It is similar to function pointers in C++ but provides type safety by ensuring that the delegate's signature matches the method's signature.
Why Type-Safe Function Pointer?
A delegate is called a type-safe function pointer because the signature of the delegate must exactly match the signature of the method it calls. This ensures that the correct method is invoked without runtime errors due to signature mismatches. Like in the previous example, the signature of Method1 and the delegate are the same. This way, we ensure that Method1 is the target method.
Example of ThreadStart
Let's discuss ThreadStart
in detail:
If you goto the ThreadStart
definition, you will see that it is a delegate defined in the System.Threading
namespace.
In the previous example, the signature of the ThreadStart
delegate and the signature of Method1
are the same: both have a void
return type and no parameters.
Explanation of ThreadStart and Thread Initialization:-
When working with delegates, we follow three steps:
Define a delegate (in this case, it's already defined as
ThreadStart
).Instantiate the delegate (binding the method to the delegate).
Use the delegate.
Let's go through an example that demonstrates these steps.
Example Using ThreadStart Delegate:
using System;
using System.Threading;
namespace Multithreading
{
internal class ThreadProgram
{
static void Method1()
{
Console.WriteLine("Hello world");
}
static void Main(string[] args)
{
// Step 2: Instantiate the delegate (bind the method to the delegate)
ThreadStart ts = new ThreadStart(Method1); // The Method1 fulfills all criteria (bound to the delegate)
// Create a new thread and pass the ThreadStart delegate instance
Thread t = new Thread(ts); // Thread is taking ThreadStart delegate (delegate bound with Thread)
// Start the thread
t.Start();
}
}
}
// Output: Hello World
Difference Between the Two Examples:
Previous Example:
Thread T1 = new Thread(Method1);
T1.Start();
- Implicit Delegate Binding: In this example, the
Thread
class constructor implicitly creates an instance of theThreadStart
delegate and binds it toMethod1
. The runtime and framework handle this implicitly.
Current Example:
ThreadStart ts = new ThreadStart(Method1);
Thread t = new Thread(ts);
t.Start();
- Explicit Delegate Binding: In this example, we explicitly create an instance of the
ThreadStart
delegate and bind it toMethod1
. We then pass this delegate instance to theThread
constructor.
Clarification:
Thread t = new Thread(Method1);
This line implicitly passes the methodMethod1
as a parameter to the constructor of theThread
class, which expects aThreadStart
delegate. The Common Language Runtime (CLR) and framework handle the creation of theThreadStart
delegate instance internally.
Q :
Thread t = new Thread(Method1);
In this constructor, we cannot pass a method directly, but here we pass the method directly. How?Ans : The constructor
Thread t = new Thread(Method1);
does not directly take a method as a parameter. Instead, it implicitly or internally creates aThreadStart
delegate instance and binds it to the provided method. The CLR and framework handle this conversion internally.
These two code snippets are very much similar:
Thread T1 = new Thread(Method1);
andThreadStart ts = new ThreadStart(Method1); Thread t = new Thread(ts); t.Start();
There are multiple ways to initialize and start a thread using the ThreadStart
delegate in C#. Let's explore these different methods:
Directly Pass the Method Name: This directly assigns the method
Method1
to theThreadStart
delegate. The delegate automatically binds to the method that matches its signature.Using an Anonymous Method: This uses an anonymous method to bind the
ThreadStart
delegate to the logic ofMethod1
. It allows you to write the method logic directly within the delegate if needed.Using a Lambda Expression: This uses a lambda expression to bind the
ThreadStart
delegate toMethod1
. Lambda expressions provide a concise way to represent anonymous methods.
using System;
using System.Threading;
namespace Multithreading
{
internal class ThreadProgram
{
static void Method1()
{
Console.WriteLine("Hello world");
}
static void Main(string[] args)
{
// Option 1: Directly pass the method name
// ThreadStart ts = Method1;
// Option 2: Using an anonymous method
// ThreadStart ts = delegate { Method1(); };
// Option 3: Using a lambda expression
ThreadStart ts = () => Method1();
// Create a new thread and pass the ThreadStart delegate instance
Thread t = new Thread(ts);
// Start the thread
t.Start();
}
}
}
// Output: Hello World
Option 1 is the simplest and most direct way to bind a method to a delegate.
Option 2 provides flexibility to include logic directly within the delegate.
Option 3 offers a concise and modern approach to delegate binding using lambda expressions.
In all the above cases, the method Method1
does not have any parameters. But what happens if Method1
has parameters? In that case, ThreadStart
will not work. Instead, we use a parameterized thread class called ParameterizedThreadStart
.
ParameterizedThreadStart Delegate
The ParameterizedThreadStart
delegate is used to pass parameters to a thread method. This delegate takes an object
as a parameter, which can then be cast to the appropriate type within the method.
using System;
using System.Threading;
namespace Multithreading
{
internal class ThreadProgram
{
static void Method1(int i)
{
Console.WriteLine("Hello world " + i);
}
static void Method2(object num)
{
int i = Convert.ToInt32(num);
Console.WriteLine("Hello world " + i);
}
static void Main(string[] args)
{
// Using ParameterizedThreadStart delegate to specify the method to be executed by the thread
ParameterizedThreadStart ts = new ParameterizedThreadStart(Method2);
// Create a new thread and pass the ParameterizedThreadStart delegate instance
Thread t = new Thread(ts);
// Start the thread and pass a parameter
t.Start(50); // Output: Hello world 50
// However, passing an incorrect type can cause runtime errors
// The following line will cause a runtime error because Method2 expects an integer convertible object
t.Start("Hello"); // This will result in a runtime exception (FormatException)
// To make it type-safe, you can implement validation or use generic methods to ensure the correct type is passed
}
}
}
Key Points
ParameterizedThreadStart Delegate:
This delegate allows passing a parameter to the thread method.
It takes an
object
as a parameter, which can be cast to the required type within the method.Example Usage:
The
Method2
method takes anobject
parameter and converts it to anint
.The
Thread
class is instantiated with theParameterizedThreadStart
delegate.The
Start
method of the thread is used to pass the parameter.Type Safety:
Since
ParameterizedThreadStart
takes anobject
, it is not type-safe by default.Passing a wrong type, like a string when an integer is expected, will cause a runtime error (e.g.,
FormatException
).To ensure type safety, you can implement validation or use generic methods.
Join Method in Multitherading:
The Join
method, available in the Thread
class, is used to make the main thread wait until a specific thread finishes executing. This is useful to ensure that a particular thread completes its task before the calling thread continues.
Normal Thread Execution Without
Join
using System; using System.Threading; namespace Multithreading { internal class ThreadProgram { static void Method1() { Console.WriteLine("Method 1 thread start"); for (int i = 0; i < 5; i++) { Console.WriteLine("Method 1: " + i); } Console.WriteLine("Method 1 thread end"); } static void Method2() { Console.WriteLine("Method 2 thread start"); for (int i = 0; i < 5; i++) { Console.WriteLine("Method 2: " + i); } Console.WriteLine("Method 2 thread end"); } static void Method3() { Console.WriteLine("Method 3 thread start"); for (int i = 0; i < 5; i++) { Console.WriteLine("Method 3: " + i); } Console.WriteLine("Method 3 thread end"); } static void Main() { Console.WriteLine("Main thread start"); Thread t1 = new Thread(Method1); Thread t2 = new Thread(Method2); Thread t3 = new Thread(Method3); t1.Start(); t2.Start(); t3.Start(); Console.WriteLine("Main thread end"); } } } /* Possible Output (varies each time): Main thread start Method 1 thread start Method 1: 0 Method 2 thread start Method 2: 0 Method 2: 1 Method 2: 2 Method 2: 3 Method 2: 4 Method 2 thread end Main thread end Method 1: 1 Method 1: 2 Method 1: 3 Method 1: 4 Method 1 thread end Method 3 thread start Method 3: 0 Method 3: 1 Method 3: 2 Method 3: 3 Method 3: 4 Method 3 thread end */
In this example, the main thread starts and then gives control to threads 1, 2, and 3. Then the main thread exits. The problem is that the main thread should exit in the middle of the program. I don't want the main thread to exit until all threads finish. That's why we use the
join
method.Using
Join
to Wait for Thread Completion: To ensure that the main thread waits for the other threads to complete before exiting, we use theJoin
method:using System; using System.Threading; namespace Multithreading { internal class ThreadProgram { static void Method1() { Console.WriteLine("Method 1 thread start"); for (int i = 0; i < 5; i++) { Console.WriteLine("Method 1: " + i); } Console.WriteLine("Method 1 thread end"); } static void Method2() { Console.WriteLine("Method 2 thread start"); for (int i = 0; i < 5; i++) { Console.WriteLine("Method 2: " + i); } Console.WriteLine("Method 2 thread end"); } static void Method3() { Console.WriteLine("Method 3 thread start"); for (int i = 0; i < 5; i++) { Console.WriteLine("Method 3: " + i); } Console.WriteLine("Method 3 thread end"); } static void Main() { Console.WriteLine("Main thread start"); Thread t1 = new Thread(Method1); Thread t2 = new Thread(Method2); Thread t3 = new Thread(Method3); t1.Start(); t2.Start(); t3.Start(); t1.Join(); // Wait for t1 to finish t2.Join(); // Wait for t2 to finish t3.Join(); // Wait for t3 to finish Console.WriteLine("Main thread end"); } } } /* Output: Main thread start Method 1 thread start Method 1: 0 Method 1: 1 Method 1: 2 Method 1: 3 Method 1: 4 Method 1 thread end Method 2 thread start Method 2: 0 Method 2: 1 Method 2: 2 Method 2: 3 Method 2: 4 Method 2 thread end Method 3 thread start Method 3: 0 Method 3: 1 Method 3: 2 Method 3: 3 Method 3: 4 Method 3 thread end Main thread end */
Here, the
Join
method ensures that the main thread does not exit until all the threads (t1
,t2
, andt3
) have completed their execution. Generally, we use theJoin
method in the main method.Join
Method with Timeout: TheJoin
method is overloaded and can accept a timeout value, allowing the calling thread to wait for a specified amount of time for the thread to complete:using System; using System.Threading; namespace Multithreading { internal class ThreadProgram { static void Method1() { Console.WriteLine("Method 1 thread start"); for (int i = 0; i < 10; i++) { Console.WriteLine("Method 1: " + i); Thread.Sleep(200); } Console.WriteLine("Method 1 thread end"); } static void Method2() { Console.WriteLine("Method 2 thread start"); for (int i = 0; i < 10; i++) { Console.WriteLine("Method 2: " + i); } Console.WriteLine("Method 2 thread end"); } static void Method3() { Console.WriteLine("Method 3 thread start"); for (int i = 0; i < 10; i++) { Console.WriteLine("Method 3: " + i); } Console.WriteLine("Method 3 thread end"); } static void Main() { Console.WriteLine("Main thread start"); Thread t1 = new Thread(Method1); Thread t2 = new Thread(Method2); Thread t3 = new Thread(Method3); t1.Start(); t2.Start(); t3.Start(); t1.Join(1000); //1000 milliseconds: The main method waits for the t1 thread to finish in 1000 milliseconds. If t1 does not finish in 1000 milliseconds, the main thread will continue. t2.Join(); t3.Join(); Console.WriteLine("Main thread end"); } } } /*Out:- Main thread start Method 1 thread start Method 1: 0 Method 2 thread start Method 2: 0 Method 2: 1 Method 2: 2 Method 2: 3 Method 2: 4 Method 2: 5 Method 2: 6 Method 2: 7 Method 2: 8 Method 2: 9 Method 2 thread end Method 3 thread start Method 3: 0 Method 3: 1 Method 3: 2 Method 3: 3 Method 3: 4 Method 3: 5 Method 3: 6 Method 3: 7 Method 3: 8 Method 3: 9 Method 3 thread end Method 1: 1 Method 1: 2 Method 1: 3 Method 1: 4 Main thread end Method 1: 5 Method 1: 6 Method 1: 7 Method 1: 8 Method 1: 9 Method 1 thread end */
If
t1
does not complete within 1000 milliseconds, the main thread will continue its execution. However, sincet2
andt3
do not have a timeout specified, the main thread will wait until they finish.
Summary about JOIN method:
The
Join
method is used to make the calling thread wait until the specified thread finishes its execution.Using
Join
ensures that the main thread does not exit until other threads have completed, which is useful for synchronization.The
Join
method can also accept a timeout value, allowing the calling thread to wait for a specified amount of time for the thread to complete. If the thread does not complete within this time, the calling thread continues its execution.
ThreadLocking:
In all the above examples, we used static methods. Now, we will use non-static methods:
Context Switching: When you run multiple threads in your code, the operating system shares time among each thread and transfers control between them. This process is called context switching.
using System;
using System.Threading;
namespace Multithreading
{
internal class ThreadProgram
{
public void Method1()
{
Console.Write("My name is ");
Thread.Sleep(3000);
Console.WriteLine("Mritunjay kumar");
}
static void Main()
{
//Calling Method1 three times using an instance of the class
ThreadProgram obj = new ThreadProgram();
//Output 1 come reason:-
/*
obj.Method1();
obj.Method1();
obj.Method1();
*/
//Now use thread
//Calling Method1 three times using threads (In real-world projects, more than one thread may need to access resources like a database)
//Output 2 come reason:-
/*
Thread t1 = new Thread(obj.Method1);//Bind the non-static method with thread.
t1.Start();
*/
//Output 3 come reason:-
Thread t1 = new Thread(obj.Method1);
Thread t2 = new Thread(obj.Method1);
Thread t3 = new Thread(obj.Method1);
t1.Start();
t2.Start();
t3.Start();
}
}
}
/*Output 1:-
My name is Mritunjay kumar
My name is Mritunjay kumar
My name is Mritunjay kumar
*/
/*Output 2:-
My name is Mritunjay kumar
*/
/*Output 3:-
My name is My name is My name is Mritunjay kumar
Mritunjay kumar
Mritunjay kumar
*/
In Output 3, the output appears this way because we use three threads. Each thread starts executing and prints the first statement, then they sleep. After sleeping, they print the next statement. Due to context switching controlled by the OS, the output gets mixed.
What happens if this method works with a database? It can cause big problems. So, how do we resolve this? To resolve this problem, we use Thread Locking mechanism.
Handling Context Switching with Thread Locking Mechanism:
Thread locking is a way to make sure that only one thread can access a critical part of the code or a shared resource at a time. This stops race conditions and keeps data consistent by making other threads wait until the lock is released.
using System;
using System.Threading;
namespace Multithreading
{
internal class ThreadProgram
{
public void Method1()
{
lock (this)
{
Console.Write("My name is ");
Thread.Sleep(3000);
Console.WriteLine("Mritunjay kumar");
}
}
static void Main()
{
ThreadProgram obj = new ThreadProgram();
Thread t1 = new Thread(obj.Method1);
Thread t2 = new Thread(obj.Method1);
Thread t3 = new Thread(obj.Method1);
t1.Start();
t2.Start();
t3.Start();
}
}
}
/*Out:-
My name is Mritunjay kumar
My name is Mritunjay kumar
My name is Mritunjay kumar
*/
Locking:
The
lock
statement ensures that only one thread can enter the critical section of the code at a time.When a thread enters the
lock
statement, it acquires a lock onlockObj
, stopping other threads from entering the locked section until the lock is released.Example: Imagine three people using the same glass to drink water. When the first person drinks, the others wait. Once the first person finishes and leaves, the next person can drink. It means access is given to one thread at a time.
Note'
Context Switching: The OS shares CPU time among threads, leading to interleaved execution.
Thread Locking: Using
lock
ensures that only one thread accesses the critical section at a time, preventing issues with shared resources.Join Method: The
Join
method ensures that the calling thread waits for another thread to complete its execution.
Thread Priority:
What happens if one thread has more work to do compared to another thread? In that case, we can set the thread priority. Let's see how:
When one thread has more work to do compared to another thread, we can set the thread priority to manage CPU resource allocation more efficiently. The Priority
property in the Thread
class allows us to set the priority of a thread. This priority is defined under the ThreadPriority
enum, which has five levels:
Lowest
BelowNormal
Normal
AboveNormal
Highest
By default, every thread runs with a Normal
priority. According to the levels, the CPU resources used increase: Lowest
uses the least CPU resources, while Highest
uses the most.
Example with Same Priority:
using System;
using System.Threading;
namespace Multithreading
{
internal class ThreadProgram
{
static long count1, count2;
static void Method1()
{
while (true)
{
count1 += 1;
}
}
static void Method2()
{
while (true)
{
count2 += 1;
}
}
static void Main()
{
Thread t1 = new Thread(Method1);
Thread t2 = new Thread(Method2);
t1.Start();
t2.Start();
Thread.Sleep(10000); // Main thread sleeps for 10 seconds
// Terminate the threads
t1.Abort();
t2.Abort();
// Wait until the threads are fully terminated
t1.Join();
t2.Join();
Console.WriteLine("Count1: " + count1);
Console.WriteLine("Count2: " + count2);
}
}
}
/* Output:
Count1: 1170866865
Count2: 1171613038
*/
// Each time you get a different result.
In this example, both methods execute with the same priority (Normal
), so the results should be similar, but small differences will occur due to the operating system's scheduling. The OS decides which thread to run at any given time, resulting in varying counts for each run.
Thread priority only sets the order of importance, not the exact amount of CPU time a thread will receive.
Example with diffrence Priority:
using System;
using System.Threading;
namespace Multithreading
{
internal class ThreadProgram
{
static long count1, count2;
static void Method1()
{
while (true)
{
count1 += 1;
}
}
static void Method2()
{
while (true)
{
count2 += 1;
}
}
static void Main()
{
Thread t1 = new Thread(Method1);
Thread t2 = new Thread(Method2);
// Setting the priority
t1.Priority = ThreadPriority.Lowest;
t2.Priority = ThreadPriority.Highest;
t1.Start();
t2.Start();
Thread.Sleep(10000); // Main thread sleeps for 10 seconds
// Terminate the threads
t1.Abort();
t2.Abort();
// Wait until the threads are fully terminated
t1.Join();
t2.Join();
Console.WriteLine("Count1: " + count1);
Console.WriteLine("Count2: " + count2);
}
}
}
/* Output:
Count1: 343970565
Count2: 1349243933
*/
By setting the priority of t1
and t2
threads, the t1
thread uses less CPU resources, and the t2
thread uses more CPU resources. That way, the Count1
value is lower, and the Count2
value is higher.
Difference between single-threaded and multi-threaded model:
using System;
using System.Threading;
using System.Diagnostics; //Use to measurement of the time
namespace Multithreading
{
internal class ThreadProgram
{
static long count1, count2;
static void Method1()
{
for (int i = 0; i <= 10000000; i++)
{
count1 += 1;
};
}
static void Method2()
{
for (int i = 0; i <= 10000000; i++)
{
count2 += 1;
};
}
static void Main()
{
Stopwatch StopWatchByThread = new Stopwatch();
Stopwatch StopWatchWithoutThread = new Stopwatch();
Thread t1 = new Thread(Method1);
Thread t2 = new Thread(Method2);
StopWatchByThread.Start();
t1.Start(); t2.Start();
Console.WriteLine("Stop Watch By Thread: "+ StopWatchByThread.ElapsedMilliseconds + " Milliseconds");
StopWatchWithoutThread.Start();
Method1(); Method2();
Console.WriteLine("Stop Watch Without Thread: " + StopWatchByThread.ElapsedMilliseconds + " Milliseconds");
}
}
}
/* Output:
Stop Watch By Thread: 9 Milliseconds
Stop Watch Without Thread: 224 Milliseconds
*/
// Each time you get a different result.
In this example, Method1(); Method2();
uses only the main method thread, while t1.Start(); t2.Start();
uses multiple threads.
Collections:
Definition: A collection is a dynamic array that can automatically resize itself and manage its elements, providing greater flexibility compared to a traditional array.
Arrays vs. Collections:
Arrays:
Arrays have a fixed size. Once declared, their size cannot be changed.
You can resize an array using
Array.Resize
, but this creates a new array with the new size and destroys the old one.
Example: Resizing an Array
internal class Program
{
static void Main(string[] args)
{
int[] arr = new int[10];
Array.Resize(ref arr, 12); // This uses an output parameter (ref)
}
}
In this example, the old array is destroyed, and a new array with a size of 12 is created.
Limitations of Arrays:
The size of an array cannot be increased directly.
You cannot add a value in the middle of an array if it already contains values.
To add a new value, you need to increase the array's size, which involves creating a new array.
Similarly, you cannot delete a value in the middle of an array directly.
Collections:
Automatically increase its size when new values are added.
Insert and delete values in the middle of the collection.
Collections in .NET:
Non-Generic Collections
Generic Collections
STACK:
Stack stores values as LIFO (Last In First Out). It has a Push() method to add a value and Pop() & Peek() methods to get values.
Stack is a special collection that stores elements in LIFO style. C# has both generic and non-generic Stack classes. It's better to use the generic Stack collection.
Stack is useful for storing temporary data in LIFO style, and you might want to delete an element after getting its value.
Creating a stack:
Stack<int> myStack = new Stack<int>();
myStack.Push(1);
myStack.Push(2);
myStack.Push(3);
myStack.Push(4);
foreach (var item in myStack)
Console.Write(item + ","); //prints 4,3,2,1,
QUEUE:
Queue stores values in FIFO style (First In First Out). It keeps the order in which values were added.
It provides an Enqueue() method to add values and a Dequeue() method to get values from the collection.
Queue is a FIFO (First In First Out) collection.
It is part of the System.Collections.Generic namespace.
Queue can hold elements of a specified type. It provides compile-time type checking and avoids boxing-unboxing because it is generic.
Elements can be added using the Enqueue() method. You cannot use collection-initializer syntax.
Elements can be retrieved using the Dequeue() and Peek() methods. It does not support indexing.
Creating a Queue:
Queue<int> callerIds = new Queue<int>();
callerIds.Enqueue(1);
callerIds.Enqueue(2);
callerIds.Enqueue(3);
callerIds.Enqueue(4);
foreach(var id in callerIds)
Console.Write(id); //prints 1234
We do not use angular brackets <> when creating a non-generic collection. Example:
Generic collection:
ArrayList<int> arl=new ArrayList<int> ();
Non-Generic collection:
ArrayList arl=new ArrayList();
Non-Generic Collections:
Collections were introduced in .NET 1.0 as non-generic collections.
Examples of non-generic collections include
Stack
,Queue
,LinkedList
,SortedList
,ArrayList
, andHashtable
.These collections are implemented as classes and are defined in the
System.Collections
namespace.
In traditional programming languages like C++, you need to define and implement Stack
, Queue
, LinkedList
, SortedList
, ArrayList
, and Hashtable
data structures yourself. However, in .NET, they are provided as part of the framework in the form of classes within the System.Collections
namespace.
Summary
Arrays: Have fixed size, cannot be resized directly, and have limitations in inserting and deleting values.
Collections: Provide dynamic resizing, and the ability to insert and delete values easily.
Non-Generic Collections: Introduced in .NET 1.0, they include
Stack
,Queue
,LinkedList
,SortedList
,ArrayList
, andHashtable
, all defined in theSystem.Collections
namespace.Q. Diffrence bitween Array and ArrayList?
Ans:-
Array ArrayList Fixed Length Variable Length Not possible to insert items We can insert item into the middle Not posible to delete items We can delete items from the middle
ArrayList:
Using an ArrayList is similar to using an array:
Definition: An ArrayList
is a dynamic array that can automatically resize to fit new elements. Unlike traditional arrays, the size of an ArrayList
changes as elements are added or removed.
To use ArrayList
, you need to import the System.Collections
namespace:
Basic Example of UsingArrayList
:
using System;
using System.Collections;
namespace Collection
{
internal class Program
{
static void Main(string[] args)
{
ArrayList al = new ArrayList(); // Size dynamically increases as needed
al.Add(100); // Store any type of value in ArrayList at the last position
Console.WriteLine("Element added: " + al[0]);
}
}
}
al.Add(100)
stores a value in the first cell. You might wonder how many cells are in the ArrayList
. To understand this, we need to know about the property called capacity. The capacity is a property that tells us the number of items that can be stored in a collection.
Understanding Capacity:
The capacity of an ArrayList
is the number of elements it can hold before it needs to resize. The initial capacity can grow automatically as you add more elements.
namespace Collection
{
internal class Program
{
static void Main(string[] args)
{
ArrayList al = new ArrayList(); // Size dynamically increases as required
Console.WriteLine("Initial Capacity: " + al.Capacity); // Output: Initial Capacity: 0
al.Add(100); // Add an element
Console.WriteLine("Capacity after adding one element: " + al.Capacity); // Output: Capacity after adding one element: 4
}
}
}
When the initial capacity is filled, the ArrayList
doubles its capacity.
namespace Collection
{
internal class Program
{
static void Main(string[] args)
{
ArrayList al = new ArrayList(); // Size dynamically increases as required
Console.WriteLine("Initial Capacity: " + al.Capacity); // Output: Initial Capacity: 0
al.Add(100); al.Add(200); al.Add(300); al.Add(400);
Console.WriteLine("Capacity after adding four elements: " + al.Capacity); // Output: Capacity after adding four elements: 4
al.Add(500);
Console.WriteLine("Capacity after adding fifth element: " + al.Capacity); // Output: Capacity after adding fifth element: 8
}
}
}
If the 4 items fill the capacity, it becomes 8. If 8 is filled, the capacity changes to 16. If 16 is filled, the capacity changes to 32. This is how the capacity increases.
The special feature of ArrayList is that it resizes automatically. There is no limit.
Another constructor in ArrayList
is the parameterized constructor. You can also specify the size. If you pass the size, it means you can set the initial capacity.
Parameterized Constructor:
You can also initialize an ArrayList
with a specified initial capacity:
namespace Collection
{
internal class Program
{
static void Main(string[] args)
{
ArrayList al = new ArrayList(5); // Set initial capacity to 5
Console.WriteLine("Initial Capacity: " + al.Capacity); // Output: Initial Capacity: 5
al.Add(100); al.Add(200); al.Add(300); al.Add(400); al.Add(500);
Console.WriteLine("Capacity after adding five elements: " + al.Capacity); // Output: Capacity after adding five elements: 5
al.Add(600);
Console.WriteLine("Capacity after adding sixth element: " + al.Capacity); // Output: Capacity after adding sixth element: 10
}
}
}
Operations onArrayList
:
- Adding Elements:
al.Add(100); // Adds 100 to the end of the ArrayList
- Inserting Elements:
al.Insert(3, 350); // Inserts 350 at index 3
- Removing Elements:
al.Remove(350); // Removes the first occurrence of 350
al.RemoveAt(3); // Removes the element at index 3
- Printing Elements:
foreach (object o in al)
{
Console.Write(o + " "); // Prints all elements in the ArrayList
}
Console.WriteLine();
Complete Example:
using System;
using System.Collections;
namespace Collection
{
internal class Program
{
static void Main(string[] args)
{
ArrayList al = new ArrayList(5); // Set initial capacity to 5
Console.WriteLine("Initial Capacity: " + al.Capacity); // Output: 5
al.Add(100); al.Add(200); al.Add(300); al.Add(400); al.Add(500);
Console.WriteLine("Capacity after adding five elements: " + al.Capacity); // Output: 5
al.Add(600);
Console.WriteLine("Capacity after adding sixth element: " + al.Capacity); // Output: 10
// Print all elements
foreach (object o in al)
{
Console.Write(o + " "); // Output: 100 200 300 400 500 600
}
Console.WriteLine();
// Insert new item in the middle
al.Insert(3, 350);
foreach (object o in al)
{
Console.Write(o + " "); // Output: 100 200 300 350 400 500 600
}
Console.WriteLine();
// Remove the item
al.Remove(350);
foreach (object o in al)
{
Console.Write(o + " "); // Output: 100 200 300 400 500 600
}
Console.WriteLine();
// Remove by index position
al.RemoveAt(3);
foreach (object o in al)
{
Console.Write(o + " "); // Output: 100 200 300 500 600
}
Console.WriteLine();
}
}
}
This corrected explanation and examples should help you understand how to use ArrayList
in .NET effectively.
Hashtable:
When working with arrays and ArrayList
, you access elements using an index. This can be limiting when dealing with more complex data, like storing employee information (name, salary, position). In these cases, managing or remembering indexes can be difficult.
Problem with Arrays and ArrayLists:
Arrays and
ArrayList
use predefined indexes that cannot be changed, making it difficult to remember and manage the position of each element.For example, if you store employee data, it's tough to remember which index corresponds to which information (e.g., name, salary).
Solution: Hashtable
A Hashtable
addresses this issue by storing data in key-value pairs. This means you can define keys to make the data more readable and accessible.
Key Features of Hashtable:
Dynamic Size: Automatically resizes as needed. Similar to
ArrayList
, it can resize itself, and you can insert or remove values in the middle.Key-Value Pairs: Allows for storing data with user-defined keys, making it easier to access and manage data.
Example Usage:
using System;
using System.Collections;
namespace Collection
{
internal class Program
{
static void Main(string[] args)
{
// Create a Hashtable instance
Hashtable employeeData = new Hashtable();
// Add key-value pairs to the Hashtable
employeeData.Add("FName", "Mritunjay"); // Using the Add method
employeeData["LName"] = "Kumar"; // Using the indexer
employeeData["Salary"] = 50000;
employeeData["Position"] = "Software Developer";
// Access and print the values using keys
Console.WriteLine("Name: " + employeeData["FName"] + " " + employeeData["LName"]); // Output: Name: Mritunjay Kumar
Console.WriteLine("Salary: " + employeeData["Salary"]); // Output: Salary: 50000
Console.WriteLine("Position: " + employeeData["Position"]); // Output: Position: Software Developer
// Get all the keys from the Hashtable
foreach (object key in employeeData.Keys)
{
Console.Write(key + " ");
} // Output: FName LName Salary Position (Order is not same alwase order may vary due to hashing)
}
}
}
In .NET, every class by default contains four methods: GetHashCode()
, Equals()
, GetType()
, and ToString()
.
What is a Hashcode?
The
GetHashCode()
method returns a numeric representation of an object, known as a hash code. For example, if you writeConsole.WriteLine("Name".GetHashCode());
, it might return a numeric value like-694638
. This numeric value is called a hash code.Hash codes are consistent for the same value. For instance, calling
GetHashCode()
on the string "Name" will always return the same hash code.
Hashcode in Hashtable:
In a
Hashtable
, every item contains a key, a value, and a hash code. The key and value are objects, while the hash code is an integer.The
Hashtable
uses the hash code of the key to quickly locate the corresponding value, making data retrieval very efficient.
Why is Fetching Data Using Hashcode Efficient?
Hash codes allow for efficient data retrieval. Instead of searching through an array or
ArrayList
by index, aHashtable
can use the hash code to directly access the desired value.This is why
Hashtable
uses hash codes, as they enable fast and efficient data access compared to using indices in arrays orArrayList
.
Advantages of Hashtable:
Readability: Keys make the data more understandable (e.g., "Name" instead of an index).
Ease of Access: You can directly access data using keys without remembering indices.
Flexibility: Easily add, update, or remove key-value pairs as needed.
By using a Hashtable
, you can store and manage complex data more efficiently compared to arrays and ArrayList
.
Drawback of Non-Generic Collections
One of the main drawbacks of using non-generic collections like ArrayList
and Hashtable
is that they are not type-safe. This is because these collections store elements as objects, allowing any type of value to be added. For example:
internal class Program
{
static void Main(string[] args)
{
ArrayList al = new ArrayList();
al.Add(200);
al.Add("Mritunjay");
al.Add(true);
Console.WriteLine();
}
}
In the above example, the ArrayList
accepts all types of values (integer, string, boolean), which can lead to runtime errors and type casting issues.
Problem with Non-Generic Collections:
Lack of Type Safety: Since non-generic collections store values as objects, any type of value can be added. This can lead to runtime errors when the wrong type is retrieved or cast.
Potential Performance Issues: Storing values as objects requires boxing and unboxing for value types, which can degrade performance.
Difficult to Manage: When dealing with large collections of specific types, managing and ensuring the correct type can be cumbersome and error-prone.
Solution: Generic Collections
Generic Collections:
To overcome these limitations, .NET 2.0 introduced generic collections. Generic collections provide both strongly type safety and automatic resizing.
What is a Generic Collection?
A generic collection is a strongly-type-safe collection that allows you to specify the type of elements it can store. This ensures that only the specified type can be added to the collection, preventing runtime errors and improving performance by eliminating the need for boxing and unboxing.
How Type Safety Works in Generic Collections:
In generic collections, type safety is ensured by using type parameters. When you create a generic collection, you specify the type of elements it will store with the syntax List<T>
, where T
is the type. This ensures the collection can only store elements of that specified type.
Example of Type Safety in a Generic Collection
When you define a generic list with a specific type, the collection is restricted to store only that type of elements. For instance, List<int>
can only store integers. Attempting to store a different type will result in a compile-time error.
using System;
using System.Collections.Generic;
namespace CollectionExample
{
internal class Program
{
static void Main(string[] args)
{
// Creating a generic list of integers
List<int> intList = new List<int>();
// Adding integer values to the list
intList.Add(200);
intList.Add(300);
intList.Add(400);
// This line would cause a compile-time error because the list is type-safe
// intList.Add("Mritunjay");
// Displaying the values
foreach (int value in intList)
{
Console.WriteLine(value);
}
}
}
}
In the example above, intList
is a list that only accepts integers. If you try to add a string or any other type, the compiler will throw an error.
Using Complex Types in Generic Collections:
Generic collections are not limited to simple types like integers or strings. You can also use complex types, such as custom classes, as type parameters.
using System;
using System.Collections.Generic;
namespace CollectionExample
{
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public double Balance { get; set; }
}
internal class Program
{
static void Main(string[] args)
{
// Creating a generic list of Customer objects
List<Customer> customers = new List<Customer>();
// Adding Customer objects to the list
customers.Add(new Customer { Id = 1, Name = "John Doe", Balance = 1000.50 });
customers.Add(new Customer { Id = 2, Name = "Jane Smith", Balance = 2000.75 });
// Displaying the customer details
foreach (Customer customer in customers)
{
Console.WriteLine($"Id: {customer.Id}, Name: {customer.Name}, Balance: {customer.Balance}");
}
}
}
}
In the example above, customers
is a list that only accepts Customer
objects. This ensures type safety and makes the code more readable and maintainable.
We will discuss this line customers.Add(new Customer { Id = 1, Name = "John Doe", Balance = 1000.50 });
later.
System.Collections vs. System.Collections.Generic:
System.Collections: Contains non-generic collections like
ArrayList
andHashtable
. These collections can store any type of objects but lack type safety.System.Collections.Generic: Contains generic collections like
List<T>
,Dictionary<TKey, TValue>
,Queue<T>
, andStack<T>
. These collections provide type safety and eliminate the need for type casting.
Using generic collections, you can specify the type of elements the collection should store, ensuring that only that type is allowed. This enhances type safety and reduces the likelihood of runtime errors caused by incorrect type handling.
Using Generic Lists in Code:-
To use a generic list in your code, specify the type parameter when creating an instance of the List
class. This ensures that only the specified type can be stored in the list.
using System;
using System.Collections.Generic;
namespace Collection
{
class GenericList
{
static void Main()
{
List<int> list = new List<int>();//The bhebhier of this list class is exactly same as the behaviour of ArrayList in collection but diffrence is ArrayList store any type of value but List can store specific type of value
list.Add(10); list.Add(20); list.Add(30);
//list.Add(60.5)//Error come
list.Insert(2, 25);
list.Remove(2);
foreach (object o in list)
{
Console.Write(o + " ");
}
}
}
}
By using generic collections, you can leverage the benefits of type safety and automatic resizing, making your code more robust and easier to maintain.
Generics:
Earlier, we learned that in the ArrayList
, the Add
method accepts an Object
, but in List<T>
, the Add
method only accepts a particular type of value.
In C# 2.0, Microsoft introduced generics to address the limitations of collections like ArrayList
and methods that handle different data types. Generics improve type safety and performance by allowing you to define classes, methods, and interfaces with a placeholder for the type of data they store or use.
Problems with Non-Generic Code:
using System;
namespace Collection
{
internal class GenericTest
{
public bool Compare(int a, int b)
{
return a == b;
}
public bool Compare(float a, float b)
{
return a == b;
}
public bool Compare(object a, object b)
{
return a.Equals(b);
}
static void Main()
{
GenericTest obj = new GenericTest();
// Compare int
bool result = obj.Compare(10, 10);
Console.WriteLine(result); // Output: True
// Compare float
result = obj.Compare(10.56f, 10.56f);
Console.WriteLine(result); // Output: True
// Compare object
result = obj.Compare(10, 10);
Console.WriteLine(result); // Output: True
// Compare bool
result = obj.Compare(true, true);
Console.WriteLine(result); // Output: True
// Problem: Compare float and double
result = obj.Compare(10.56f, 10.56);
Console.WriteLine(result); // Output: False
}
}
}
In the code above, there are two main problems we see in result = obj.Compare(10.56f, 10.56);
:
Lack of Type Safety: The
Compare
method that takesobject
parameters can compare any types without type checking, leading to potential errors at runtime.Performance Issues: When we pass a value which is a
value type
(float and double) and theCompare
method takes anobject
, which is areference type
, it internally performs boxing to convert the value type to a reference type. Because of this, the boxing operation is performed internally. If we want to use a float value and a double value, we have to unbox them again. So, every time boxing and unboxing occur when we use this type of function, the performance of our program decreases.
To solve this problem, Microsoft introduced Generics in C# 2.0. With Generics, you can make methods type-safe and avoid using boxing and unboxing.
Example of Generic method:-
using System;
namespace Collection
{
internal class GenericTest
{
public bool Compare<T>(T a, T b)
{
return a.Equals(b);
}
static void Main()
{
GenericTest obj = new GenericTest();
// Compare float values
bool result = obj.Compare<float>(10.56f, 10.56f);
Console.WriteLine(result); // Output: True
}
}
}
Explanation:
Generic Method Definition:
public bool Compare<T>(T a, T b) { return a.Equals(b); }
- The method
Compare
is defined with a type parameterT
. This means the method can accept arguments of any type, as long as both arguments are of the same type.
- The method
Calling the Generic Method with Explicit Type Parameter:
GenericTest obj = new GenericTest(); bool result = obj.Compare<float>(10.56f, 10.56f); Console.WriteLine(result); // Output: True
- Here, the
Compare
method is explicitly called withfloat
as the type parameter. This ensures thatT
is set tofloat
.
- Here, the
Additional Examples with Different Types:
Compare
int
values:result =
obj.Compare
<int>(10, 10);
Compare
string
values:result =
obj.Compare
<string>("Hello", "Hello");
Type Safety:
The generic
Compare
method ensures type safety. If you try to pass arguments of different types, the compiler will throw an error. For example:// This will cause a compile-time error bool result = obj.Compare(10.56f, 10.56);
In this case, one argument is a
float
and the other is adouble
. The compiler will not allow this because it expects both arguments to be of the same type.
Avoiding Boxing and Unboxing:
Generics eliminate the need for boxing and unboxing. In non-generic collections or methods that use
object
, value types are boxed (converted toobject
) when added to the collection or passed as arguments, and unboxed (converted back to the original type) when retrieved. This can negatively impact performance.With generics, the actual type is used, avoiding the overhead of boxing and unboxing, thus improving performance
Using Generics:
Generics in C# allow for the creation of flexible, type-safe methods and classes, eliminating the need for boxing and unboxing, and improving performance. Let's explore how to use generics with methods and classes to perform.
Generic Methods:
Generics ensure type safety, but they don't directly support arithmetic operations because the type T
is unknown at compile time. To solve this, we can use the dynamic
type, which allows the type to be resolved at runtime.
using System;
namespace Collection
{
class GenericTest
{
public void Add<T>(T a, T b)
{
// Console.WriteLine(a + b); // This line would produce an error because at compile time, the types of 'a' and 'b' are unknown. To perform operations like addition, we need to use the 'dynamic' type to resolve the types at runtime.
dynamic d1 = a;
dynamic d2 = b;
Console.WriteLine(d1 + d2);
}
public void Sub<T>(T a, T b)
{
dynamic d1 = a;
dynamic d2 = b;
Console.WriteLine(d1 - d2);
}
public void Mul<T>(T a, T b)
{
dynamic d1 = a;
dynamic d2 = b;
Console.WriteLine(d1 * d2);
}
public void Div<T>(T a, T b)
{
dynamic d1 = a;
dynamic d2 = b;
Console.WriteLine(d1 / d2);
}
static void Main()
{
GenericTest obj = new GenericTest();
obj.Add<int>(10, 20);
obj.Sub<int>(10, 20);
obj.Mul<int>(10, 20);
obj.Div<int>(10, 20);
}
}
}
In the above example, dynamic
allows the Add
, Sub
, Mul
, and Div
methods to work with various data types at runtime, performing automatic type conversion. For instance, if a
and b
are integers, d1
and d2
will be treated as integers; if they are floats, d1
and d2
will be treated as floats, and so on.
Dynamic:
dynamic
is a new feature introduced in C# 4.0 that allows declaring a variable as dynamic, meaning its data type is identified at runtime. In C# 3.0, we havevar
, which is similar todynamic
, but withvar
, the data type is identified at compile time (var
identifies the data type at compile time, whiledynamic
identifies it at runtime). At runtime, if you pass anint
value toa
andb
, thend1
andd2
becomeint
. If you pass afloat
value, thend1
andd2
becomefloat
. If you pass adecimal
value, they automatically convert todecimal
. Automatic conversion happens.Understanding
var
anddynamic
:-In C#, the
dynamic
type was introduced in version 4.0, allowing you to declare variables whose type is determined at runtime. This is different fromvar
, introduced in C# 3.0, which determines the type at compile time.Key Differences Between
var
anddynamic
:
var
:
Determines the data type at compile time.
The compiler figures out the type from the assigned value.
Once assigned, the type cannot change.
dynamic
:
Determines the data type at runtime.
Allows more flexible code, as the type can change based on the assigned value.
Useful when the type is not known until runtime.
Summary:
var
: Used when the type is known at compile time. The type is inferred from the assigned value and cannot change.
dynamic
: Used when the type is not known until runtime. It allows the type to change based on the assigned value, providing greater flexibility for dynamic scenarios.
Generic Classes with Dynamic Typing:
We can also use generics at the class level, allowing us to specify the type once when creating an instance of the class. This avoids having to specify the type with each method call.
using System;
namespace Collection
{
class GenericTest2<T>
{
public void Add(T a, T b)
{
dynamic d1 = a;
dynamic d2 = b;
Console.WriteLine(d1 + d2);
}
public void Sub(T a, T b)
{
dynamic d1 = a;
dynamic d2 = b;
Console.WriteLine(d1 - d2);
}
public void Mul(T a, T b)
{
dynamic d1 = a;
dynamic d2 = b;
Console.WriteLine(d1 * d2);
}
public void Div(T a, T b)
{
dynamic d1 = a;
dynamic d2 = b;
Console.WriteLine(d1 / d2);
}
}
class Program
{
static void Main()
{
GenericTest2<int> obj = new GenericTest2<int>();
obj.Add(10, 20);
obj.Sub(10, 20);
obj.Mul(10, 20);
obj.Div(10, 20);
}
}
}
The advantage here is that you don't need to specify <T>
for each method; you specify at once when defining the class. This way, all methods in the class use the same type, making the code simpler and easier to read.
Summary:
Just as we use generics with collections like List<T>
, we can apply the same concept to our own classes. For example:
List<int> list = new List<int>();
We can also create an instance of a generic class:
GenericTest2<int> obj = new GenericTest2<int>();
In this case, because int
is specified, all methods in GenericTest2
will operate on integers. If we want to use floats, we can do:
GenericTest2<float> obj = new GenericTest2<float>();
Then all methods will operate on floats. The same applies if we use double
:
GenericTest2<double> obj = new GenericTest2<double>();
Whatever type is specified, all methods in the class will operate on that type. This pattern shows how generic collections and classes are implemented in .NET 2.0. The use of generics provides type safety, flexibility, and performance benefits, ensuring that all methods operate on the specified type and eliminating the need for type casting.
Using Dictionaries and Lists in Generic Collections:
In the previous lesson, we learned about the Hashtable, which stores key-value pairs. In generic collections, the Hashtable is replaced with the Dictionary. When creating a Dictionary, it takes two types: the first is KeyType and the second is ValueType. This is denoted as Dictionary<TKey, TValue>
. A List takes only one type, which is denoted as List<T>
.
Example:
using System;
using System.Collections.Generic;
namespace Collection
{
class DictionaryTest
{
static void Main()
{
Dictionary<string, object> dt = new Dictionary<string, object>();
// 'string' represents the key and 'object' represents the value
// Insert values
dt.Add("Name", "Mritunjay Kumar");
dt.Add("Job", "Manager");
dt.Add("Salary", 25000.00);
dt.Add("Age", 23);
dt.Add("Gender", 'M');
// Retrieve values
foreach (string key in dt.Keys)
{
Console.WriteLine(key + ": " + dt[key]);
}
}
}
}
Dictionaries store values in a sequence, whereas Hashtables do not store values in a sequence.
In the case of a generic collection, the type of values we want to store in the collections need not be predefined types only (like int
, float
, char
, string
, bool
, etc.). It can also be some user-defined type.
Example with a User-Defined Type:
using System;
using System.Collections.Generic;
namespace Collection
{
public class Customer
{
public int CustId { get; set; }
public string Name { get; set; }
public string City { get; set; }
public double Balance { get; set; }
}
class DictionaryTest
{
static void Main()
{
// In a list, we can do the following:
List<Customer> list = new List<Customer>();
// Create instances of Customer
Customer c1 = new Customer { CustId = 101, Name = "Mritunjay", City = "Hyderabad", Balance = 25000.00 };
Customer c2 = new Customer { CustId = 102, Name = "Sumit", City = "Delhi", Balance = 24000.00 };
Customer c3 = new Customer { CustId = 103, Name = "Rahul", City = "Chennai", Balance = 21000.00 };
// Add all instances to the list
list.Add(c1);
list.Add(c2);
list.Add(c3);
// Or we can store all three values at once
// list.AddRange(new Customer[] { c1, c2, c3 });
// Retrieve all values
foreach (Customer obj in list)
{
Console.WriteLine($"{obj.CustId} {obj.Name} {obj.City} {obj.Balance}");
}
// Output:
/*
101 Mritunjay Hyderabad 25000
102 Sumit Delhi 24000
103 Rahul Chennai 21000
*/
}
}
}
In a list, we can store not only predefined values but also user-defined values.
There are two interfaces generally used in collections:
IComparable
interfaceIComparer
interface
Example:
using System;
using System.Collections.Generic;
namespace Collection
{
public class Customer
{
public int CustId { get; set; }
public string Name { get; set; }
public string City { get; set; }
public double Balance { get; set; }
}
class DictionaryTest
{
static void Main()
{
// Create instances of Customer
Customer c1 = new Customer { CustId = 101, Name = "Mritunjay", City = "Hyderabad", Balance = 25000.00 };
Customer c2 = new Customer { CustId = 102, Name = "Sumit", City = "Delhi", Balance = 24000.00 };
Customer c3 = new Customer { CustId = 103, Name = "Rahul", City = "Chennai", Balance = 21000.00 };
Customer c4 = new Customer { CustId = 104, Name = "Amit", City = "Delhi", Balance = 21500.00 };
Customer c5 = new Customer { CustId = 105, Name = "Mohan", City = "Jharkhand", Balance = 21800.00 };
Customer c6 = new Customer { CustId = 106, Name = "Gudu", City = "Uttar Pradesh", Balance = 23800.00 };
// Assign all instances to a list
List<Customer> cus = new List<Customer>() { c1, c2, c3, c4, c5, c6 };
// Retrieve all values
foreach (Customer obj in cus)
{
Console.WriteLine($"{obj.CustId} {obj.Name} {obj.City} {obj.Balance}");
}
}
}
}
//Out:-
/*
101 Mritunjay Hyderabad 25000
102 Sumit Delhi 24000
103 Rahul Chennai 21000
104 Amit Delhi 21500
105 Mohan Jharkhand 21800
106 Gudu Uttar Pradesh 23800
*/
To sort the list, we use the Sort()
method like that:
using System;
using System.Collections.Generic;
namespace Collection
{
class GenericList
{
static void Main()
{
List<int> list = new List<int>();//The bhebhier of this list class is exactly same as the behaviour of ArrayList in collection but diffrence is ArrayList store any type of value but List can store specific type of value
list.Add(100); list.Add(40); list.Add(50);
list.Add(90); list.Add(30); list.Add(24);
foreach (object o in list)
{
Console.Write(o + " ");
}
Console.WriteLine();
list.Sort();
foreach (object o in list)
{
Console.Write(o + " ");
}
Console.ReadLine();
}
}
}
//Out:-
/*
100 40 50 90 30 24
24 30 40 50 90 100
*/
But, if you use the Sort()
method with List<Customer>
, you will get an error. This happens because Customer
is a complex type and the compiler is unsure of how to sort it (by CustId
, Name
, City
, or Balance
). To sort the data, we need to write the sorting logic inside the Customer
class using the IComparable<Customer>
interface and its CompareTo
method. And i also want to short by balance or any other value.
Short by balance:
using System;
using System.Collections.Generic;
namespace Collection
{
public class Customer : IComparable<Customer>
{
public int CustId { get; set; }
public string Name { get; set; }
public string City { get; set; }
public double Balance { get; set; }
public int CompareTo(Customer other)
{
// Sort based on Balance
if (this.Balance > other.Balance)
{
return 1; // If this instance's Balance is greater
}
else if (this.Balance < other.Balance)
{
return -1; // If this instance's Balance is less
}
else
{
return 0; // If both Balance are equal
}
}
}
class DictionaryTest
{
static void Main()
{
// Create instances of Customer
Customer c1 = new Customer { CustId = 101, Name = "Mritunjay", City = "Hyderabad", Balance = 25000.00 };
Customer c2 = new Customer { CustId = 102, Name = "Sumit", City = "Delhi", Balance = 24000.00 };
Customer c3 = new Customer { CustId = 103, Name = "Rahul", City = "Chennai", Balance = 21000.00 };
Customer c4 = new Customer { CustId = 104, Name = "Amit", City = "Delhi", Balance = 21500.00 };
Customer c5 = new Customer { CustId = 105, Name = "Mohan", City = "Jharkhand", Balance = 21800.00 };
Customer c6 = new Customer { CustId = 106, Name = "Gudu", City = "Uttar Pradesh", Balance = 23800.00 };
// Assign all instances to a list
List<Customer> cus = new List<Customer>() { c1, c2, c3, c4, c5, c6 };
// Sort the list
cus.Sort();
// Retrieve all values after sorting
foreach (Customer obj in cus)
{
Console.WriteLine($"{obj.CustId} {obj.Name} {obj.City} {obj.Balance}");
}
}
}
}
//Out:-
/*
103 Rahul Chennai 21000
104 Amit Delhi 21500
105 Mohan Jharkhand 21800
106 Gudu Uttar Pradesh 23800
102 Sumit Delhi 24000
101 Mritunjay Hyderabad 25000
*/
If you want to reverse the sort order, you only need to change the return values in the CompareTo
method:
Reverse using CustId:
public int CompareTo(Customer other)
{
if (this.CustId > other.CustId)
{
return -1; // Reverse the comparison
}
else if (this.CustId < other.CustId)
{
return 1; // Reverse the comparison
}
else
{
return 0;
}
}
//Out:-
/*
106 Gudu Uttar Pradesh 23800
105 Mohan Jharkhand 21800
104 Amit Delhi 21500
103 Rahul Chennai 21000
102 Sumit Delhi 24000
101 Mritunjay Hyderabad 25000
*/
But assume you don't have access to the Customer
class code and it sorts data based on CustId
, but you want to sort based on Balance
. In this case, you can create a new class that implements IComparer<Customer>
to achieve this. Let's see how:
Example with Custom Sorting:
using System;
using System.Collections.Generic;
namespace Collection
{
public class Customer : IComparable<Customer>
{
public int CustId { get; set; }
public string Name { get; set; }
public string City { get; set; }
public double Balance { get; set; }
public int CompareTo(Customer other)
{
// Sorting by CustId
if (this.CustId > other.CustId)
{
return 1;
}
else if (this.CustId < other.CustId)
{
return -1;
}
else
{
return 0;
}
}
}
class BalanceComparer : IComparer<Customer>
{
public int Compare(Customer x, Customer y) //These parameters represent the two Customer objects that need to be compared.
{
// Sorting by Balance
if (x.Balance > y.Balance)
{
return 1;
}
else if (x.Balance < y.Balance)
{
return -1;
}
else
{
return 0;
}
}
}
class DictionaryTest
{
static void Main()
{
// Create instances of Customer
Customer c1 = new Customer { CustId = 101, Name = "Mritunjay", City = "Hyderabad", Balance = 25000.00 };
Customer c2 = new Customer { CustId = 102, Name = "Sumit", City = "Delhi", Balance = 24000.00 };
Customer c3 = new Customer { CustId = 103, Name = "Rahul", City = "Chennai", Balance = 21000.00 };
Customer c4 = new Customer { CustId = 104, Name = "Amit", City = "Delhi", Balance = 21500.00 };
Customer c5 = new Customer { CustId = 105, Name = "Mohan", City = "Jharkhand", Balance = 21800.00 };
Customer c6 = new Customer { CustId = 106, Name = "Gudu", City = "Uttar Pradesh", Balance = 23800.00 };
// Assign all instances to a list
List<Customer> cus = new List<Customer>() { c1, c2, c3, c4, c5, c6 };
// Retrieve all values sorted by CustId
Console.WriteLine("Sorted by CustId:");
foreach (Customer obj in cus)
{
Console.WriteLine($"{obj.CustId} {obj.Name} {obj.City} {obj.Balance}");
}
Console.WriteLine();
BalanceComparer balanceComparer = new BalanceComparer();
cus.Sort(balanceComparer);
// Retrieve all values sorted by Balance
Console.WriteLine("Sorted by Balance:");
foreach (Customer obj in cus)
{
Console.WriteLine($"{obj.CustId} {obj.Name} {obj.City} {obj.Balance}");
}
}
}
}
Sorted by CustId:
101 Mritunjay Hyderabad 25000
102 Sumit Delhi 24000
103 Rahul Chennai 21000
104 Amit Delhi 21500
105 Mohan Jharkhand 21800
106 Gudu Uttar Pradesh 23800
Sorted by Balance:
103 Rahul Chennai 21000
104 Amit Delhi 21500
105 Mohan Jharkhand 21800
106 Gudu Uttar Pradesh 23800
102 Sumit Delhi 24000
101 Mritunjay Hyderabad 25000
The Sort()
method can work in two ways:
Sort() without parameters: This uses the default way of sorting provided in the
Customer
class, which sorts byCustId
because it implements theIComparable
interface.Sort(IComparer<T>) with a custom comparer: This uses a custom way of sorting, like sorting by
Balance
, provided by anIComparer
implementation. In our example, we use theBalanceComparer
class for this.IComparer
is a parameter.
This makes sorting flexible, allowing you to sort Customer
objects by different properties depending on what you need.
Now we have two options: Built-in Sorting and Custom Sorting.
Sort() Method Overloads:
The Sort()
method has four overloads:
Sort() without parameters:
- Uses the default way of sorting provided in the class, such as
Customer
, which sorts byCustId
because it implements theIComparable
interface. And this is also suitable for simple types likeint
,float
,double
, etc. Alwarady seen.
- Uses the default way of sorting provided in the class, such as
Sort(Comparison<T> comparison):
- Takes a
Comparison<T>
delegate as a parameter. This delegate represents the method that compares two objects of the same type. It's useful for defining custom sorting logic directly in place.
- Takes a
Sort(IComparer<T> comparer):
- Uses can short custom way, like sorting by
Balance
, provided by anIComparer<T>
implementation. In our example, we use theBalanceComparer
class for this.IComparer
is a parameter. Alwarady seen.
- Uses can short custom way, like sorting by
Sort(int index, int count, IComparer<T> comparer):
Sorts a range of elements in the list using the specified comparer. This allows you to specify which portion of the list to sort.
Example:
using System; using System.Collections.Generic; namespace Collection { public class Customer : IComparable<Customer> { public int CustId { get; set; } public string Name { get; set; } public string City { get; set; } public double Balance { get; set; } public int CompareTo(Customer other) { // Sorting by CustId if (this.CustId > other.CustId)return 1; else if (this.CustId < other.CustId) return -1; else return 0; } } class BalanceComparer : IComparer<Customer> { public int Compare(Customer x, Customer y) { // Sorting by Balance if (x.Balance > y.Balance) return 1; else if (x.Balance < y.Balance) return -1; else return 0; } } class DictionaryTest { static void Main() { // Create instances of Customer Customer c1 = new Customer { CustId = 101, Name = "Mritunjay", City = "Hyderabad", Balance = 25000.00 }; Customer c2 = new Customer { CustId = 102, Name = "Sumit", City = "Delhi", Balance = 24000.00 }; Customer c3 = new Customer { CustId = 103, Name = "Rahul", City = "Chennai", Balance = 21000.00 }; Customer c4 = new Customer { CustId = 104, Name = "Amit", City = "Delhi", Balance = 21500.00 }; Customer c5 = new Customer { CustId = 105, Name = "Mohan", City = "Jharkhand", Balance = 21800.00 }; Customer c6 = new Customer { CustId = 106, Name = "Gudu", City = "Uttar Pradesh", Balance = 23800.00 }; // Assign all instances to a list List<Customer> cus = new List<Customer>() { c1, c2, c3, c4, c5, c6 }; //Create instance BalanceComparer balanceComparer = new BalanceComparer(); cus.Sort(1, 4, balanceComparer); //Index start from '0' // Retrieve all values sorted by Balance Console.WriteLine("Sorted by Balance:"); foreach (Customer obj in cus) { Console.WriteLine($"{obj.CustId} {obj.Name} {obj.City} {obj.Balance}"); } } } }
Out:-
Sorted by Balance: 101 Mritunjay Hyderabad 25000 103 Rahul Chennai 21000 104 Amit Delhi 21500 105 Mohan Jharkhand 21800 102 Sumit Delhi 24000 106 Gudu Uttar Pradesh 23800
Remaining index numbers 0 and 5 are not included in sorting; the others are included in sorting by Balance.
Sort using Delegate2nd Sort
-> Sort(Comparison<T> comparison)
:
Comparison is a delegate and this method have must same signature return type is integer and take two parameter.
using System;
using System.Collections.Generic;
namespace Collection
{
public class Customer : IComparable<Customer>
{
public int CustId { get; set; }
public string Name { get; set; }
public string City { get; set; }
public double Balance { get; set; }
public int CompareTo(Customer other)
{
// Sorting by CustId
if (this.CustId > other.CustId)return 1;
else if (this.CustId < other.CustId) return -1;
else return 0;
}
}
class DictionaryTest
{
//Create method:
public static int ComparteByName(Customer x, Customer y)
{
return x.Name.CompareTo(y.Name);
}
static void Main()
{
Customer c1 = new Customer { CustId = 101, Name = "Mritunjay", City = "Hyderabad", Balance = 25000.00 };
Customer c2 = new Customer { CustId = 102, Name = "Sumit", City = "Delhi", Balance = 24000.00 };
Customer c3 = new Customer { CustId = 103, Name = "Rahul", City = "Chennai", Balance = 21000.00 };
Customer c4 = new Customer { CustId = 104, Name = "Amit", City = "Delhi", Balance = 21500.00 };
Customer c5 = new Customer { CustId = 105, Name = "Mohan", City = "Jharkhand", Balance = 21800.00 };
Customer c6 = new Customer { CustId = 106, Name = "Gudu", City = "Uttar Pradesh", Balance = 23800.00 };
List<Customer> cus = new List<Customer>() { c1, c2, c3, c4, c5, c6 };
//Pass 'ComparteByName' method in 'Comparison' delegant and also signature is matching both of them 'Comparison' delegant and 'ComparteByName' method
Comparison<Customer> compDele = new Comparison<Customer>(ComparteByName);
// Sort the list using the Comparison delegate
cus.Sort(compDele);
//Or
//cus.Sort(CompareByName);
foreach (Customer obj in cus)
{
Console.WriteLine($"{obj.CustId} {obj.Name} {obj.City} {obj.Balance}");
}
}
}
}
//Out:-
/*
104 Amit Delhi 21500
106 Gudu Uttar Pradesh 23800
105 Mohan Jharkhand 21800
101 Mritunjay Hyderabad 25000
103 Rahul Chennai 21000
102 Sumit Delhi 24000
*/
When using cus.Sort(CompareByName)
and cus.Sort(compDele)
, both achieve the same result, but in slightly different ways:
cus.Sort(CompareByName)
: Directly passes theCompareByName
method to theSort()
method. Since theCompareByName
method matches theComparison<Customer>
delegate signature, theSort()
method internally creates a delegate for you.cus.Sort(compDele)
: Explicitly uses aComparison<Customer>
delegate (compDele
) that was previously created using theCompareByName
method.
Using an anonymous method shortens the code:
using System;
using System.Collections.Generic;
namespace Collection
{
public class Customer : IComparable<Customer>
{
public int CustId { get; set; }
public string Name { get; set; }
public string City { get; set; }
public double Balance { get; set; }
public int CompareTo(Customer other)
{
// Sorting by CustId
if (this.CustId > other.CustId)return 1;
else if (this.CustId < other.CustId) return -1;
else return 0;
}
}
class DictionaryTest
{
static void Main()
{
Customer c1 = new Customer { CustId = 101, Name = "Mritunjay", City = "Hyderabad", Balance = 25000.00 };
Customer c2 = new Customer { CustId = 102, Name = "Sumit", City = "Delhi", Balance = 24000.00 };
Customer c3 = new Customer { CustId = 103, Name = "Rahul", City = "Chennai", Balance = 21000.00 };
Customer c4 = new Customer { CustId = 104, Name = "Amit", City = "Delhi", Balance = 21500.00 };
Customer c5 = new Customer { CustId = 105, Name = "Mohan", City = "Jharkhand", Balance = 21800.00 };
Customer c6 = new Customer { CustId = 106, Name = "Gudu", City = "Uttar Pradesh", Balance = 23800.00 };
List<Customer> cus = new List<Customer>() { c1, c2, c3, c4, c5, c6 };
// Sort the list using the Comparison delegate
cus.Sort(delegate (Customer x, Customer y)
{
return x.Name.CompareTo(y.Name);
});
foreach (Customer obj in cus)
{
Console.WriteLine($"{obj.CustId} {obj.Name} {obj.City} {obj.Balance}");
}
}
}
}
Using a lambda expression shortens the code cus.Sort((s1, s2) => s1.Name.CompareTo(s2.Name));
:
using System;
using System.Collections.Generic;
namespace Collection
{
public class Customer : IComparable<Customer>
{
public int CustId { get; set; }
public string Name { get; set; }
public string City { get; set; }
public double Balance { get; set; }
public int CompareTo(Customer other)
{
// Sorting by CustId
if (this.CustId > other.CustId)return 1;
else if (this.CustId < other.CustId) return -1;
else return 0;
}
}
class DictionaryTest
{
static void Main()
{
Customer c1 = new Customer { CustId = 101, Name = "Mritunjay", City = "Hyderabad", Balance = 25000.00 };
Customer c2 = new Customer { CustId = 102, Name = "Sumit", City = "Delhi", Balance = 24000.00 };
Customer c3 = new Customer { CustId = 103, Name = "Rahul", City = "Chennai", Balance = 21000.00 };
Customer c4 = new Customer { CustId = 104, Name = "Amit", City = "Delhi", Balance = 21500.00 };
Customer c5 = new Customer { CustId = 105, Name = "Mohan", City = "Jharkhand", Balance = 21800.00 };
Customer c6 = new Customer { CustId = 106, Name = "Gudu", City = "Uttar Pradesh", Balance = 23800.00 };
List<Customer> cus = new List<Customer>() { c1, c2, c3, c4, c5, c6 };
// Sort the list using the Comparison delegate
cus.Sort((s1, s2) => s1.Name.CompareTo(s2.Name));
foreach (Customer obj in cus)
{
Console.WriteLine($"{obj.CustId} {obj.Name} {obj.City} {obj.Balance}");
}
}
}
}
The
CompareTo
method for strings compares two strings to determine their order:
Comparison:
- Lexicographical Order: Strings are compared character by character based on their Unicode values (similar to alphabetical order).
Return Values:
Negative: If the first string comes before the second string.
Zero: If both strings are equal.
Positive: If the first string comes after the second string.
Example:
string str1 = "apple"; string str2 = "banana"; int result = str1.CompareTo(str2); // result will be negative because "apple" comes before "banana"
Summary:
CompareTo
for strings checks each character's Unicode value to sort them in alphabetical order.
IEnumerable Interface:
The IEnumerable
interface is the parent of all collection types.
IEnumerable Interface: This is the base interface for all non-generic collections. It defines a single method,
GetEnumerator()
, which returns anIEnumerator
.ICollection Interface: This inherits from
IEnumerable
and adds methods for size, enumerators, and synchronization.IList and IDictionary Interfaces: These inherit from
ICollection
.IList Interface: This represents collections of objects that can be individually accessed by index. Classes like
ArrayList
are part of this.IDictionary Interface: This represents a collection of key/value pairs. Classes like
Hashtable
andDictionary
are part of this.
Here's a simplified visual hierarchy:
IEnumerable
├── ICollection
│ ├── IList
│ │ └── ArrayList
│ └── IDictionary
│ ├── Hashtable
│ └── Dictionary
Inside the IEnumerable
interface ICollection
class is there, inside the ICollection
class IList
and IDictionary
class ther and all list
related class in inside the IList
class like ArrayList
and inside the IDictionary
class all class which take key
and value
pair like Hashtable
, Dictionary
are available.
In the previous examples, you were able to extract all values from lists and dictionaries because every collection inherits from the IEnumerable
interface. The IEnumerable
interface internally has a method called GetEnumerator()
. When a class implements IEnumerable
, it must also implement the GetEnumerator()
method, which is responsible for enabling the foreach
loop to iterate through the collection.
If you look at the definition of the List
class, you will see that it inherits from IEnumerable<T>
. The IEnumerable<T>
interface, in turn, inherits from the non-generic IEnumerable
. The IEnumerable
interface contains the GetEnumerator()
method. Without the GetEnumerator()
method, the foreach
loop would not work. This is why you can use foreach
with any collection that implements IEnumerable
.
Note's:
IEnumerable
is the base interface for all collections.
ICollection
adds size, enumerators, and synchronization methods.
IList
is for index-based collections.
IDictionary
is for key/value pair collections.The
GetEnumerator()
method inIEnumerable
enables theforeach
loop.Note's:
When you use a
foreach
loop, theforeach
loop internally calls theGetEnumerator
method to get anenumerator
. Andenumerator
is an object that enables iteration over a collection.enumerator
provides methods likeMoveNext
andReset
, and a property calledCurrent
. Enumerators come from collections that implement theIEnumerable
orIEnumerable<T>
interface (Enumerators are used with collections that haveIEnumerable
orIEnumerable<T>
.).
Example Code:
Here I create one class that works like a list:
Without IEnumerable
Implementation:
using System;
using System.Collections.Generic;
namespace Collection
{
public class Employee
{
public int CustId { get; set; }
public string Name { get; set; }
public string City { get; set; }
public double Balance { get; set; }
}
//Non-Generic
public class Orginization
{
//Hold the value we use Array:
List<Employee> Emps = new List<Employee>();
public void AddEmp(Employee emp)
{
Emps.Add(emp);
}
}
class IEnumerableTest
{
static void Main()
{
Orginization orgEmp = new Orginization();
orgEmp.AddEmp(new Employee { CustId = 101, Name = "Mritunjay", City = "Hyderabad", Balance = 25000.00 });
orgEmp.AddEmp(new Employee { CustId = 102, Name = "Sumit", City = "Delhi", Balance = 24000.00 });
orgEmp.AddEmp(new Employee { CustId = 103, Name = "Rahul", City = "Chennai", Balance = 21000.00 });
orgEmp.AddEmp(new Employee { CustId = 104, Name = "Amit", City = "Delhi", Balance = 21500.00 });
orgEmp.AddEmp(new Employee { CustId = 105, Name = "Mohan", City = "Jharkhand", Balance = 21800.00 });
orgEmp.AddEmp(new Employee { CustId = 106, Name = "Gudu", City = "Uttar Pradesh", Balance = 23800.00 });
foreach (Employee Emp in orgEmp)//Give error hear in 'orgEmp'
Console.WriteLine(Emp.CustId + " " + Emp.Name + " " + Emp.City + " " + Emp.Balance);
}
}
}
The above code will give an error: foreach statement cannot operate on variables of type 'Orginization' because 'Orginization' does not contain a public instance or extension definition for 'GetEnumerator'
.
Explain Method
AddEmp
:The method
AddEmp
in theOrginization
class is designed to add anEmployee
object to theEmps
list, which holds all theEmployee
objects.
public void AddEmp(Employee emp) { Emps.Add(emp); }
(Employee emp)
: The method takes one parameter, which is anEmployee
object namedemp
.
Emps.Add(emp);
: This line adds theEmployee
object passed as a parameter (emp
) to theEmps
list.Emps
is a list ofEmployee
objects, declared asList<Employee> Emps = new List<Employee>();
in theOrginization
class. TheAdd
method of theList<T>
class appends the specified element to the end of the list.Purpose: The
AddEmp
method provides a way to add newEmployee
objects to theOrginization
's internal list (Emps
). By encapsulating theAdd
functionality within this method,Explain List
List<Employee> Emps = new List<Employee>();
:
List<T>
: This is a generic collection class in theSystem.Collections.Generic
namespace. In this case,T
is replaced withEmployee
, soList<Employee>
is a list that holdsEmployee
objects.
List<Employee> Emps
:The variableEmps
is declared to hold a reference to aList<Employee>
object.
new List<Employee>()
creates a new, empty list ofEmployee
objects. At this point,Emps
is an empty list, ready to haveEmployee
objects added to it.new
is a keyword which used to create a new instance of an object.List<Employee>()
This is the constructor of theList<T>
class not the constructor of theEmployee
class, which initializes a new instance of the list.The
Employee
class constructor is not called innew List<Employee>()
statement. TheEmployee
class constructor will only be called when you create instances of theEmployee
class itself, such as when you add newEmployee
objects to the list.new List<Employee>()
is the constructor of theList<T>
class that is being called.The
Employee
class would be a user-defined class. Each instance of theEmployee
class represents a single employee with properties such asCustId
,Name
,City
, andBalance
.
new List<Employee>()
initializes a new list, and eachnew Employee { ... }
initializes new instances of theEmployee
class. TheEmployee
class constructor is called when creating newEmployee
objects, not when initializing the list.When you add
Employee
instances to this list, theEmployee
class constructor is called for each newEmployee
object. For example:Emps.Add(new Employee { CustId = 101, Name = "John Doe", City = "New York", Balance = 1000.0 });
In this linenew Employee { CustId = 101, Name = "John Doe", City = "New York", Balance = 1000.0 }
calls theEmployee
class constructor to create a new instance ofEmployee
and initialize it with the provided properties.
WithIEnumerable
Implementation:
To fix this, you need to inherit IEnumerable
and add the GetEnumerator
method in the Orginization
class:
using System;
using System.Collections.Generic;
using System.Collections;
namespace Collection
{
public class Employee
{
public int CustId { get; set; }
public string Name { get; set; }
public string City { get; set; }
public double Balance { get; set; }
}
//Non-Generic
public class Orginization : IEnumerable
{
List<Employee> Emps = new List<Employee>();
public void AddEmp(Employee emp)
{
Emps.Add(emp);
}
public IEnumerator GetEnumerator()
{
return Emps.GetEnumerator();//Return type is GetEnumerator
}
}//This class work like collection now for employes
class IEnumerableTest
{
static void Main()
{
Orginization orgEmp = new Orginization();
orgEmp.AddEmp(new Employee { CustId = 101, Name = "Mritunjay", City = "Hyderabad", Balance = 25000.00 });
orgEmp.AddEmp(new Employee { CustId = 102, Name = "Sumit", City = "Delhi", Balance = 24000.00 });
orgEmp.AddEmp(new Employee { CustId = 103, Name = "Rahul", City = "Chennai", Balance = 21000.00 });
orgEmp.AddEmp(new Employee { CustId = 104, Name = "Amit", City = "Delhi", Balance = 21500.00 });
orgEmp.AddEmp(new Employee { CustId = 105, Name = "Mohan", City = "Jharkhand", Balance = 21800.00 });
orgEmp.AddEmp(new Employee { CustId = 106, Name = "Gudu", City = "Uttar Pradesh", Balance = 23800.00 });
foreach (Employee Emp in orgEmp)
Console.WriteLine(Emp.CustId + " " + Emp.Name + " " + Emp.City + " " + Emp.Balance);
}
}
}
/*Out:-
101 Mritunjay Hyderabad 25000
102 Sumit Delhi 24000
103 Rahul Chennai 21000
104 Amit Delhi 21500
105 Mohan Jharkhand 21800
106 Gudu Uttar Pradesh 23800
*/
This implementation of
Orginization
class behave like a collection ofEmployee
objects for employees by implementing theIEnumerable
interface. This allows it to be used in aforeach
loop.IEnumerable Implementation: The
GetEnumerator()
method returns the enumerator of theEmps
list, allowing theOrginization
class to be used in aforeach
loop.
The
Orginization
objectorgEmp
is created.Several
Employee
objects are added toorgEmp
using theAddEmp
method.The
foreach
loop iterates overorgEmp
, printing the details of eachEmployee
.
The
GetEnumerator()
method is used to enable iteration over the collection. It provides the necessary functionality for theforeach
loop to iterate through the elements of theOrginization
class.Method Signature
public IEnumerator GetEnumerator()
: This indicates a public method that returns anIEnumerator
object. TheIEnumerator
interface provides the basic mechanisms for iterating over a collection.I told you earlier: When you use a
foreach
loop, theforeach
loop internally calls theGetEnumerator
method to get anenumerator
. Andenumerator
is an object that enables iteration over a collection.enumerator
provides methods likeMoveNext
andReset
, and a property calledCurrent
. Enumerators come from collections that implement theIEnumerable
orIEnumerable<T>
interface (Enumerators are used with collections that haveIEnumerable
orIEnumerable<T>
.).
return Emps.GetEnumerator();
:Emps
is aList<Employee>
. TheList<T>
class implementsIEnumerable<T>
, so it has aGetEnumerator
method that returns an enumerator. We also usedEmps.GetEnumerator();
. TheGetEnumerator
method of theList<T>
class is called, which returns an enumerator object that can iterate through the list of employees.In
foreach
loop callsorgEmp.GetEnumerator()
to get an enumerator. The enumerator is then used to iterate over eachEmployee
object in theEmps
list.
Custom Enumerator Implementation:
If you do not want to return the List
class GetEnumerator
, you can create your own enumerator:
public IEnumerator GetEnumerator()
{
throw new NotImplementedException();
}
return the GetEnumerator
:
public IEnumerator GetEnumerator()
{
return Emps.GetEnumerator();//Return type is GetEnumerator
}
If you do not want to return the List
class GetEnumerator
, you can create your own enumerator:
using System;
using System.Collections;
using System.Collections.Generic;
namespace Collection
{
public class Employee
{
public int CustId { get; set; }
public string Name { get; set; }
public string City { get; set; }
public double Balance { get; set; }
}
public class Orginization : IEnumerable
{
List<Employee> Emps = new List<Employee>();
public void AddEmp(Employee emp)
{
Emps.Add(emp);
}
public IEnumerator GetEnumerator()
{
return new OrginizationEnumerator(this);
}
public int Count => Emps.Count;
public Employee this[int index] => Emps[index];
}
public class OrginizationEnumerator : IEnumerator
{
Orginization orgColl;
int currentIndex;
Employee currentEmployee;
public OrginizationEnumerator(Orginization org)
{
orgColl = org;
currentIndex = -1;
}
public object Current => currentEmployee;
public bool MoveNext()
{
if (++currentIndex >= orgColl.Count)
return false;
else
{
currentEmployee = orgColl[currentIndex]; //Set the index
return true;
}
}
public void Reset()
{
currentIndex = -1;
}
}
class IEnumerableTest
{
static void Main()
{
Orginization orgEmp = new Orginization();
orgEmp.AddEmp(new Employee { CustId = 101, Name = "Mritunjay", City = "Hyderabad", Balance = 25000.00 });
orgEmp.AddEmp(new Employee { CustId = 102, Name = "Sumit", City = "Delhi", Balance = 24000.00 });
orgEmp.AddEmp(new Employee { CustId = 103, Name = "Rahul", City = "Chennai", Balance = 21000.00 });
orgEmp.AddEmp(new Employee { CustId = 104, Name = "Amit", City = "Delhi", Balance = 21500.00 });
orgEmp.AddEmp(new Employee { CustId = 105, Name = "Mohan", City = "Jharkhand", Balance = 21800.00 });
orgEmp.AddEmp(new Employee { CustId = 106, Name = "Gudu", City = "Uttar Pradesh", Balance = 23800.00 });
foreach (Employee Emp in orgEmp)
Console.WriteLine($"{Emp.CustId} {Emp.Name} {Emp.City} {Emp.Balance}");
}
}
}
/*Out:-
101 Mritunjay Hyderabad 25000
102 Sumit Delhi 24000
103 Rahul Chennai 21000
104 Amit Delhi 21500
105 Mohan Jharkhand 21800
106 Gudu Uttar Pradesh 23800
*/
Purpose of
OrginizationEnumerator
class: TheOrginizationEnumerator
class is an implementation of theIEnumerator
interface. It is designed to iterate over a collection ofEmployee
objects contained in anOrginization
class.Fields:
orgColl
: Holds a reference to theOrginization
instance being enumerated.
currentIndex
: Keeps track of the current position in the collection. Initialized to-1
to start before the first element.
currentEmployee
: Holds the currentEmployee
object during iteration.Constructor:
public OrginizationEnumerator(Orginization org) { orgColl = org; currentIndex = -1; }
Initializes
orgColl
with the givenOrginization
object.Index start from
0
. SetscurrentIndex
to-1
, meaning the enumerator starts before the first element.Method:
MoveNext()
:
public bool MoveNext() { if (++currentIndex >= orgColl.Count) return false; else { currentEmployee = orgColl[currentIndex]; return true; } }
Moves the
enumerator
to the next element.Increases
currentIndex
.
if (++currentIndex >= orgColl.Count) return false;
:IfcurrentIndex
is equal to or more than the total number of elements, it returnsfalse
, meaning the end of the collection.
currentEmployee = orgColl[currentIndex];
: it updatescurrentEmployee
with the new element and returnstrue
.Property:
Current
:public object Current => currentEmployee;
Returns the currentEmployee
object. It is typed asobject
to match theIEnumerator
interface. You can also write like that:
public object Current //Current is use to access the current record. { get{ return CurrectEmployee; } }
Method:
Reset()
:
public void Reset() { currentIndex = -1; }
Resets the enumerator to its initial state, before the first element. This is often used to restart enumeration. Actually, it's not required at that time; you can simply write it like this:
public void Reset(){}
.Why We Need
MoveNext()
,Reset()
, andCurrent
:
MoveNext()
,Reset()
, andCurrent
are part of theIEnumerator
interface, which is used for iterating over a collection.
MoveNext()
Method:
Purpose: Moves the enumerator to the next element in the collection.
What It Does: It increments the internal index and checks if it is still within the bounds of the collection. If it is, it updates the
Current
property to the new element and returnstrue
. If it’s out of bounds (i.e., past the last element), it returnsfalse
and the iteration ends.Why We Need It: This method is essential for advancing through the collection. The
foreach
loop callsMoveNext()
to progress to the next item.
Reset()
Method:
Purpose: Resets the enumerator to its initial position, before the first element.
What It Does: It sets the index back to -1 or the starting position. This allows for re-iteration or starting the iteration again from the beginning.
Why We Need It: While
Reset()
is less commonly used in typical iteration scenarios, it can be useful if you need to iterate over the collection again using the same enumerator.
Current
Property:
Purpose: Gets the current element in the collection.
What It Does: It provides access to the element at the current position of the enumerator.
Why We Need It: This property returns the element that the enumerator is currently pointing to. During iteration, it allows you to access the item that
MoveNext()
has moved to.Purpose of
GetEnumerator
Method: Allows theOrginization
class to be used withforeach
loops.
public IEnumerator GetEnumerator() { return new OrginizationEnumerator(this); }
Returns a new
OrginizationEnumerator
initialized with the current instance ofOrginization
. It means, whenGetEnumerator()
is called, it constructs a newOrginizationEnumerator
object. And this enumerator is initialized with the currentOrginization
instance, allowing it to access and iterate through theEmployee
objects in that specificOrginization
instance.The
foreach
loop callsGetEnumerator()
to get a newOrginizationEnumerator
object each time. Then uses this object to go through the items in theOrginization
collection.
new
Keyword: Thenew
keyword is used to create a new instance of a class or struct. This means it allocates memory for the new object and calls its constructor to set it up. In this case,new
is used to create a newOrginizationEnumerator
object. This new object will be in charge of going through the items in theOrginization
collection.
this
Keyword: Thethis
keyword refers to the current instance of the class where it is used. It provides a way to access the current object’s members (like methods and properties). It's often used to pass the current instance to other methods or constructors. Here,this
is used to pass the current instance of theOrginization
class to the constructor ofOrginizationEnumerator
. This allows the enumerator to access the collection of employees in theOrginization
class.Indexing and Counting:
Indexing and Counting
are features of theOrginization
class that enhance its functionality:
Count
Property:public int Count => Emps.Count;
Purpose: Provides the number of
Employee
items in theOrginization
.What It Does: Returns the count of items in the
Emps
list.Why We Need It: This property is useful for determining the number of elements in the collection, especially if you need to perform operations based on the size of the collection.
public int Count => Emps.Count; //Use to Counting //Or public int Count { get { return Emps.Count; } }
Indexer (
this[int index]
):public Employee this[int index] => Emps[index];
Purpose: Allows access to
Employee
objects in theOrginization
collection using an index, similar to array indexing.What It Does: Retrieves the
Employee
at the specified index from theEmps
list.Why We Need It: This indexer provides a way to access elements directly by index, making the
Orginization
class behave like an array or list. This can be useful for direct access to items without needing to iterate through the collection.
public Employee this[int index] => Emps[index]; //Use to Indexing //Or public Employee this[int index] { get{ return Emps[index]; } }
this: The keyword that indicates this is an indexer, not a regular property. It allows the class to be indexed.
[int index]: The parameter list for the indexer. It specifies that the indexer will take an integer parameter, which represents the index of the element to access.
Employee: The return type of the indexer. It specifies that the indexer will return an
Employee
object.Emps[index]: The body of the getter. It returns the
Employee
object at the specifiedindex
from the internalEmps
list.Use
Employee emp = orgEmp[0];
:here's what happens:
orgEmp[0]
: The indexer is called with the index0
.get { return Emps[0]; }: The getter of the indexer is executed, which returns
Emps[0]
.Emps[0]: This accesses the
Emps
list (which is aList<Employee>
) and retrieves theEmployee
object at index0
.
When a foreach
loop is used with an Organization
object, it looks for the GetEnumerator
method in the Organization
class. The Organization
class implements the IEnumerable
interface, which requires a GetEnumerator
method that returns an IEnumerator
. The IEnumerator
implementation is provided by the OrganizationEnumerator
class, where we define the logic for the Current
property and the MoveNext
method.
In that example, we have 4 classes:
Employee
classOrganization
class: This class works like a collection. We define theAddEmp
method to add employees, theCount
property to return the number of items, the indexer to return an item by index, and theGetEnumerator
method to enable the foreach loop. ThisGetEnumerator
method returns anIEnumerator
type, which is an interface. Next, we define anOrganizationEnumerator
class to implement theIEnumerator
interface.OrganizationEnumerator
class: This class inherits fromIEnumerator
. I define a constructor to access theOrganization
, and then I implement theCurrent
property and two methods,MoveNext
andReset
, although we do not useReset
.IEnumerableTest
class: This is the main class. Here, we use the main method to create an instance of theOrganization
class calledorgEmp
. Then, we add employees and use a foreach loop to get the data.
Q. What is
IEnumerable
,IEnumerator
andGetEnumerator
?Ans:-
IEnumerable
:IEnumerable
is an interface used by all collection classes. It includes a method calledGetEnumerator
. Because ofGetEnumerator
, we can use a foreach loop on the collection. If we want our classes to act like collections, we need to implement theGetEnumerator
method.
IEnumerator
: An interface that defines methods for iterating over a collection.MoveNext()
: Moves to the next item.Reset()
: Resets to the position before the first item.Current
: Gets the current item in the collection.
GetEnumerator
: A method fromIEnumerable
that returns anIEnumerator
. It allows us to use a foreach loop to iteration the collection.Q. CRUD operations using Collection (Generic Collection)?
Create a Product class
It has the following properties like Id, name, price
Create a collection of Product class
Add three products and display
We need to do the following operations
Add a New Product
Update an existing product price
Display product by id
Display all products
Delete a product by taking id
Ans:
LINQ(Language Integrated Query)
:
LINQ is a query language designed by Microsoft in .NET 3.5. LINQ allows you to write queries on various data sources such as arrays, collections, database tables, datasets, and XML data.
LINQ is available in the System.Linq
namespace.
Consider the task of sorting an array. Traditionally, you might use loops and predefined methods to do this. LINQ offers a simpler and more elegant way to handle such tasks.
Here's an example array:
int[] numbers = { 17, 34, 8, 56, 23, 91, 42, 73, 15, 27, 68, 39, 44, 53, 11, 22, 78, 31, 86, 9 };
To sort this array using LINQ, you can use the following syntax. LINQ syntax is similar to SQL, where you select and manipulate data.
SQL Syntax:
SELECT <column_list> FROM <table> [AS <alias>] [<clauses>]
LINQ Syntax:
from <alias> in <collection | array> [<clauses>] select <alias>
Getting All Data from an Array Using LINQ:
var num1 = from i in numbers select i;
var
is a keyword introduced in C# 3.0. It declares an implicitly typed local variable, And the data type ofvar
is determined by the value it holds.
num1
is used to capture the array data fromfrom i in numbers select i
.num1
is now an array.
Sort the data which is greater than 40:
var numbers1 = from i in numbers where i > 40 select i;
Sort the data which is greater than 40 in ascending order:
var numbers1 = from i in numbers where i > 40 orderby i select i;
Sort the data which is greater than 40 in descending order:
var numbers1 = from i in numbers where i > 40 orderby i descending select i;
Display values:
foreach (var item in numbers1)
Console.Write(item + " ");
LINQ to SQL:
It's a query language that was introduced in the .NET 3.5 framework for working with relational databases, such as SQL Server.
LINQ to SQL is not just for querying data, it also lets us perform CRUD operations.
CRUD: Create(Insert), Read(Select), Update, Delete**.**
We can also call stored procedures using LINQ to SQL.
Q. There is already a language known as SQL, which we can use to interact with SQL Server with the help of ADO.Net. Then why do we need LINQ?
Ans: SQL is a powerful language used to interact with SQL Server via ADO.NET. However, LINQ (Language Integrated Query) has several benefits over traditional SQL when used in a .NET environment. Let's look at why LINQ is needed and how it can be better than SQL in some cases.
Advantages of LINQ over SQL in ADO.NET:
Compile-Time Syntax Checking:
SQL in ADO.NET: When you write an SQL query in ADO.NET, it's usually wrapped in double quotes as a string. The .NET compiler doesn't recognize the syntax of this string. The query is sent to the SQL Server, where the database engine validates the syntax. If there's a syntax error, it's caught at runtime, which can increase the load on the database engine.
LINQ: LINQ queries are checked for syntax errors at compile time by the .NET compiler. This means errors are caught earlier, reducing runtime issues and lowering the load on the database engine.
Type Safety:
SQL in ADO.NET: SQL queries in ADO.NET are not type-safe. For example, if your query tries to insert a value into a table with a mismatched data type or too many columns, the database engine will return an error. This error is only caught at runtime, which can lead to inefficiencies and wasted time.
LINQ: LINQ is completely type-safe. The .NET compiler makes sure that the types of values in your queries match the database schema. This type checking happens on the client side, making development safer and more efficient. Visual Studio's IntelliSense helps you see the data types of columns, avoiding type mismatches.
IntelliSense Support:
SQL in ADO.NET: When writing SQL queries in ADO.NET, you don't get IntelliSense support for column names, table names, or data types, which can lead to errors and slower development.
LINQ: LINQ offers full IntelliSense support in Visual Studio. This means you get real-time suggestions and feedback on the structure of your query, making development faster and more error-free.
Debugging Capabilities:
SQL in ADO.NET: Debugging SQL statements is hard because the SQL code runs on the database server. You can't step through SQL code like you can with .NET code.
LINQ: LINQ queries are written in C# or VB.NET and run on the client side. This allows you to debug LINQ queries just like any other .NET code, making the debugging process easier.
Pure Object-Oriented Code:
SQL in ADO.NET: SQL code in ADO.NET often feels separate from the rest of your application. For example, when inserting data into a table, you have to manually join strings, which can lead to SQL injection risks and makes the code harder to maintain.
LINQ: LINQ lets you work with data in a fully object-oriented way. Tables become classes, columns become properties, rows are instances of those classes, and stored procedures are methods. This makes the code more consistent, easier to maintain, and in line with modern programming practices.
Simplified Code Structure:
Working with LINQ to SQL:
To work with LINQ to SQL, we first need to convert all the relational objects of the database into object-oriented types. This process is known as ORM (Object-Relational Mapping).
Working with LINQ to SQL: To work with LINQ to SQL, first we need to convert all the relational objects of the database into object oriented types. This process is known as ORM (Object Relational Mapping).
To perform ORM, we use a tool called the OR designer.
Steps to Perform ORM Using LINQ to SQL:
Using the Object-Relational (OR) Designer:
- ORM with OR Designer: The OR Designer is a tool provided by Visual Studio that helps you perform ORM by visually mapping database tables, views, and stored procedures to corresponding classes, properties, and methods in your .NET application.
Adding a Reference to the System.Data.Linq Assembly:
- To work with LINQ to SQL, you need to add a reference to the
System.Data.Linq.dll
assembly in your project. This assembly contains the necessary classes and methods for working with LINQ to SQL.
- To work with LINQ to SQL, you need to add a reference to the
Configuring the Connection String:
- You need to write the connection string in the configuration file (typically
App.config
orWeb.config
) of your project. This connection string will provide the necessary information for your application to connect to the SQL Server database.
- You need to write the connection string in the configuration file (typically
To add the OR Designer, select New Item and choose
LINQ to SQL Classes
. The extension is.dbml
(Database Markup Language). Name the file, ideally matching the database name. Then go toSolution Explorer
>References
and checkSystem.Data.Linq
. If you cannot find the "LINQ to SQL Classes" option, it might be because it is a legacy feature not included by default in newer versions of Visual Studio. To enable it:
Open
Visual Studio Installer
.Ensure that
ASP.NET and web development
is checked under theWorkloads
tab.Go to the
Individual components
tab, search forLINQ to SQL tools
, and check it.Click
Modify
to install the required components.After installation, return to Visual Studio, select
New Item
again, and search forLINQ to SQL Classes
.When you create a
.dbml
file, it generates two additional files:
<filename>.dbml.layout
<filename>.designer.cs
The
designer.cs
file is automatically generated by Visual Studio and is not meant to be edited manually. Visual Studio writes the code in this file when you drag and drop database objects onto the OR Designer. You should only view this file, not modify it.Inside the
designer.cs
file, a class is generated with the name<filename>DataContext
. This class is a partial class that inherits fromSystem.Data
.Linq.DataContext
.What does the
<filename>DataContext
class do?This class acts as a connection to the database. In ADO.NET, you would use a
SqlConnection
class to connect to a database, but in LINQ to SQL, the<filename>DataContext
class serves this purpose. When you create an instance of this class, it helps establish the connection to the database.This class needs a connection string, which specifies the database URL. This connection string is usually stored in the
App.config
file. When you create an instance of the<filename>DataContext
class, it reads the connection string fromApp.config
and establishes the connection to the database.The two panels in the OR Designer in Visual Studio serve specific purposes:
Diagram Panel: This left panel lets you visually design your data model by dragging and dropping tables, views, and relationships. It provides a graphical overview of your database schema.
Properties Panel: This right panel shows detailed information about the selected item in the diagram, such as column names, data types, and relationships. It allows for precise adjustments and configurations.
In my project "LinqToSqlProject," the database name is also "LinqToSqlProject," and I named my .dbml
file as DataClasses1.dbml
.
Steps:
Drag and Drop Tables:
Go to the "Tables" section (under the "Tables" folder) in
Server Explorer
.Select the table (e.g.,
Employee
) and drag it to theDiagram Panel
in the OR Designer.
Connection String in
App.config
:After performing the above step, Visual Studio automatically adds the connection string to the
App.config
file. It might look like this:App.config:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> </configSections> <connectionStrings> <add name="LinqToSqlProject.Properties.Settings.LinqToSqlProjectConnectionString" connectionString="Data Source=DESKTOP-HOOMVQE\MSSQLSERVER02;Initial Catalog=LinqToSqlProject;Integrated Security=True;Encrypt=True;TrustServerCertificate=True" providerName="System.Data.SqlClient" /> </connectionStrings> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" /> </startup> </configuration>
Generated Code in
designer.cs
:The
DataClasses1.dbml
file generates adesigner.cs
file. This file is not meant for manual editing; Visual Studio manages it automatically when you drag and drop items in the designer.The
designer.cs
file includes a parameterless constructor in theDataClasses1DataContext
class, which reads the connection string:public DataClasses1DataContext() : base(global::LinqToSqlProject.Properties.Settings.Default.LinqToSqlProjectConnectionString, mappingSource) { OnCreated(); }
Additionally, a property is created for each table, matching the table name. For example, if your table is named
Employee
, the code will look like this:public System.Data.Linq.Table<Employee> Employees { get { return this.GetTable<Employee>(); } }
A class named
Employee
is also generated, with fields and properties corresponding to the columns of theEmployee
table. The fields use an underscore prefix (_):public partial class Employee { private System.Nullable<int> _Eno; private string _Ename; private string _Job; private System.Nullable<decimal> _Salary; private string _Dname; public Employee() { } [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Eno", DbType="Int")] public System.Nullable<int> Eno { get { return this._Eno; } set { if ((this._Eno != value)) { this._Eno = value; } } } [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Ename", DbType="VarChar(50)")] public string Ename { get { return this._Ename; } set { if ((this._Ename != value)) { this._Ename = value; } } } [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Job", DbType="VarChar(50)")] public string Job { get { return this._Job; } set { if ((this._Job != value)) { this._Job = value; } } } [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Salary", DbType="Money")] public System.Nullable<decimal> Salary { get { return this._Salary; } set { if ((this._Salary != value)) { this._Salary = value; } } } [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Dname", DbType="VarChar(50)")] public string Dname { get { return this._Dname; } set { if ((this._Dname != value)) { this._Dname = value; } } } }
Note: The rows or records in the database are represented as instances of the
Employee
class when the program is running.
Load the data:
To load the data, we use the
Employees
property, which returns the Table.Example get data from database :
Drag and drop the
DataGridView
inForm1.cs
.Page:
Form1.cs
:using System; using System.Windows.Forms; namespace LinqToSqlProject { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { //Establish the connection DataClasses1DataContext data = new DataClasses1DataContext(); //get the property dataGridView1.DataSource = data.Employees; } private void dataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e) { } } }
Server Explorer
:Toolbox
:DataClasses1.dbml
:SQL Server
:Get the data in Form1: when run the
Form1
That's how ORM works.
Q. Create the Form to see the employee data one by one?
Ans:- Steps:
Open SSMS:
Create SQL Server
Notes: Make sure to check the
Trust server certificate
option, otherwise you will get an error.Create databas and add data:
Create database Company; CREATE TABLE Employee ( EmployeeID INT PRIMARY KEY, FirstName NVARCHAR(50), LastName NVARCHAR(50), JobTitle NVARCHAR(100), Salary DECIMAL(10, 2), Department NVARCHAR(50), HireDate DATE ); INSERT INTO Employee (EmployeeID, FirstName, LastName, JobTitle, Salary, Department, HireDate) VALUES (1, 'John', 'Doe', 'Software Engineer', 75000.00, 'IT', '2022-01-15'), (2, 'Jane', 'Smith', 'Data Analyst', 68000.00, 'IT', '2021-03-22'), (3, 'Michael', 'Johnson', 'Project Manager', 85000.00, 'Operations', '2020-08-30'), (4, 'Emily', 'Davis', 'HR Specialist', 60000.00, 'Human Resources', '2019-11-05'), (5, 'David', 'Brown', 'Senior Developer', 90000.00, 'IT', '2018-07-17'), (6, 'Linda', 'Wilson', 'Marketing Manager', 78000.00, 'Marketing', '2023-04-12'), (7, 'Robert', 'Taylor', 'Accountant', 67000.00, 'Finance', '2021-12-01'), (8, 'Mary', 'Lee', 'Customer Support', 52000.00, 'Support', '2020-06-15'), (9, 'James', 'Martin', 'DevOps Engineer', 83000.00, 'IT', '2017-05-23'), (10, 'Patricia', 'Anderson', 'Business Analyst', 72000.00, 'Operations', '2019-02-14');
Notes: Do not close the server
Create
Windows Form App
:
Give the project name
ShowEmployeeFromDatabase
.
Open
Server Explorer
and configure the database:
Copy the
server name
from SSMS, then go toServer Explorer
and right-clickData Connections
. Select the data source and press next. Enter the server name, then select the database fromSelect or enter a database name:
. Press ok.
Notes: Make sure to check the
Trust server certificate
option, otherwise you will get an error.If you do not see the database, please restart the system.
ORM setup:
Add a new item
LINQ to SQL
(.dbml file).Go to
Server Explorer
, open the Tables folder, and drag and drop theEmployee
table to the left side ofCompany.dbml
. ORM setup is complete.Crete design:
Go to
Solution Explorer
and open Form1.cs, which is automatically created, or you can choose your own. Next, openToolbox
and thenCommon Control
to create a design like this.
Write the code in
Form1.cs
:
using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; namespace ShowEmployeeFromDatabase { public partial class Form1 : Form { CompanyDataContext dc; // DataContext instance to connect to the database List<Employee> employees; // List to hold Employee data int currentRecordIndex = 0; // Variable to track the current record index public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { //CompanyDataContext dc = new CompanyDataContext();//Create instance //List<Employee> emp = new List<Employee>();//emp is a list which store only Employee type of data // Initialize the DataContext and fetch employee data into the list dc = new CompanyDataContext(); employees = dc.Employees.ToList(); // Show the first record on form load ShowData(); } private void ShowData() { // Display employee data in the respective text boxes textBox2.Text = employees[currentRecordIndex].EmployeeID.ToString(); textBox1.Text = $"{employees[currentRecordIndex].FirstName} {employees[currentRecordIndex].LastName}"; textBox3.Text = employees[currentRecordIndex].JobTitle; textBox4.Text = employees[currentRecordIndex].Salary.ToString(); textBox5.Text = employees[currentRecordIndex].Department; textBox6.Text = employees[currentRecordIndex].HireDate.ToString("yyyy-MM-dd"); // Format the date for clarity } private void Prev_Click(object sender, EventArgs e) { if (currentRecordIndex > 0) { currentRecordIndex -= 1; ShowData(); } else { MessageBox.Show("This is the first record of the table!"); } } private void Next_Click(object sender, EventArgs e) { if (currentRecordIndex < employees.Count - 1) { currentRecordIndex += 1; ShowData(); } else { MessageBox.Show("This is the last record of the table!"); } } private void Close_Click(object sender, EventArgs e) { this.Close(); // Close the form when the close button is clicked } } }
This code is a Windows Forms application in C# that connects to a database using LINQ to SQL. It displays employee records and allows navigation through them using "Next" and "Previous" buttons.
Key Points:
DataContext (
CompanyDataContext
): Connects to the database.Employee List (
employees
): Stores employee records fetched from the database.Record Navigation:
currentRecordIndex
tracks the current record.
ShowData()
displays the current record's details in text boxes."Previous" and "Next" Buttons: Navigate through records.
Close Button: Closes the form.
Output:
If you don't see the output video here, go hear Outpur.
The form loads employee data on startup and provides a simple interface for viewing and navigating records.
Performing CRUD Operations using LINQ:
- CRUD: Create, Read, Update, and Delete.
Setup project:
Create a Windows application named
CRUD_WindowsForm
.Configure the database in
Server Explorer
.Use an existing database and table.
Create a
DatabaseData.dbml
file and add the employee table.Create a
Form1.cs
file and design it to display the data:In the properties, name the
Insert
buttonInsertData
, theUpdate
buttonbutton2
, theDelete
buttonbutton4
, and theClose
buttonbutton3
. Name theDataGridView
asdataGridView1
.Create a
Form2Insert.cs
file and design it to insert data:In this form, name the 7
textBox
controls fromtextBox1
totextBox7
. Name theSubmit
buttonbutton1
, theClear
buttonbutton2
, and theClose
buttonbutton3
.Create a
Form3Update.cs
file and design it to update data:In this form, name the 7
textBox
controls fromtextBox1
totextBox7
. Name theSubmit
buttonbutton1
, theClear
buttonbutton2
, and theClose
buttonbutton3
. Set theModifiers
property of alltextBox
controls tointernal
.
Codeing Part:
Form1.cs
:
using System;
using System.Linq;
using System.Windows.Forms;
namespace CRUD_WindowsForm
{
public partial class Form1 : Form
{
DatabaseDataDataContext dataContext;
public Form1()
{
InitializeComponent();
}
//Data loader method
private void LoadData()
{
dataContext = new DatabaseDataDataContext();
dataGridView1.DataSource = dataContext.Employees;
}
//Form Load 1 time
private void Form1_Load(object sender, EventArgs e)
{
LoadData();
}
//InsertData:-
private void button1_Click(object sender, EventArgs e)
{
Form2Insert fi = new Form2Insert(); //Use form 2 to insert data
fi.ShowDialog();
LoadData();
}
//UpdateData:-
private void button2_Click(object sender, EventArgs e)
{
if (dataGridView1.SelectedRows.Count > 0) // Check if a row is selected
{
DataGridViewRow selectedRow = dataGridView1.SelectedRows[0];
Form3Update fu = new Form3Update(); // Initialize Form3Update //Use same form "form 2" to update the value by changing the modifier in textBox field private to internal
fu.textBox1.ReadOnly = true;
fu.button2.Enabled = false;
fu.button1.Text = "Update";
fu.textBox1.Text = selectedRow.Cells[0].Value.ToString() ?? String.Empty; ;
fu.textBox2.Text = selectedRow.Cells[1].Value.ToString() ?? String.Empty;
fu.textBox3.Text = selectedRow.Cells[2].Value.ToString() ?? String.Empty;
fu.textBox4.Text = selectedRow.Cells[3].Value.ToString() ?? String.Empty;
fu.textBox5.Text = selectedRow.Cells[4].Value.ToString() ?? String.Empty;
fu.textBox6.Text = selectedRow.Cells[5].Value.ToString() ?? String.Empty;
fu.textBox7.Text = selectedRow.Cells[6].Value.ToString() ?? String.Empty;
//'dataGridView1' is DataGridView name which is taken from property
fu.ShowDialog();
LoadData();
}
else
{
MessageBox.Show("Please select a row first.", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information); // Prompt user to select a row if none is selected
}
}
//Close
private void button3_Click(object sender, EventArgs e)
{
this.Close();
}
//Delete
private void button4_Click(object sender, EventArgs e)
{
if (dataGridView1.SelectedRows.Count > 0)
{
if (MessageBox.Show("Are you suore want to delete this data?", "Confirmation", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
int Eno = Convert.ToInt32(dataGridView1.SelectedRows[0].Cells[0].Value);
Employee obj = dataContext.Employees.SingleOrDefault(E => E.EmployeeID == Eno);
dataContext.Employees.DeleteOnSubmit(obj);
dataContext.SubmitChanges();
LoadData();
}
}
else
{
MessageBox.Show("Please select a row first for deletion.", "Information",MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
}
Form2Insert.cs
:
using System;
using System.Windows.Forms;
namespace CRUD_WindowsForm
{
public partial class Form2Insert : Form
{
public Form2Insert()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
DatabaseDataDataContext db = new DatabaseDataDataContext();
Employee eobj = new Employee();
eobj.EmployeeID = int.Parse(textBox1.Text);
eobj.FirstName = textBox2.Text;
eobj.LastName = textBox3.Text;
eobj.JobTitle = textBox4.Text;
eobj.Salary = decimal.Parse(textBox5.Text);
eobj.Department = textBox6.Text;
eobj.HireDate = DateTime.Now;
// Assuming you want to insert the new employee into the database
db.Employees.InsertOnSubmit(eobj);
db.SubmitChanges();
MessageBox.Show("Employee added successfully!");
}
private void button2_Click(object sender, EventArgs e)
{
foreach (Control ctrl in this.Controls)
{
if(ctrl is TextBox)
{
TextBox tb = ctrl as TextBox;
tb.Clear();
}
}
textBox1.Focus();
}
private void button3_Click(object sender, EventArgs e)
{
this.Close();
}
}
}
Form3Update.cs
using System;
using System.Linq;
using System.Windows.Forms;
namespace CRUD_WindowsForm
{
public partial class Form3Update : Form
{
public Form3Update()
{
InitializeComponent();
}
//Submit
private void button1_Click(object sender, EventArgs e)
{
DatabaseDataDataContext db = new DatabaseDataDataContext();
//We do not want to create new record we want to update that why we need the refrence of current record that why we use ' db.Employees.SingleOrDefault(E=>E.EmployeeID == int.Parse(textBox1.Text))'
Employee eobj = db.Employees.SingleOrDefault(E => E.EmployeeID == int.Parse(textBox1.Text));
if(eobj != null)
{
//If use not modify old value taken
eobj.FirstName = textBox2.Text;
eobj.LastName = textBox3.Text;
eobj.JobTitle = textBox4.Text;
eobj.Salary = decimal.Parse(textBox5.Text);
eobj.Department = textBox6.Text;
eobj.HireDate = DateTime.Now;
db.SubmitChanges();
MessageBox.Show("Employee update successfully!");
}
else
{
MessageBox.Show("Employee not found.");
}
}
//Close
private void button3_Click(object sender, EventArgs e)
{
this.Close();
}
}
}
Coding Explanation:
LoadData Method:
The
LoadData
method is responsible for loading and displaying the employee data onForm1
. Here is the method:private void LoadData() { dataContext = new DatabaseDataDataContext(); // Establish a connection to the database dataGridView1.DataSource = dataContext.Employees; // Bind the Employees table to the DataGridView }
Explanation of the LoadData Method
Connecting to the Database:
dataContext = new DatabaseDataDataContext();
This line initializes a new instance of
DatabaseDataDataContext
, which represents the database connection and provides access to theEmployees
table.
Binding Data to the DataGridView:
dataGridView1.DataSource = dataContext.Employees;
This line sets the
DataSource
property ofdataGridView1
(the grid displaying data) to theEmployees
table. This binds the employee data to the grid, displaying it on the form.
dataContext
is a variable of typeDatabaseDataDataContext
, which is a class. It is declared globally in theForm1
class asDatabaseDataDataContext dataContext;
. AnddataContext
inslized byLoadData()
method.
Close
Button Functionality:For every Close button on your forms, the code is consistent. Double-click the Close button in the form designer, which will automatically generate an event handler. Inside this event handler, add the following line of code:
this.Close();
This will close the current form window, when the Close button is clicked.
Clear
Button Functionality:Clears all text boxes in the form and sets the focus back to
textBox1
.private void button2_Click(object sender, EventArgs e) { foreach (Control ctrl in this.Controls) { if (ctrl is TextBox) { TextBox tb = ctrl as TextBox; tb.Clear(); } } textBox1.Focus(); }
Explanation:
Control
: In Windows Forms, aControl
is a base class for all components that are displayed on a form (e.g., buttons, text boxes, labels, etc.).this.Controls
: Represents a collection of all the controls present on the form.if (ctrl is TextBox)
: This line checks if the current control (ctrl
) is aTextBox
.is TextBox
, theis
keyword checks if an object is of a specific type. Here, it checks whetherctrl
is aTextBox
.TextBox tb = ctrl as TextBox;
: This line tries to convert thectrl
object into aTextBox
. Theas
keyword is used for safe casting. Ifctrl
is aTextBox
, it will be converted to aTextBox
and assigned to the variabletb
. If not,tb
will benull
.tb.Clear();
: This line clears the text inside theTextBox
.Clear()
: TheClear()
method is aTextBox
method that removes all text from the text box, making it empty.
Delete
Button Functionality:Deletion of a selected employee record from the database in a Windows Forms application.
//Delete private void button4_Click(object sender, EventArgs e) { // Check if any row is selected in the DataGridView if (dataGridView1.SelectedRows.Count > 0) { // Ask the user for confirmation before deleting the selected record if (MessageBox.Show("Are you sure you want to delete this data?", "Confirmation", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) { // Get the EmployeeID (assumed to be in the first cell of the selected row) from the selected row int Eno = Convert.ToInt32(dataGridView1.SelectedRows[0].Cells[0].Value); // Retrieve the employee object from the database that matches the selected EmployeeID Employee obj = dataContext.Employees.SingleOrDefault(E => E.EmployeeID == Eno); // If the employee is found, delete it from the database dataContext.Employees.DeleteOnSubmit(obj); // Submit the changes to the database to perform the deletion dataContext.SubmitChanges(); // Reload the data in the DataGridView to reflect the deletion LoadData(); } } else { // Display a message prompting the user to select a row first if no row is selected MessageBox.Show("Please select a row first for deletion.", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information); } }
Explanation:
Retrieving the Employee ID:
int Eno = Convert.ToInt32(dataGridView1.SelectedRows[0].Cells[0].Value);
This line retrieves the
EmployeeID
from the first cell (Cells[0]
) of the selected row.Eno
stores this ID as an integer, which is used to identify the employee to be deleted.Fetching the Employee Object:
Employee obj = dataContext.Employees.SingleOrDefault(E => E.EmployeeID == Eno);
The code queries the database to find the
Employee
object that matches the selectedEmployeeID
using LINQ.SingleOrDefault
is used to retrieve a single employee record that matches the condition. If no match is found,obj
will benull
.Deleting the Employee:
dataContext.Employees.DeleteOnSubmit(obj); dataContext.SubmitChanges();
DeleteOnSubmit
marks the employee object for deletion in the database.SubmitChanges
commits the changes to the database, effectively removing the employee record.
Insert
Button Functionality:To implement the insert functionality, double-click the Insert button in the form designer to generate an event handler. Inside the generated
button1_Click
method, add the following code:private void button1_Click(object sender, EventArgs e) { Form2Insert fi = new Form2Insert(); // Create an instance of Form2Insert to handle data insertion fi.ShowDialog(); // Open Form2Insert as a modal dialog LoadData(); // Reload the data on Form1 after the insertion is complete }
Explanation of the Insert Button Code
Creating an Instance of
Form2Insert
:Form2Insert fi = new Form2Insert();
: This line creates an instance of theForm2Insert
class, which is the form where users can enter new employee data.
Opening
Form2Insert
:fi.ShowDialog();
: This opens theForm2Insert
form as a modal dialog. A modal dialog means that the user must interact with this form before returning to the main form (Form1
). The control flow pauses here untilForm2Insert
is closed.
Reloading Data on
Form1
:LoadData();
: After theForm2Insert
form is closed, control returns to this method. TheLoadData()
method is then called to refresh the data grid onForm1
, reflecting any new data inserted viaForm2Insert
.
Submit
button Functionality inForm2Insert.cs
:This method handles the insertion of a new employee record into the database when the user clicks the "Submit" button in a Windows Forms application.
private void button1_Click(object sender, EventArgs e) { //Create new instance DatabaseDataDataContext db = new DatabaseDataDataContext(); //Create New Employee Object Employee eobj = new Employee(); //Assign Values eobj.EmployeeID = int.Parse(textBox1.Text); eobj.FirstName = textBox2.Text; eobj.LastName = textBox3.Text; eobj.JobTitle = textBox4.Text; eobj.Salary = decimal.Parse(textBox5.Text); eobj.Department = textBox6.Text; eobj.HireDate = DateTime.Now; // Assuming you want to insert the new employee into the database db.Employees.InsertOnSubmit(eobj); db.SubmitChanges(); MessageBox.Show("Employee added successfully!"); }
DatabaseDataDataContext db = new DatabaseDataDataContext();
: Establish a connection to the database. A new instance of theDatabaseDataDataContext
class is created, which serves as the connection between the application and the database. This instance,db
, allows you to interact with the database tables.Create New Employee Object
Employee eobj = new Employee();
: A new instance of the Employee class is created. This object, eobj, represents a new record that you want to insert into the Employees table in the database.Insert New Record
db.Employees.InsertOnSubmit(eobj);
: TheInsertOnSubmit
method is called on theEmployees
table (which is a part of thedb
context). This method marks theeobj
object (the new employee) for insertion into the database when the changes are submitted.Save Changes
db.SubmitChanges();
: TheSubmitChanges
method is called to save all the changes made to the database through thedb
context. This commits the insertion of the newEmployee
record into theEmployees
table in the database.
Control return back to
Form1.cs
->button1_Click
.Submit
button Functionality inForm1.cs
:This method is used to update an existing employee record in the database when the user clicks the "Update" button in the main form of a Windows Forms application. The code opens a new form (Form3Update) where the user can edit the details of the selected employee.
This method is triggered when the "Update" button (button2) is clicked.
//UpdateData:- private void button2_Click(object sender, EventArgs e) { if (dataGridView1.SelectedRows.Count > 0) // Check if a row is selected { DataGridViewRow selectedRow = dataGridView1.SelectedRows[0]; Form3Update fu = new Form3Update(); // Initialize Form3Update //Use same form "form 2" to update the value by changing the modifier in textBox field private to internal fu.textBox1.ReadOnly = true; fu.button2.Enabled = false; fu.button1.Text = "Update"; //set the data: fu.textBox1.Text = selectedRow.Cells[0].Value.ToString() ?? String.Empty; ; fu.textBox2.Text = selectedRow.Cells[1].Value.ToString() ?? String.Empty; fu.textBox3.Text = selectedRow.Cells[2].Value.ToString() ?? String.Empty; fu.textBox4.Text = selectedRow.Cells[3].Value.ToString() ?? String.Empty; fu.textBox5.Text = selectedRow.Cells[4].Value.ToString() ?? String.Empty; fu.textBox6.Text = selectedRow.Cells[5].Value.ToString() ?? String.Empty; fu.textBox7.Text = selectedRow.Cells[6].Value.ToString() ?? String.Empty; //'dataGridView1' is DataGridView name which is taken from property fu.ShowDialog(); LoadData(); } else { MessageBox.Show("Please select a row first.", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information); // Prompt user to select a row if none is selected } }
Form3Update fu = new Form3Update();
: A new instance of theForm3Update
class (which is a form designed for updating employee details) is created. This form will be used to display the selected employee's data and allow the user to modify it.textBox1.ReadOnly = true;
: The first textbox (textBox1
) inForm3Update
is made read-only, so the user cannot modify the Employee ID. This ensures that the primary key remains unchanged during the update process.button2.Enabled = false;
: The second button (button2
) inForm3Update
is disabled, which might be an additional button not used for the update process.button1.Text = "Update";
: The text on the first button (button1
) inForm3Update
is changed to "Update" to reflect the action that will be performed when clicked.fu.ShowDialog();
: This displays theForm3Update
form as a modal dialog, meaning that the user must close this form before they can return to the main form (Form1
). The user will make changes in this form and submit them.LoadData();
: After the user closes the update form, theLoadData()
method is called to refresh theDataGridView
in the main form, reflecting any changes made to the employee's data.
Calling Stored Procedure using LINQ:
- When you open the Server Explorer in Visual Studio, under the Data Connections node, you will see a list of available databases. Inside each database connection, you'll find a folder named Stored Procedures. This folder may be empty if you haven't created any stored procedures yet.
Creating a Stored Procedure:
Right-click on the Stored Procedures folder under your database in the Object Explorer.
Select New Stored Procedure.
A new
query window
will open with a template for creating a stored procedure.
SQL Code to Create a Stored Procedure:
Let's create a stored procedure that returns all records from the Employee
table:
CREATE PROCEDURE Employee_Select
AS
SELECT EmployeeID, FirstName, LastName, JobTitle, Salary, Department, HireDate
FROM Employee Order By EmployeeID;
--Or
CREATE PROCEDURE Employee_Select
AS
BEGIN
SELECT EmployeeID, FirstName, LastName, JobTitle, Salary, Department, HireDate
FROM Employee
ORDER BY EmployeeID;
END
The code you provided is SQL code, specifically Transact-SQL (T-SQL), which is used for writing queries and stored procedures in SQL Server.
CREATE PROCEDURE
: This is a SQL command used to create a stored procedure in a SQL Server database.
AS
andBEGIN ... END
: These keywords define the body of the stored procedure. The first version of this code omitsBEGIN ... END
, which is optional when i have only one SQL statement, but the second version includes them to clearly define the start and end of the procedure's body.
SELECT ... FROM ... ORDER BY
: This is a SQL query that selects specific columns (EmployeeID
,FirstName
,LastName
, etc.) from theEmployee
table and orders the results byEmployeeID
.
After writing the SQL code, right-click on the
dbo.Procedure.sql
page, then select Execute or pressCtrl + Shift + E
.If any errors occur, they will be displayed at the bottom of the window. If there are no errors, a success message will be shown.There’s no need to save the command because the procedure is created directly on the database server.
If you refresh the Server Explorer, you will find the stored procedure listed. In this example, it will be named
Employee_Select
.
Calling the Stored Procedure using LINQ:
Double-click on the
.dbml
file in your project.Drag and drop the
Employee_Select
stored procedure from the Server Explorer onto the right-hand side of the.dbml
design surface.
This action will automatically generate a method named
Employee_Select()
in theDatabaseDataDataContext
class, which is defined in theDatabaseData.designer.cs
file.public ISingleResult<Employee_SelectResult> Employee_Select() { IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod()))); return ((ISingleResult<Employee_SelectResult>)(result.ReturnValue)); }
The method
Employee_Select()
returns anISingleResult<Employee_SelectResult>
, whereEmployee_SelectResult
is a class generated by LINQ to SQL.The
Employee_SelectResult
class contains properties corresponding to the columns selected in the stored procedure (EmployeeID
,FirstName
,LastName
,JobTitle
,Salary
,Department
,HireDate
).ISingleResult<Employee_SelectResult>
is a type in LINQ to SQL that represents the result of executing a stored procedure or a query that returns a sequence of records.ISingleResult<T>
:ISingleResult
is an interface in LINQ to SQL, specifically in theSystem.Data
.Linq
namespace. It is used to represent the result of a query or stored procedure that returns a collection of records (rows).T
is the type of the individual records in that collection.ISingleResult
is like a table ofEmployee_SelectResult
in the database, similar toTable<Employee>
, which is just a table of Employee.Employee_SelectResult
: This is a class generated by LINQ to SQL when you drag and drop a stored procedure into the.dbml
design surface. The classEmployee_SelectResult
contains properties that match the columns returned by theEmployee_Select
stored procedure. For example, if the stored procedure returns columns likeEmployeeID
,FirstName
,LastName
, etc., theEmployee_SelectResult
class will have properties corresponding to these columns.
ISingleResult<Employee_SelectResult>
represents a collection ofEmployee_SelectResult
objects, where each object corresponds to a row returned by theEmployee_Select
stored procedure.ISingleResult
ensures that you can enumerate over the result set, typically using aforeach
loop, to access eachEmployee_SelectResult
object in the collection.
Difference Between Employee
Class and Employee_SelectResult
Class:
Employee
Class: Represents the entireEmployee
table. It contains properties for all the columns in the table, so when you use this class, you automatically retrieve all the columns.Employee_SelectResult
Class: Represents the result of theEmployee_Select
stored procedure. This class is similar to theEmployee
class but is specifically tailored to match the columns returned by the stored procedure. This allows you to specify and retrieve only the columns you need from theEmployee
table, offering more control over the data you work with.In summary, while the
Employee
class automatically includes all columns from theEmployee
table, theEmployee_SelectResult
class allows you to retrieve only the columns specified in your stored procedure, giving you greater flexibility in managing the data and you can write any type of SQL query like:CREATE PROCEDURE Employee_SelectBySalary @MinimumSalary DECIMAL(18, 2) AS BEGIN SELECT EmployeeID, FirstName, Salary FROM Employee WHERE Salary > @MinimumSalary ORDER BY Salary DESC; END
Calling the Employee_Select()
method and displaying the results in a DataGridView
:
Create a new form named
Form1SQL
with aDataGridView
. Double-click the form (not theDataGridView
). When you do this, theForm1SQL.cs
page will open. Then, import theusing
System.Data
.Linq;
namespace. write this code:-private void Form1SQL_Load(object sender, EventArgs e) { DatabaseDataDataContext db = new DatabaseDataDataContext(); ISingleResult<Employee_SelectResult> tab = db.Employee_Select(); dataGridView1.DataSource = tab; }
In the future, if the
Employee_Select()
method needs a parameter, we will be able to pass it.In the case of
dc.Employee_Select
, we use a predefined property, but indc.Employee_Select()
, we use a stored procedure to perform this.Update
Program.cs
to RunForm1SQL
: Modify theMain
Method inProgram.cs
. Change theApplication.Run
line to useForm1SQL
instead of the default form.using System; using System.Windows.Forms; namespace CRUD_WindowsForm { static class Program { [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1SQL()); } } }
Summary:
Form Creation: You created
Form1SQL
with aDataGridView
and implemented theForm1SQL_Load
event to load data.Loading Data: In
Form1SQL_Load
, you instantiatedDatabaseDataDataContext
, calledEmployee_Select()
, and set theDataSource
ofdataGridView1
to the result.Running the Form: You updated
Program.cs
to runForm1SQL
instead of the default form.
This setup ensures that when
Form1SQL
is loaded, it will call theEmployee_Select()
stored procedure, retrieve the data, and display it in theDataGridView
.
If you want to retrieve any type of data according to your requirements, you can do so by changing the query of Employee_SelectResult
. For example, if I want the data based on the department name:
CREATE PROCEDURE Employee_Select(@Department Varchar(50) = Null)
AS
Begin
if @Department is Null
SELECT EmployeeID, FirstName, LastName, JobTitle, Salary, Department, HireDate FROM Employee Order By EmployeeID;
Else
SELECT EmployeeID, FirstName, LastName, JobTitle, Salary, Department, HireDate FROM Employee Where Department = @Department Order By EmployeeID;
End;
/* Old Query:
CREATE PROCEDURE Employee_Select
AS
SELECT EmployeeID, FirstName, LastName, JobTitle, Salary, Department, HireDate
FROM Employee Order By EmployeeID;*/
Follow the same process: delete the current Employee_Select
available in the .dbml
file, then re-execute the DatabaseData.designer.cs
and drag and drop it on the right side of the .dbml
file. Now you will see it takes a parameter.
private void Form1SQL_Load(object sender, EventArgs e)
{
DatabaseDataDataContext db = new DatabaseDataDataContext();
ISingleResult<Employee_SelectResult> tab = db.Employee_Select();
dataGridView1.DataSource = tab;
}
Now hear db.Employee_Select()
, if you pass null, you get all values. If you pass a department name, you get data for that department. If you don't pass anything, you get an error.
Here is the code for the DatabaseData.designer.cs
file:
public ISingleResult<Employee_SelectResult> Employee_Select([global::System.Data.Linq.Mapping.ParameterAttribute(Name="Department", DbType="VarChar(50)")] string department)
{
IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())), department);
return ((ISingleResult<Employee_SelectResult>)(result.ReturnValue));
}
Now it's taking a parameter.
If the stored procedure is parameterized, then the method becomes parameterized. If the stored procedure is non-parameterized, then the
DatabaseData.designer.cs
method is also non-parameterized.
How to write a query on a database using SQL:
Create table to understande:
Employee table:
EmployeeID FirstName LastName JobTitle Salary Department HireDate 1 John Doe Software Engineer 75000.00 IT 2022-01-15 2 Jane Smith Data Analyst 68000.00 IT 2021-03-22 3 Michael Johnson Project Manager 85000.00 Operations 2020-08-30 4 Emily Davis HR Specialist 60000.00 Human Resources 2019-11-05 5 David Brown Senior Developer 90000.00 IT 2018-07-17 6 Linda Wilson Marketing Manager 78000.00 Marketing 2023-04-12 7 Robert Taylor Accountant 67000.00 Finance 2021-12-01 8 Mary Lee Customer Support 52000.00 Support 2020-06-15 9 James Martin DevOps Engineer 83000.00 IT 2017-05-23 10 Patricia Anderson Business Analyst 72000.00 Operations 2019-02-14 Department table:
CREATE TABLE Department ( DepartmentID INT PRIMARY KEY, DepartmentName VARCHAR(50) NOT NULL, Manager VARCHAR(50) );
INSERT INTO Department (DepartmentID, DepartmentName, Manager) VALUES (1, 'IT', 'Alice Cooper'), (2, 'Operations', 'Bob Stevens'), (3, 'Human Resources', 'Catherine Green'), (4, 'Marketing', 'Diana Prince'), (5, 'Finance', 'Edward Norton'), (6, 'Support', 'Fiona White');
DepartmentID DepartmentName Manager 1 IT Alice Cooper 2 Operations Bob Stevens 3 Human Resources Catherine Green 4 Marketing Diana Prince 5 Finance Edward Norton 6 Support Fiona White Both tables are in the same database
Company
in SQL Server. Configure the database in Server Explorer in Visual Studio. Drag and drop both tables into the.dbml
file on the left side. Then, create theForm2SQL.cs
file.Double-click on the form, and a load method will be created:
private void Form2SQL_Load(object sender, EventArgs e){}
Change the
Program.cs
file code to runForm2SQL()
.
Application.Run(new Form2SQL());
Writing the SQL query:
Syntac:-
Select * | <collist> form <table> as <allas> [<clauses>]
Sequence for writing the SQL query:
Clauses:
Where
Group By
Having
Order By
Writing the LINQ query:
Syntax:
From <alias> in <table> [<clauses>] select <alias> | new {<list of columns/collist>}
Clauses:
Where
Group By
Order By
But you can use Having clauses in LINQ query.
Example: Retrive all data from database
From E in dc.Employees select E;
To strore the data:
var tabl = From E in dc.Employees select E;
Example of LINQ:
Get all
IT
department employees:var tab = from E in dc.Employees where E.Department == "IT" select E;
private void Form2SQL_Load(object sender, EventArgs e) { DatabaseDataDataContext db = new DatabaseDataDataContext(); var tab = from E in db.Employees select E; dataGridView1.DataSource = tab; }
Get
EmployeeID, FirstName, LastName, JobTitle and Salary
of allIT
department employees:var tab = from E in db.Employees where E.Department == "IT" select new { E.EmployeeID, E.FirstName, E.LastName, E.JobTitle, E.Salary };
private void Form2SQL_Load(object sender, EventArgs e) { DatabaseDataDataContext db = new DatabaseDataDataContext(); var tab = from E in db.Employees where E.Department == "IT" select new { E.EmployeeID, E.FirstName, E.LastName, E.JobTitle, E.Salary}; dataGridView1.DataSource = tab.ToList(); }
Create filter by
Department
:Add comboBox in form. And add this code in Form Load:
//Form2SQL.cs using System; using System.Data; using System.Linq; using System.Windows.Forms; namespace CRUD_WindowsForm { public partial class Form2SQL : Form { DatabaseDataDataContext db; public Form2SQL() { InitializeComponent(); } //Form load method private void Form2SQL_Load(object sender, EventArgs e) { db = new DatabaseDataDataContext(); var dep = from E in db.Employees select new { E.Department }; //comboBox1.DataSource = dep; //comboBox1.DataSource = dep; This shows all departments, but the problem is it comes in object form like { Department = "IT" }, and I want it to show only "IT". comboBox1.DataSource = dep.Distinct(); //This shows all departments without duplicates, but the problem is it comes in object form like { Department = "IT" }, and I want it to show only "IT". comboBox1.DisplayMember = "Department"; //This shows all departments in a readable form like "IT" dataGridView1.DataSource = from E in db.Employees select E; } //Combo box method private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) { dataGridView1.DataSource = from E in db.Employees where E.Department == comboBox1.Text select E; //Filter the data and show in dataGridView1 } } }
Explanation:
DatabaseDataDataContext db;
Database Context (db
): TheDatabaseDataDataContext
object (db
) is declared as a class-level variable.Constructor: The
Form2SQL
constructor initializes the form components but doesn't do anything specific for the database until the form loads.Form Load (
Form2SQL_Load
) : When the form is loaded, theDatabaseDataDataContext
object is initializeddb = new DatabaseDataDataContext();
, establishing a connection to the database .Initial Data Load (
dataGridView1.DataSource = from E in db.Employees select E;
) : This line sets theDataGridView
(dataGridView1
) data source to all rows from theEmployees
table. Theselect E
query retrieves all columns for each employee .Department Query (
dep
)var dep = from E in db.Employees select new { E.Department };
: This LINQ query retrieves all department names from theEmployees
table. The query returns an anonymous object with a single propertyDepartment
for each row.The
Distinct()
method ensures that each department name appears only once, eliminating duplicates.DisplayMember (
comboBox1.DisplayMember = "Department";
): TheDisplayMember
property is set to "Department", which tells theComboBox
to display just the department names, not the full anonymous object{ Department = "IT" }
. The comment notes an issue with duplicates appearing ifDistinct()
isn't used correctly.
Event Handling:
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) { dataGridView1.DataSource = from E in db.Employees where E.Department == comboBox1.Text select E; }
ComboBox Selection Change (
comboBox1_SelectedIndexChanged
):When the user selects a department from the
ComboBox
, theSelectedIndexChanged
event triggers.The
DataGridView
is then filtered to show only those employees whoseDepartment
matches the selected value incomboBox1
. The query retrieves all columns (select E
) for the matching employees.
Output: If you do not see the output, click on this link https://cdn.hashnode.com/res/hashnode/image/upload/v1723745503915/611620fb-4b26-4427-8e4f-b426ffe79266.gif?auto=format,compress&gif-q=60&format=webm.
Get data ordered by
Salary
:dataGridView1.DataSource = from E in db.Employees orderby E.Salary select E;
Get data ordered by
FirstName
in descending order:dataGridView1.DataSource = from E in db.Employees orderby E.FirstName descending select E;
Get required columns:
dataGridView1.DataSource = from E in db.Employees select new { E.EmployeeID, E.FirstName, E.Department, E.Salary};
Change the column names or alias the names
First_Name = E.FirstName
:dataGridView1.DataSource = from E in db.Employees select new { E.EmployeeID, First_Name = E.FirstName, E.Department, E.Salary};
Get the number of employees in each Department:
//In SQL: Select Department, EmployeeCount = count(*) from Emp Group By Department; //In LINQ: dataGridView1.DataSource = from E in db.Employees group E by E.Department into deptGroup select new { Department = deptGroup.Key, EmployeeCount = deptGroup.Count() };
The
into
keyword allows you to give a name to the grouped result and continue querying it.deptGroup
is name which represents each group of employees that share the same department.deptGroup
: After grouping, eachdeptGroup
represents a group of employees in a specific department. It's a collection of all employees who have the same department value.The
Key
property is used to access the value by which the grouping was performed.deptGroup.Key
: This refers to the department name (e.g., "IT", "HR") that the group is based on.Count()
is an aggregate function in LINQ. We can use all aggregate functions like Max, Min, Sum, Avg, and Count.Get the department which have greater than 3 employees :
//In SQL: Select Department, EmployeeCount = count(*) from Emp Group By Department Having Count(*) > 5;
In LINQ:
LINQ
doesn't haveHAVING
clauses. So, how do you achieve this? InLINQ
, when you useWHERE
clauses beforeGROUP BY
, it works like aWHERE
clause. But if you useWHERE
clauses afterGROUP BY
, it works like aHAVING
clause.//In LINQ dataGridView1.DataSource = from E in db.Employees group E by E.Department into deptGroup where deptGroup.Count() > 5 select new { Department = deptGroup.Key, EmployeeCount = deptGroup.Count() };
Use multiple clauses:
//In SQL: Select Dept, Count = Count(*) From Employees Where Department = "IT" Group By Dept; //In LINQ: dataGridView1.DataSource = from E in db.Employees where E.Department == "IT" group E by E.Department into D select new { Dept = D.Key, Count = D.Count() };
//In SQL: Select Dept, Count = Count(*) From Employees Where Department = "IT" Group By Dept Having Count(*)>1; //In LINQ: dataGridView1.DataSource = from E in db.Employees where E.Department == "IT" group E by E.Department into D where D.Count() > 1 select new { Dept = D.Key, Count = D.Count() };
Arrange this data in decinding order of department number:
//In SQL: Select Dept, Count = Count(*) From Employees Where Department = "IT" Group By Dept Having Count(*)>1 Order By Dept DESC; //In LINQ: dataGridView1.DataSource = from E in db.Employees where E.Department == "IT" group E by E.Department into D where D.Count() > 1 orderby D.key descending select new { Dept = D.Key, Count = D.Count() };
Thank you for completing the C# programming language course with a focus on LINQ concepts. Your dedication to mastering this powerful tool will undoubtedly enhance your coding efficiency and data manipulation skills. Keep up the great work!