-
auto property syntax
public string FirstName { get; set; }
-
Initializing a property is possible
public string FirstName { get; set; } = string.Empty;
-
You can initialize the property in the declaration or constructor.
public List<int> points { get; set;} = new List<int>();
-
If you also delete the
setaccessor, you will have a read-only property that you can not reassign after construction is completed.public List<int> points { get; } = new List<int>();
-
-
valuecontextual keyword is used as the input parameter forsetaccessor.//_num is a field set { _num = value; }
-
expression-bodied members can be used to implement single expression properties
set => firstName = value;
-
setorgetaccessors access modifier can be restrictive but not permissive than property access modifier:public int N { get; private set;} // is valid private int N { get; public set;} // is not valid
-
Properties are just like methods. This means that you can't modify the return value of a property of value type.
-
Provide an access mechanism to objects similar to array access by brackets.
[] -
You can treat indexers like properties, methods that accept arguments of any type.
-
Use
this[arg(s)]to access indexers. -
Indexers are overloadable.
private int[] _v = new int[10]; private int[,] _m = new int[10,10]; //Type 1️⃣ syntactic sugar but not get public int this[int index] => _v[index]; //Type 2️⃣ - like properties public int this[int index] { get => _v[index]; set => _v[index] = value; } //Type 3️⃣ - multi-dimensional public int this[int d1, int d2] { get => _m[d1, d2]; set => _m[d1, d2] = value; } //Type 4️⃣ - any type of argument is possible public int this[string s] { get => s; } //Type 5️⃣ - indexers are like properties. Doing a conditional evaluation for example public int? this[int i] { get => (i < 0 || i >= _v.Length) ? null : _v[i]; }
- You can modify the operator function in types by operator overloading.
- Most of the operators are overloadable, even
trueorfalseoperators. - These operators MUST be overloaded in pairs:
==and!=pair<and>pair<=and>=pair
- These operators are not overloadable.
^x, x = y, x.y, x?.y, c ? t : f, x ?? y, x ??= y, x..y, x->y, =>, f(x), as, await, checked, unchecked, default, delegate, is, nameof, new, sizeof, stackalloc, switch, typeof, with
- Operator overloading MUST:
- Be
public static - At least one parameter of the type of the defining type (sample 3) is needed.
- Order of types matter ie.
Tyep1+Type2is different fromType2+Type1 - You can overload both
- Order of types matter ie.
- Be
//1- unary operator overloading sample:
public static Counter operator ++(Counter x) { /*body*/ }
//2- binary operator overloading sample:
//evaluates x+y both of the Counter type.
public static Counter operator +(Counter x, Counter y)
{
return new Counter(x.Count + y.Count);
}
//3- at least one parameter MUST be of the type of the defining class
//(here Counter is the defining class) <Counter instance> + <an int number>
public static Counter operator +(Counter x, int y) { /*body*/ }- To define a type conversion operator (casting), we use
explicitorimplicitkeywords with the type to be cast as the operator method name.implicitshould be used when there is a standard built-in type promotion available(e.g. int->long,...)- If no standard type promotion available you may encounter exceptions or weird behavior
- Our first choice is to use
explicit. We useimplicitjust when it makes sense
// is used to resolve: (int)counter
public static explicit operator int(Counter x)
{
return x.Count;
}
// is used to resolve implicit conversions like:
// <int or a type with a built-in conversion
// available of int such as long, double,... > + counter
public static explicit operator int(Counter x)
{
return x.Count;
}trueandfalseoperators are also overloadable. Their main purpose is to define the condition that is not true neither false.- After the
nullablefeature is added to C#, overloading these operators may not be reasonable. - You can overload
implicitcast toboolto cover any reasonable condition about booleans.- you can combine
?with the type in to define anullableconversion operator
- you can combine
- After the
// implicit cast to nullable bool
public static implicit operator bool?(Counter x)
{
if (x.x > 0)
return true;
if (x.x < 0)
return false;
return null;
}
//Example above without nullable support
public static implicit operator bool(Counter x)
{
if (x.x >= 0)
return true;
return false;
}
//to overload true or false
public static bool operator true(Counter x)
{
//body with return type of bool
}- You can define events for
structorclass. - Event model is a way to broadcast notifications to the subscribed objects.
- With event subscription, the event source object couples to the event sink object.
- Events are related to
delagtes. So we will talk more about them in later chapters. The author of the book has talked a little about events here because an event is, after all, a type.
- They're used to declare a type inside the scope of another.
- They're useful for implementing a better encapsulation.
- Their default access is
private. Although you can give them permissive access(likepublic), they're not designed to be used in this way, and you may violate the Code quality rule: Nested types should not be visible. - Nested types:
- Should not be visible(accessible) outside of the containing(parent/outer) type.
- Don't use them for logical grouping.
- Don't use them to avoid name collision.
- (
namespaceis for this job.)
- (
- To refer to the outer type, you can initialize the inner/nested type with the outer/containing type instance. (refer to code below)
public class Container //Outer Type
{
public Container()
{
//this, here, refers to the defining type== Container class instance
var nested = new Nested(this);
}
public class Nested // Inner Type
{
private Container parent;
public Nested(Container parent)
{
//this, here, points to the defining type== Nested class instance
this.parent = parent;
}
}
}Interface is a type to declare some services without any implementation*. It can be used by different types with different implementation.
* Starting with C# 8.0, interfaces can define defualt implementation. (more details later)
A class or a struct can use interfaces by declaring them after the type name, following with a colon. The type must implement all members of interfaces.
public interface IService
{
public void M();//Just declaration with no implementation
}
public class ClassX : IService
{
/* Class body*/
// Implementation of interface members
public void M()
{
//body
}
}
//=====To access interface member defined in a type=====
var classX = new ClassX();
classX.M(); // How to call the member of interface from a class instanceNote: A type can implement multiple interfaces by using a comma(,) separating interfaces name.
public class ClassX : IService1, IService2, IService3
{
/* Class body*/
}- Before C# 8:
- Methods
- Properties
- Indexers
- Events
- Added after C# 8:
- Constants
- Operators
- Static constructor
- Nested types
- Static fields, methods, properties, indexers, and events
- Member declarations with the explicit implementation
- Explicit access modifiers (the default access is
public)
- Beginning with C# 11
static abstractcan usable to all types except fields.
-
You can't declare non-static fields in interfaces.
-
You can't change the access modifier when implementing.
-
You can add a
getorsetproperty implementation if it's not declared in the interface ( not supported for explicit implemented properties)interface IZ { public string Name { get; } // set is not declared } class ID : IZ { private string _name = ""; public string Name { get => _name; private set => _name = value;// as set is not declared in interface, //we can add it with arbitrary access modifier } }
-
The
privatemembers of an interface must have a default implementation. -
Any member of an interface can have a default implementation.
-
To use a member of an interface, you should implement it in your type(like
class) regardless of whether the member has been implemented in theinterface(has a default implementation) or not. -
All not-implemented members of an interface MUST be implemented in the target type.
-
To implement an
interfacemember in a type(likeclass,struct,...), both members must have the same signature (same member name and same parameter types) -
If two interfaces used in a type have the same member, you should implement them explicitly by the interface name preceding the member:
- Syntax <interface name>.<member name>
- To call the explicit member you should cast the instance to the related interface or use the
askeyword like the following example:
interface IY
{
public void MyName();
}
interface IZ
{
public void MyName();
}
class MultipleImp : IY, IZ
{
void IY.MyName() //explicit implementation
{
Console.WriteLine("I'm the IY.MyName implementation");
}
void IZ.MyName() //explicit implementation
{
Console.WriteLine("I'm the IZ.MyName implementation");
}
}
//=====RUN=====
var multImp=new MultipleImp();
(multImp as IY).MyName(); //can not call directly. use the `as` keyword or cast
(multImp as IZ).MyName(); //can not call directly. use the `as` keyword or castOutput:
I'm the IY.MyName implementation
I'm the IZ.MyName implementation
- Enum (short for enumeration) is a type to define named values.
- Enumerations are for defining named values (flags, etc.) and combining them.
- Enumerations bring us code readability. Searching in codes and modifying flags gets easier with enumerations.
Enumerations utilize integral value type constants for the underlying layer.
- to define an
enumtype, you can list some names:
//===Defining an enum===
public enum FourElements
{
Fire , //compiler may give it value == 0
Air , // also == 1
Water, // also == 2
Earth // also == 3
}
//===An example of using an enum===
FourElements element = FourElements.Fire;
if (element == FourElements.Fire)
Console.WriteLine("The elemnt is Fire");- members of an enumeration are not mutually exclusive by default, so to define them as flags, you should give them mutually exclusive values manually:
public enum FourElements
{
Fire = 1 << 1, // = 0b0000_0001
Air = 1 << 2, // = 0b0000_0010
Water = 1 << 3, // = 0b0000_0100
Earth = 1 << 4, // = 0b0000_1000
}- underlying value type for an enumeration is
int. Asinthas 32-bits, to define a flag set with more than 32 flags, you should uselongas the underlying type. Define the underlying data type by using a colon as follow:
public enum ALotNumberOfFlags : long
{/*body*/}enumtype members are practically constants.- you can combine them by bitwise-OR operator
|. Exclusion is also possible in addition to any arithmetic or logical operation. - For checking flags' presence in an enum variable, use the
HasFlagmethod :
public enum Colors
{
Black = 0 ,
Red = 1 , //0b0000_0001
Green = 2 , //0b0000_0010
Blue = 4 , //0b0000_0100
NoColor = 128 , //0b1000_0000
Yellow = Green | Red, //0b0011
}
Colors weirdYellow = Colors.NoColor | Colors.Yellow;
Colors normalYellow = Colors.Yellow;
Console.WriteLine(weirdYellow.HasFlag(Colors.Red)); //-->True
Console.WriteLine(normalYellow.HasFlag(Colors.Red)); //-->True<enumX_instance>.HasFlag(<enumX_member>)
is equaivallent to
(<enumX_instance> & <enumX_member> ) == <enumX_member>[System.Flags]attribute provides functionalities dedicated to flags. By employing this attribute before defining theenum,compiler will treat enum as genuine flags. Decomposition intostringgets possible. Use it when you want to perform logical operations on mutually exclusive flags.- Nothing wrong will happen if you do not use this attribute. Its principal purpose is to decompose an
enummember into strings.
- Nothing wrong will happen if you do not use this attribute. Its principal purpose is to decompose an
[System.Flags]
public enum ColorsF
{
Red = 1 , //0b0000_0001
Green = 2 , //0b0000_0010
Blue = 4 , //0b0000_0100
}
public enum Colors
{
Red = 1 , //0b0000_0001
Green = 2 , //0b0000_0010
Blue = 4 , //0b0000_0100
}
//===Run Sample===
Colors Yellow = Colors.Green | Colors.Red;
ColorsF YellowF = ColorsF.Green | ColorsF.Red;
Console.WriteLine(Yellow.ToString());
Console.WriteLine(YellowF.ToString());Output:
3
Red, Green
Will be discussed later in chapter 9.
Will be discussed later in chapter 21.
There are few classes with special handling in certain circumstances like:
- exception classes will be discussed later in chapter 8.
- attribute classes that will be discussed later in chapter 15.
- You can encapsulate a read-only set of properties easily with anonymous types without defining the type.
- They are handy when working with LINQ queries.
- Two anonymous types have same type if they specify a sequence of properties that meet the following conditions:
- properties in the same order have the same name
- properties in the same order have the same type
- both have the same number of properties
After the new keyword, write your property(s) with their value(s) within a curly braces{}. As the compiler infers the type of properties we can use the var keyword to defining a variable as an anonymous type:
//defining an anonymous type
var animal = new { Name = "Sheep", Feet = 4 };
//defining an array of anonymous types
var animalArray = new[] { new { Name = "Sheep" , Feet = 4 },
new { Name = "Swan" , Feet = 2 } };
//===Run===
Console.WriteLine( animal.Name );
Console.WriteLine( animal );
Console.WriteLine(animalArray[1]);Output:
Sheep
{ Name = Sheep, Feet = 4 }
{ Name = Swan, Feet = 2 }
-
nullis not supported -
It is derived directly from the
objectclass. -
To pass it as an argument, the corresponding parameter type can be
object. -
It is recommended to use named struct or class to store a query result or pass an anonymous type outside the method boundaries.
- You can define a type in multiple files using the
partialkeyword. - It's supported for
class,struct,interfaceandrecordtypes. - Partial types may contain partial methods.
- It can be useful while working on:
- large projects
- automatically generated codes (like Windows forms designer)
File_1.cs contains the first part of PartialClass
namespace PartialTypes
{
public partial class PartialClass
{
public partial void PM(); //PM has declared here
}
}File_2.cs contains the second part of PartialClass
namespace PartialTypes
{
public partial class PartialClass
{
int index;
public void M()
{/*body*/}
public partial void PM() // PM has defined here
{/*body*/}
}
}Partial methods allow us to declare a method in one file and implement it in the other one.
- It enables developers to declare method hooks with no implementation defined. Developers can decide later to add the implementation or not.*
- Declared but not implemented methods will be discarded from the compilation process.
- Both declaration and implementation definition signatures MUST be the same.
- To define a method as partial, use the
partialkeyword.
* If all the following conditions are met, you are eligible to omit the Implementation of a partial method, otherwise you can not :
- has no access modifier (
public,privateand so on) - returns
void - has no parameter with
outmodifier - has not defined with
virtual,override,sealed,new, orexternmodifiers
partial is not applicable to:
- constructors
- finalizers
- overloaded operators
- property declarations
- event declarations