.Net Type
How to improve code readability?
Syntactic sugar
Syntactic sugar is syntax within a programming language that is designed to make things easier to read or to express.
Operator, Expression, and Statement
int num = 2*3;
Operator
C# provides a number of operators. Many of them are supported by the built-in types and allow you to perform basic operations with values of those types
Expression
The simplest C# expressions are literals (for example, integer and real numbers) and names of variables. You can combine them into complex expressions by using operators. Operator precedence and associativity determine the order in which the operations in an expression are performed. You can use parentheses to change the order of evaluation imposed by operator precedence and associativity.
Statement
The actions that a program takes are expressed in statements. Common actions include declaring variables, assigning values, calling methods, looping through collections, and branching to one or another block of code, depending on a given condition.
Lambda Expression and Anonymous Functions Doc
You use a lambda expression to create an anonymous function. Use the lambda declaration operator => to separate the lambda’s parameter list from its body. A lambda expression can be of any of the following two forms:
- Expression lambda that has an expression as its body:
(input-parameters) => expression
- Statement lambda that has a statement block as its body:
(input-parameters) => { <sequence-of-statements> }
// Lambda Expression
using System;
namespace MyBusiness
{
class Program
{
static void Main(string[] args)
{
// Local function
bool compareMethod(int a, int b)
{
return a == b;
}
Console.WriteLine(compareMethod(3, 4));
// Expression lambda
bool compareLambda(int a, int b) => (a == b);
Console.WriteLine(compareLambda(3, 4));
// Print a Fibonacci sequence
for (int i = 0; i < 10; i++)
{
Console.WriteLine("The {0} term of the Fibonacci sequence is {1:N0}.",
arg0: i + 1,
// arg1: FibMethod(term: i));
arg1: FibLambda(term: i));
// N0: The numeric ("N") format specifier converts a number to a string of the form. N0 does not represent any decimal place but rounding is applied to it.
}
}
// Fibonacci sequence
static int FibMethod(int term)
{
switch (term)
{
case 0:
return 0;
case 1:
return 1;
default:
return FibMethod(term - 1) + FibMethod(term - 2);
}
}
// Fibonacci sequence
// Statement lambda
static int FibLambda(int term) => term switch
{
0 => 0,
1 => 1,
_ => FibLambda(term - 1) + FibLambda(term - 2)
};
}
}
Delegates Doc
Basic Delegates
A delegate is a type that represents references to methods with a particular parameter list and return type. When you instantiate a delegate, you can associate its instance with any method with a compatible signature (the method name, and the type and order of parameters) and return type. You can invoke (or call) the method through the delegate instance.
using System;
namespace MyBusiness
{
class Program
{
// Delegate declaration
public delegate int[] GenerateMyNumbers(int x, int y);
static void Main(string[] args)
{
// Create delegate objects/instances, where you put the corresponding methods as input parameters.
GenerateMyNumbers generateRandom = new GenerateMyNumbers(GetRandomNumber);
GenerateMyNumbers generateOrdered = new GenerateMyNumbers(GetOrderedNumber);
// int[] numbers = generateRandom(10, 3);
int[] numbers = generateOrdered(10, 3);
foreach (int n in numbers)
{
Console.Write(n + " ");
}
Console.WriteLine();
}
// Create an array with size amount and assign a random value 0-maxNum in this array
public static int[] GetRandomNumber(int maxNum, int amount)
{
Random random = new Random();
int[] nums = new int[amount];
for (int i = 0; i < amount; i++)
{
// 0 ~ maxNum-1
nums[i] = random.Next(0, maxNum);
}
return nums;
}
// Get an ordered integer sequence from min to max
public static int[] GetOrderedNumber(int max, int min)
{
// Avoid when max is smaller than min
if (max < min)
{
int[] noNum = { 0 };
return noNum;
}
// Create an ordered sequence
int[] nums = new int[max - min + 1];
for (int i = 0; i <= max - min; i++)
{
nums[i] = min + i;
}
return nums;
}
}
}
$ 3 4 5 6 7 8 9 10
- Delegates are used to pass methods as arguments to other methods.
- Delegates can be used to define callback methods.
- Delegates can be chained together; for example, multiple methods can be called on a single event.
- Lambda expressions (in certain contexts) are compiled to delegate types.
using System;
namespace MyBusiness
{
class Program
{
static void Main(string[] args)
{
int[] array = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// string.Join: method that lets you concatenate each element in an
// object array without explicitly converting its elements to strings
Console.WriteLine(string.Join(",", AddOne(array)));
Console.WriteLine(string.Join(",", MultiplyTwo(array)));
Console.WriteLine(string.Join(",", Square(array)));
}
// Take out each element in an array and +1
public static int[] AddOne(int[] _array)
{
int[] array = new int[_array.Length];
for (int i = 0, c = _array.Length; i < c; i++)
{
array[i] = _array[i] + 1;
}
return array;
}
// Take out each element in an array and *2
public static int[] MultiplyTwo(int[] _array)
{
int[] array = new int[_array.Length];
for (int i = 0, c = _array.Length; i < c; i++)
{
array[i] = _array[i] * 2;
}
return array;
}
// Take out each element in an array and square it
public static int[] Square(int[] _array)
{
int[] array = new int[_array.Length];
for (int i = 0, c = _array.Length; i < c; i++)
{
array[i] = _array[i] * _array[i];
}
return array;
}
}
}
$ 2,3,4,5,6,7,8,9,10
$ 2,4,6,8,10,12,14,16,18
$ 1,4,9,16,25,36,49,64,81
using System;
namespace MyBusiness
{
class Program
{
// Declaration of the delegete
public delegate int ChangeValueDelegate(int x);
static void Main(string[] args)
{
int[] array = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// Create three delegate objects that take differentmethods as parameters
ChangeValueDelegate myDelegate1 = new ChangeValueDelegate(AddOne);
ChangeValueDelegate myDelegate2 = new ChangeValueDelegate(MultiplyTwo);
ChangeValueDelegate myDelegate3 = new ChangeValueDelegate(Square);
// The following is also a valid syntax;
// ChangeValueDelegate myDelegate3 = Square;
// string.Join: method that lets you concatenate each element in an
// object array without explicitly converting its elements to strings
Console.WriteLine(string.Join(",", Change(array, myDelegate1)));
Console.WriteLine(string.Join(",", Change(array, myDelegate2)));
Console.WriteLine(string.Join(",", Change(array, myDelegate3)));
}
public static int AddOne(int number)
{
return number + 1;
}
public static int MultiplyTwo(int number)
{
return number * 2;
}
public static int Square(int number)
{
return number * number;
}
// The following is also a valid syntax using lambda expression
// public static int Square(int number) => number * number;
// A method Change that takes another method as the input parameters
// In this case, the method (as a parameter) should be defined as a delegate
public static int[] Change(int[] _array, ChangeValueDelegate changeValue)
{
int[] array = new int[_array.Length];
for (int i = 0, c = _array.Length; i < c; i++)
{
array[i] = changeValue(_array[i]);
}
return array;
}
}
}
$ 2,3,4,5,6,7,8,9,10
$ 2,4,6,8,10,12,14,16,18
$ 1,4,9,16,25,36,49,64,81
Lambda Expression with Delegates
public static int Square(int number) => number * number;
Generic Delegates
public delegate T ChangeValueDelegate<T>(T x);
Action, Func, and Predicate Delegates
- Action Delegate: Encapsulates a method that does not return a value. Doc
using System;
namespace MyBusiness
{
class Program
{
static void Main(string[] args)
{
Action<int, int> Addition = new Action<int, int>(AddNumbers);
// Alternative
// Action<int, int> Addition = AddNumbers;
Addition(10, 20);
}
// add param1 and param2 and return the sum
private static void AddNumbers(int param1, int param2)
{
int result = param1 + param2;
Console.WriteLine($"Addition result = {result}");
}
}
}
$ Addition result = 30
- Func<T,TResult> Delegate: Encapsulates a method that has parameters (up to 16) and returns a value of the type specified by the TResult parameter. DocExample
using System;
namespace MyBusiness
{
class Program
{
static void Main(string[] args)
{
// Declare a Func delegate
Func<int, int, int> Addition = new Func<int, int, int>(AddNumbers);
// Func<int, int, int> Addition = AddNumbers;
// Using Lambda Expression
// Func<int, int, int> Addition = (param1, param2) => param1 + param2;
int result = Addition(10, 20);
Console.WriteLine($"Addition result = {result}");
}
// add param1 and param2 and return the sum
private static int AddNumbers(int param1, int param2)
{
return param1 + param2;
}
}
}
$ Addition result = 30
- Advantages of Action and Func Delegates
- Easy and quick to define delegates.
- Makes code short.
- Compatible type throughout the application.
- Predicate Delegate: Represents the method that defines a set of criteria and determines whether the specified object meets those criteria. Doc
Syntax difference between predicate & func is that here in predicate, you can only take one input parameter and you don’t specify a return type because it is always a bool.
using System;
namespace MyBusiness
{
class Program
{
static void Main(string[] args)
{
Predicate<string> CheckIfApple = new Predicate<string>(IsApple);
// Predicate<string> CheckIfApple = IsApple;
bool result = IsApple("IPhone X");
if (result){
Console.WriteLine("It's an IPhone");
}
}
private static bool IsApple(string modelName)
{
// Check if the model name an iPhone
if (modelName == "IPhone X")
return true;
else
return false;
}
}
}
$ It\'s an IPhone
Delegate exercise: magic item damage calculator
Starting Code
using System;
delegate int DamageModifier(int baseDamage);
class Program
{
static void Main()
{
// Character base stats
int strength = 10;
int dexterity = 12;
int intelligence = 8;
//Randomizer
Random rng = new();
// Modifier dictionary: each string maps to a lambda function that modifies damage
Dictionary<string, DamageModifier> modifiers = new Dictionary<string, DamageModifier>
{
["giant"] = dmg => dmg + strength,
["random"] = dmg => dmg * rng.Next(0, 2)*2,
["brutal"] = dmg => (dmg * dmg)/5
};
int baseDamage = 10;
string modifier = "giant";
int finalDamage = modifiers[modifier](baseDamage);
Console.WriteLine($"Final Damage with {modifier} modifier: {finalDamage}");
}
}
Final Code
using System;
class BaseWeapon
{
public int BaseDamage { get; set; }
public double AttackSpeed { get; set; }
public double CalculateDPS(DamageModifier damageModifier, double durationInSeconds)
{
int attackCount = (int)(durationInSeconds / AttackSpeed);
double totalDamage = 0;
for (int i = 0; i < attackCount; i++)
{
totalDamage += damageModifier(this.BaseDamage);
}
return totalDamage / durationInSeconds;
}
}
//Delegate used to calculate composite damage
//equivalent to Func<int,int>
delegate int DamageModifier(int baseDamage);
class Program
{
static void Main()
{
// Character base stats
int strength = 10;
int dexterity = 12;
int intelligence = 8;
//Randomizer
Random rng = new();
//Base weapons dictionary
var weapons = new Dictionary<string,BaseWeapon>
{
["sword"] = new BaseWeapon { BaseDamage = 10, AttackSpeed = 1.0 },
["dagger"] = new BaseWeapon { BaseDamage = 5, AttackSpeed = 0.5 },
["axe"] = new BaseWeapon { BaseDamage = 20, AttackSpeed = 2.0 }
};
// Modifier dictionary: each string maps to a lambda function that modifies damage
Dictionary<string, DamageModifier> modifiers = new Dictionary<string, DamageModifier>
{
["giant"] = dmg => dmg + strength,
["random"] = dmg => dmg * rng.Next(0, 2)*2,
["brutal"] = dmg => (dmg * dmg)/5
};
// Example item with modifiers
List<string> itemModifiers = ["giant","brutal"];
string baseWeaponName = "dagger";
// Compose damage function
DamageModifier calculateDamage = dmg => dmg;
foreach (var mod in itemModifiers)
{
if (modifiers.ContainsKey(mod))
{
DamageModifier modFunc = modifiers[mod];
DamageModifier previousFunc = calculateDamage;
// Compose previous with new modifier
calculateDamage = dmg => modFunc(previousFunc(dmg));
}
}
int finalDamage = calculateDamage(weapons[baseWeaponName].BaseDamage);
string modifierLabel = string.Join(" ", itemModifiers);
Console.WriteLine($"Final Damage for the {modifierLabel} {baseWeaponName}: {finalDamage}");
Console.WriteLine($"DPS: {weapons[baseWeaponName].CalculateDPS(calculateDamage, 100000)}");
}
}
Type-testing Operators and Cast Expression
Object Class
Definition: Supports all classes in the .NET class hierarchy and provides low-level services to derived classes. This is the ultimate base class of all .NET classes; it is the root of the type hierarchy.
object myObject1 = new Cat();
// You can use a base/parent class to refer to your object
Animal animal1 = new Dog();
object myObject2 = animal1;
Problem: How do we know/guarantee what information has been stored in the object?
Implicit Casting
// Implicit casting
Cat nana = new Cat();
WildCat leopard = new WildCat();
Cat petCat = leopard;
Console.Write($"{petCat.Name} speaks ");
petCat.Speak();
// Compile error
// WildCat wildCat = petCat;
Explicit Casting
// Explicit casting
WildCat wildCat = (WildCat)petCat;
// Runtime error
// WildCat wildCat = (WildCat)nana;
Console.Write($"{wildCat.Name} speaks ");
wildCat.Speak();
public class Cat
{
public string Name = "ACat";
public virtual void Speak()
{
Console.WriteLine("Meow!");
}
}
public class WildCat : Cat
{
public int Age = 0;
public override void Speak()
{
Console.WriteLine("WildMeow!");
}
}
You can also overload the implicit and explicit operators in C# Doc.
Avoiding Casting Exceptions
// Use is operator
if (petCat is WildCat)
{
Console.WriteLine($"{petCat.Name} IS an WildCat");
WildCat wCat = (WildCat)petCat;
// safely do something with wCat
}
is Operator
The is operator will check if the run-time type of an expression is compatible with a given type. The as operator considers only reference, nullable, boxing, and unboxing conversions.
as Operator
The as operator is used to explicitly convert an expression to a given type if its run-time type is compatible with that type. The as operator returns null if the conversion is not possible.
In MyBusiness/Program.cs,
namespace MyBusiness;
class Program
{
static void Main(string[] args)
{
object myObject1 = new Cat();
// You can use a base/parent class to refer to your object
Animal animal1 = new Dog();
object myObject2 = animal1;
// Implicit casting
Cat nana = new Cat();
WildCat leopard = new WildCat();
Cat petCat = leopard;
Console.Write($"{petCat.Name} speaks ");
petCat.Speak();
// Compile error
// WildCat wildCat = petCat;
// Explicit casting
WildCat wildCat = (WildCat)petCat;
// Runtime error
// WildCat wildCat = (WildCat)nana;
Console.Write($"{wildCat.Name} speaks ");
wildCat.Speak();
// Use is operator to safely check if the types are compatible
if (petCat is WildCat)
{
Console.WriteLine($"{petCat.Name} IS an WildCat");
WildCat wCat = (WildCat)petCat;
// safely do something with wCat
}
// object is a base class of all derived classes in C#
object[] myObjects = new object[4];
myObjects[0] = new Dog();
myObjects[1] = new Cat();
myObjects[2] = "StringDog";
myObjects[3] = "StringCat";
for (int i = 0; i < myObjects.Length; i++)
{
// Convert an element in the myobjects array to a string element
string? s = myObjects[i] as string;
// Print out the current element
Console.Write($"Inspecting element: {myObjects[i]}");
// If converted successfully, s will be a string, otherwise return null.
if (s == null)
{
Console.Write(" --> Incompatible type");
}
else
{
Console.Write(" --> Compatible type");
}
Console.WriteLine(", with string!");
}
}
}
In AnimalLibrary/Animals.cs,
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("#$&%");
}
}
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Woof!");
}
}
public class Cat : Animal
{
public string Name = "UnknownCat";
public override void Speak()
{
Console.WriteLine("Meow!");
}
}
public class WildCat : Cat
{
public override void Speak()
{
Console.WriteLine("WildMeow!");
}
}
Environment.SpecialFolder Enum Doc
Other Interesting Types
- System.Numerics (BigInteger, Complex, Quaternion)