CSharp - herougo/SoftwareEngineerKnowledgeRepository GitHub Wiki
https://google.github.io/styleguide/csharp-style.html
interface IAnimal {
public void Eat();
}
class FlyingAnimal : IAnimal {
private int _stomachContents;
public string Mood;
public void Eat() {
_stomachContents += 1;
int localVar = 0;
Mood = "happy";
}
}- virtual - used for making sure the proper version of a method is called depending on the type (see example)
- abstract - used for classes and abstract methods (no implementation)
- override - used for overriding virtual and abstract methods
- internal - accessible only within files in the same assembly (e.g. dll)
- partial - used to split class contents across different sections/files
-
sealed - classes cannot inherit this class (e.g.
sealed class SealedClass) - read-only - cannot be modified after the field is initialized at run-time (different from const)
- ref - like C++ reference (even setting the argument to something else changes what was passed into the function)
- in - used to state that a parameter cannot be modified by the method (i.e. overwritten parameter)
- out - used to state that a parameter MUST be modified by the method (useful for returning multiple values)
class Animal {
public void eat() {...}
}
class FlyingAnimal : Animal {
public void eat() {...}
}
Animal a = new FlyingAnimal();
a.eat(); // calls Animal.eat (not FlyingAnimal.eat)
class AnimalV2 {
public virtual void eat() {...}
}
class FlyingAnimalV2 : AnimalV2 {
public virtual void eat() {...}
}
AnimalV2 a = new FlyingAnimalV2();
a.eat(); // calls FlyingAnimalV2.eatabstract class Shape
{
public abstract int GetArea();
}
class Square : Shape
{
private int _side;
public Square(int n) => _side = n;
// GetArea method is required to avoid a compile-time error.
public override int GetArea() => _side * _side;
}public partial class Employee
{
public void DoWork()
{
}
}
public partial class Employee
{
public void GoToLunch()
{
}
}An interface is a completely "abstract class", which can only contain abstract methods and properties (with empty bodies):
// interface
interface IAnimal
{
void animalSound(); // interface method (does not have a body)
void run(); // interface method (does not have a body)
}They look like they're methods of the class, but they are defined elsewhere.
namespace ExtensionMethods
{
public static class MyStringExtensions
{
public static int WordCount(this string str)
{
return str.Split(new char[] { ' ', '.', '?' },
StringSplitOptions.RemoveEmptyEntries).Length;
}
}
}
/*
string s = "Hello Extension Methods";
int i = s.WordCount();
*/Notes:
- If we have
Class1 c = new SubClassOfClass1(); c.MyExtensionMethod();, the Class1 implementation is used.
Source:
class ReferenceTypeExample { static void Enroll(ref Student student) { // With ref, all three lines below alter the student variable outside the method. student.Enrolled = true; student = new Student(); student.Enrolled = false; } }
class ReferenceTypeExample
{
static void Enroll(in Student student)
{
// With in assigning a new object would throw an error
// student = new Student();
// We can still do this with reference types though
student.Enrolled = true;
}
static void Main()
{
var student = new Student
{
Name = "Susan",
Enrolled = false
};
Enroll(student);
}
}class ReferenceTypeExample
{
static void Enroll(out Student student)
{
//We need to initialize the variable in the method before we can do anything
student = new Student();
student.Enrolled = false;
}
static void Main()
{
Student student;
Enroll(out student); // student will be equal to the value in Enroll. Name will be null and Enrolled will be false.
}
}Source:
- Record - basically classes (without methods) with built-in immutability, tostring, and equality
-
record classandrecordmean the same thing - positional record type (short form).
record CarRecord(string Make, string Model, string Color);
...
CarRecord car = new CarRecord("Ford", "First", "Blue");
string make = car.Make; // can access properties like this- Non-destructive mutation:
CarRecord ourOtherCar = myCarRecord with {Model = "Odyssey"}; - record structs vs records: One major difference between record structs and records is that a record struct is mutable by default. To make a record struct immutable, you must use add the readonly modifier (e.g.
public readonly record struct ReadOnlyPoint(double X, double Y, double Z);).
.NET delegates can point to either static or instance methods.
// This delegate can point to any method,
// taking two integers and returning an integer.
public delegate int BinaryOp(int x, int y);When the C# compiler processes delegate types, it automatically generates a sealed class deriving from System.MulticastDelegate. This class (in conjunction with its base class, System.Delegate) provides the necessary infrastructure for the delegate to hold onto a list of methods to be invoked later.
While BeginInvoke() and EndInvoke() are generated, they are not supported when running your code under .NET Core or .NET 5+. This can be frustrating, since you will not receive a compiler error but a runtime error if you use them.
//SimpleMath.cs
namespace SimpleDelegate;
// This class contains methods BinaryOp will
// point to.
public class SimpleMath
{
public static int Add(int x, int y) => x + y;
public static int Subtract(int x, int y) => x - y;
}
//Program.cs
using SimpleDelegate;
Console.WriteLine("***** Simple Delegate Example *****\n");
// Create a BinaryOp delegate object that
// "points to" SimpleMath.Add().
BinaryOp b = new BinaryOp(SimpleMath.Add);
// Invoke Add() method indirectly using delegate object (calls Invoke method under the hood).
Console.WriteLine("10 + 10 is {0}", b(10, 10));
Console.ReadLine();
//Additional type definitions must be placed at the end of the
// top-level statements
// This delegate can point to any method,
// taking two integers and returning an integer.
public delegate int BinaryOp(int x, int y);Additional type declarations (in this example the BinaryOp delegate) must come after all top-level statements.
// Delegates can also point to instance methods as well.
SimpleMath m = new SimpleMath();
BinaryOp b = new BinaryOp(m.Add);public class Car
{
...
// 1) Define a delegate type.
public delegate void CarEngineHandler(string msgForCaller);
// 2) Define a member variable of this delegate.
private CarEngineHandler _listOfHandlers;
// 3) Add registration function for the caller.
public void RegisterWithCarEngine(CarEngineHandler methodToCall)
{
// uses the combine method under the hood
_listOfHandlers += methodToCall;
}
public void UnRegisterWithCarEngine(CarEngineHandler methodToCall)
{
_listOfHandlers -= methodToCall;
}
// 4) Implement the Accelerate() method to invoke the delegate's
// invocation list under the correct circumstances.
public void Accelerate(int delta)
{
// If this car is "dead," send dead message.
if (_carIsDead)
{
_listOfHandlers?.Invoke("Sorry, this car is dead...");
}
else
{
CurrentSpeed += delta;
// Is this car "almost dead"?
if (10 == (MaxSpeed - CurrentSpeed))
{
_listOfHandlers?.Invoke("Careful buddy! Gonna blow!");
}
if (CurrentSpeed >= MaxSpeed)
{
_carIsDead = true;
}
else
{
Console.WriteLine("CurrentSpeed = {0}", CurrentSpeed);
}
}
}
}
Console.WriteLine("** Delegates as event enablers **\n");
// First, make a Car object.
Car c1 = new Car("SlugBug", 100, 10);
// Now, tell the car which method to call
// when it wants to send us messages.
c1.RegisterWithCarEngine(
new Car.CarEngineHandler(OnCarEngineEvent));
// Speed up (this will trigger the events).
Console.WriteLine("***** Speeding up *****");
for (int i = 0; i < 6; i++)
{
c1.Accelerate(20);
}
Console.ReadLine();
// This is the target for incoming events.
static void OnCarEngineEvent(string msg)
{
Console.WriteLine("\n*** Message From Car Object ***");
Console.WriteLine("=> {0}", msg);
Console.WriteLine("********************\n");
}// This generic delegate can represent any method
// returning void and taking a single parameter of type T.
public delegate void MyGenericDelegate<T>(T arg);Action - a delegate that returns void.
Func - a delegate that has a return type (Func<input type 1, input type 2, output type>)
In any case, given that Action<> and Func<> can save you the step of manually defining a custom delegate, you might be wondering if you should use them all the time. The answer, like so many aspects of programming, is “it depends.” In many cases, Action<> and Func<> will be the preferred course of action (no pun intended). However, if you need a delegate that has a custom name that you feel helps better capture your problem domain, building a custom delegate is as simple as a single code statement. You’ll see both approaches as you work over the remainder of this text.
As a shortcut, so you don’t have to build custom methods to add or remove methods to a delegate’s invocation list, C# provides the event keyword. When the compiler processes the event keyword, you are automatically provided with registration and un-registration methods, as well as any necessary member variables for your delegate types. These delegate member variables are always declared private, and, therefore, they are not directly exposed from the object firing the event. To be sure, the event keyword can be used to simplify how a custom class sends out notifications to external objects.
Use += and -= for registering and unregistering.
An Event declaration adds a layer of abstraction and protection on the delegate instance. This protection prevents clients of the delegate from resetting the delegate and its invocation list and only allows adding or removing targets from the invocation list.
public class CarEventArgs : EventArgs
{
public readonly string Msg;
public CarEventArgs(string message)
{
Msg = message;
}
}
public class Car
{
public delegate void CarEngineHandler(object sender, CarEventArgs e);
...
public void Accelerate(int delta)
{
// If the car is dead, fire Exploded event.
if (carIsDead)
{
Exploded?.Invoke(this, new CarEventArgs("Sorry, this car is dead..."));
}
...
}
}Given that so many custom delegates take an object as the first parameter and an EventArgs descendant as the second, you could further streamline the previous example by using the generic EventHandler type, where T is your custom EventArgs type. Consider the following update to the Car type (notice how you no longer need to define a custom delegate type at all).
public class Car
{
...
public event EventHandler<CarEventArgs> Exploded;
public event EventHandler<CarEventArgs> AboutToBlow;
}Since custom methods for delegates are only used for the delegate invokation, they can simply be defined within the code like a lambda function.
// Register event handlers as anonymous methods.
c1.AboutToBlow += delegate
{
Console.WriteLine("Eek! Going too fast!");
};
c1.AboutToBlow += delegate(object sender, CarEventArgs e)
{
Console.WriteLine("Message from Car: {0}", e.msg);
};Outer variables - local variables in the method that defines the anonymous method.
Note:
- An anonymous method cannot access ref or out parameters of the defining method.
- An anonymous method cannot have a local variable with the same name as a local variable in the outer method.
- An anonymous method CAN access instance variables (or static variables, as appropriate) in the outer class scope.
- An anonymous method can declare local variables with the same name as outer class member variables (the local variables have a distinct scope and hide the outer class member variables).
In C# 9.0, using static delegate makes sure the method cannot access variables outside its scope.
c1.AboutToBlow += static delegate
{
//This causes a compile error because it is marked static
aboutToBlowCounter++;
};Func<int,int,int> constant = delegate (int _, int _) {return 42;};Lambda expressions can be used to simplify calls even more. When you use lambda syntax, there is no trace of the underlying delegate object whatsoever.
Lambda expressions can be used anywhere you would have used an anonymous method or a strongly typed delegate (typically with far fewer keystrokes). Under the hood, the C# compiler translates the expression into a standard anonymous method making use of the Predicate delegate type (which can be verified using ildasm.exe or reflector.exe).
// "i" is our parameter list.
// "(i % 2) == 0" is our statement set to process "i".
List<int> evenNumbers = list.FindAll(i => (i % 2) == 0);The parameters of a lambda expression can be explicitly or implicitly typed. Currently, the underlying data type representing the i parameter (an integer) is determined implicitly. The compiler can figure out that i is an integer based on the context of the overall lambda expression and the underlying delegate. However, it is also possible to explicitly define the type of each parameter in the expression by wrapping the data type and variable name in a pair of parentheses, as follows:
// Now, explicitly state the parameter type.
List<int> evenNumbers = list.FindAll((int i) => (i % 2) == 0);var outerVariable = 0;
Func<int, int, bool> DoWork = static (x,y) =>
{
//Compile error since it’s accessing an outer variable
outerVariable++;
return true;
};Questions
- Does the compiler create a delegate object under the hood when using method group conversion?
ChatGPT: "Yes"
- How are delegates registered and unregistered?
- C# extension methods all you to add functions like i.GreaterThan(0);
- Keyboard shortcuts
- extract method - highlight and Ctrl+R Ctrl+M (refactor method)
- property - type "prop" then hit tab twice
- also works for "ctor" and "dtor"
- Testing a console app
- move logic into a class and test the class
- source: https://stackoverflow.com/questions/43160873/how-to-unit-test-a-console-program-that-reads-input-and-writes-to-the-console
- "Mocking" a function that uses Console.ReadLine
- put readline in its own class
- write a subclass in the test (public class TestNameRetriever : ConsoleNameRetriever)
- https://stackoverflow.com/questions/3161341/c-sharp-unit-test-for-a-method-which-calls-console-readline