Getting-started-with-csharp
Getting started with C#⌗
- Dotnet CLI
Mainmethod and Top-level statement- Namespace
- Class
- Record
- Struct
- Enum
- Interface
- Anonymous Type
- Extension method
- Type inference with
varkeyword - Object initializer
- Generics
- Type casting
- Important Interfaces
- Lambda
- Linq
- Asynchronous programming
Dotnet CLI⌗
# create new solution
dotnet new sln --output <output_location> --name <solution_name>
# types of project
dotnet new list
# create a project
dotnet new <type> --output <output_location> --name <project_name>
# add project to solution
dotnet solution add <project_path>.csproj
# add a nuget pkg
dotnet add package Dumpify
Main method and Top-level statement⌗
class Program
{
public static void Main(string[] args)
{
// Display the number of command line arguments.
Console.WriteLine(args.Length);
}
}
-
The Main method is the entry point of an executable C# program; it is where the program control starts and ends.
-
Starting in C# 9, you don’t have to explicitly include a Main method in a console application project. Instead, you can use the top-level statements feature to minimize the code you have to write. In this case, the compiler generates a class and Main method entry point for the application.
-
An application must have only one entry point. A project can have only one file with top-level statements.
-
A file with top-level statements can also contain namespaces and type definitions, but they must come after the top-level statements.
Namespace⌗
- The namespace keyword is used to declare a scope that contains a set of related objects.
- The name of the
namespacemust be a valid C# identifier name. - The compiler adds a default namespace. This unnamed namespace, sometimes referred to as the global namespace, is present in every file. It contains declarations not included in a declared namespace. Any identifier in the global namespace is available for use in a named namespace.
- File scoped namespace declarations enable you to declare that all types in a file are in a single namespace. File scoped namespaces can’t include additional namespace declarations. You cannot declare a nested namespace or a second file-scoped namespace.
// file scoped namespace
namespace FileScoped;
class BlaBla {}
// block scoped namespace
namespace BlockScoped
{
class BlaBla {}
}
Class⌗
readonlymeans it can only be set either from constructor or by initialization.constmeans it can only be set by initialization.staticmembers has to be accessed by class name.
public class User
{
// const field
public const string Country = "BD";
// static readonly field
public static readonly string Star = "Sun";
// readonly field
public readonly string Planet = "Earth";
// field
private bool _isEmailConfirmed;
// field initializer
public required string FullName { get; set; } = string.Empty;
// auto property
public required string UserName { get; set; }
public string? Email { get; set; }
// property with backing field
public bool IsEmailConfirmed
{
get => _isEmailConfirmed;
set
{
if (value && Email is not null) _isEmailConfirmed = value;
else if (value is false && Email is null) _isEmailConfirmed = value;
}
}
}
Record⌗
public record AppUserLoginRequest(string UserName, string Password);
Struct⌗
structis value type, that means memory is allocated in stack rather than heap.- A
structtype can’t inherit from other class or structure type and it can’t be the base of a class. However, a structure type can implement interfaces.
public struct Person
{
public required string FullName { get; set; }
public int Age { get; set; }
}
struct type declarations with field initializers must include an explicitly declared constructor.
public struct Person
{
public Person()
{
}
public required string FullName { get; set; } = string.Empty;
public int Age { get; set; } = 13;
}
Enum⌗
public enum BlogStatus : byte
{
Published = 1,
Draft
}
Interface⌗
- An interface defines a contract between user-defined types.
- By convention, an interface name starts with the letter I.
- All the members of an interface are public by default.
- An interface contains members including methods, properties, indexers, and events.
- A user-defined type that implements an interface must provide implementations for the members of the interface.
- A user-defined type can implement multiple interfaces.
- An interface can extend one or more interfaces.
interface IMyInterface
{
void MyMethod();
}
Anonymous Type⌗
-
Anonymous types allow you to encapsulate a set of read-only properties into a single object without having to define a class first.
-
Behind the scenes, the compiler will generate the type name for the anonymous type.
var products = new[]
{
new { Name = "Phone X", Price = 999.99 },
new { Name = "Computer Y", Price = 1999.99 },
new { Name = "Tablet Z", Price = 2999.99 }
};
Extension method⌗
using System.Linq;
namespace LearningCSharp;
public static class StringExtensions
{
public static bool IsConstantCase(this string text)
{
return text.All(c => char.IsUpper(c));
}
}
Type inference with var keyword⌗
- Use the
varkeyword for a variable with initialization from which the compiler can infer a type. - The motto is: type should be only visible in one side of variable declaration, either right or left.
varis used when type is evident in the left side.
using System;
using System.Collections.Generic;
IList<int> digitsList = [10, 20, 30, 35, 55, 66];
var digitsListWithVar = new List<int> { 10, 20, 30, 35, 55, 66 };
Object initializer⌗
var user = new User
{
FullName = "John Doe",
UserName = "john-doe"
};
User user = new()
{
FullName = "John Doe",
UserName = "john-doe"
};
Generics⌗
- Generics allow you to write code that works with more than one type.
- Generic constraints specify constraints on the type used as arguments for type parameters in generic type. Use the
wherekeyword to specify generic constraints.
public static TNumber AddTwoNumbers<TNumber>(TNumber a, TNumber b)
where TNumber : INumber<TNumber>
{
return a + b;
}
Type casting⌗
- An object reference can be implicitly upcast to a base class reference. An upcast always succeeds.
- An object reference can also be explicitly downcast to a subclass reference. A downcast succeeds only if the object is compatible typed. Syntax:
(T)E - Use the
asoperator to perform a downcast that evaluates to null rather than throwing an exception.
var employee = new Employee
{
Name = "John Doe",
JobTitle = "C# Developer"
};
Person person = employee; // upcast
Employee employee2 = (Employee)person; // downcast
Employee? employee3 = person as Employee; // downcast
class Person
{
public required string Name { get; set; }
}
class Employee : Person
{
public required string JobTitle { get; set; }
}
Important Interfaces⌗
IEnumerable<T>: When we need a read-only operation, we can use IEnumerable<T>. It is for looping through the collection in the forward direction only. It supports deferred execution and filtering.
ICollection<T>: ICollection<T> extends IEnumerable<T>. ICollection allows to add or remove elements in the collection which is not possible with IEnumerable. But we still can’t perform any index-related operations. But we still can’t perform any index-related operations.
IList<T>: IList<T> extends ICollection<T>. It allows index-related operations like Insert(), RemoveAt() etc.
IQueryable<T>: Use for working with Database, processing will be done in the DB side. Fetched data can be translated to ICollection or IList easily.
using System;
using System.Collections.Generic;
IEnumerable<int> digitsEnumerable = [10, 20, 30, 35, 55, 66];
ICollection<int> digitsCollection = [10, 20, 30, 35, 55, 66];
IList<int> digitsList = [10, 20, 30, 35, 55, 66];
Lambda⌗
- A lambda expression can reference any variables including local variables, parameters, and members that are accessible from the location where you define the lambda expression.
- The variables referenced by a lambda expression are known as captured variables. When a lambda expression has captured variables, it is called a closure.
- When a lambda expression captures variables, the C# compiler needs to create a private class and instantiate it to store a reference to the captured variables. This incurs a small performance. you can use a static keyword to ensure that the lambda expression doesn’t capture any variables.
// expression lambda
var sum1 = (int a, int b) => a + b;
// statement lambda
var sum2 = (int a, int b) =>
{
var sum = a + b;
return sum;
};
// avoid capturing outer variable
int factor = 2;
var square = static (int x) => x * factor; // ERROR
Linq⌗
using System;
using System.Collections.Generic;
using System.Linq;
// 1. Data source
IList<int> digitsEnumerable = [10, 20, 30, 35, 55, 66];
// 2. Query creation
var numberQuerySyntax =
from number in digitsEnumerable
where number % 2 == 0
select number;
var numberMethodSyntax = digitsEnumerable.Where(x => x % 2 == 0);
// 3. Query execution
foreach (var number in numberQuerySyntax)
{
Console.WriteLine(number);
}
Console.WriteLine("############");
foreach (var number in numberMethodSyntax)
{
Console.WriteLine(number);
}
Asynchronous programming⌗
public class Todo
{
public int Id { get; set; }
public int UserId { get; set; }
public string? Title { get; set; }
public bool Completed { get; set; }
}
public async Task<IList<Todo>> LoadTodoData()
{
using var httpClient = new HttpClient();
var response = await httpClient.GetAsync("https://jsonplaceholder.typicode.com/todos");
var todos = await response.Content.ReadFromJsonAsync<List<Todo>>();
return todos ?? [];
}