The Essentials
Commenting
// This is a normal comment
/// This is a documentation comment
/* This is a multiline comment
It can span over multiple lines which makes it a multiline comment */
Naming conventions
In C# almost everything is PascalCase. However, use camelCasing when naming private
or internal
fields, and prefix them with _
. You also use camelCasing for method parameters.
Access Modifiers
public
The code is accessible by any other code in the same assembly or another assembly that references it.
private
The code is only accessible within the same class or struct.
protected
The code is accessible within the same class, or in a class that inherits the class containing the protected
internal
The code is only accessible within its own assembly, not from any other assembly.
protected internal
The code can be accessed by any code in the assembly in which it's declared, or from within a derived class in another assembly.
readonly
This prevents a field from being modified after construction. A read-only field can be assigned only in its declaration or within the enclosing type’s constructor.
const
A constant is evaluated statically at compile time and the compiler literally substitutes its value whenever used. A constant can be any of the built-in numeric types, bool, char, string, or an enum type.
Aliasing
Namespaces can also be aliased to allow shorter writing
using Dict = System.Collections.Generics.Dictionary<int, int>;
Types
Each type is defined as either a value type or a reference type. Just as in Java object
ist the mother of all types meaning there is type unification.
Value and reference types
A variable of a value type contains an instance of the type. This differs from a variable of a reference type, which contains a reference to an instance of the type. By default, on assignment, passing an argument to a method, and returning a method result, variable values are copied. In the case of value-type variables, the corresponding type instances are copied. Reference types comprise all class, array, delegate, and interface types. This includes string. Multiple references can point to the same object. null
means the reference points to no object.
Stack and the heap are the places where variables reside.
The stack is a block of memory for storing local variables and parameters. The stack grows and shrinks as a method or function is entered and exited.
The heap is the memory in which objects (i.e., reference-type instances) reside. The runtime has a garbage collector that periodically deallocates objects from the heap. An unreferenced object is eventually collected by the garbage collector.
Built-in types
C# provides a standard set of built-in types. These represent integers, floating point values, Boolean expressions, text characters, decimal values, and other types of data. There are also built-in string and object types. Most built-in types (all numeric types, char and bool) as well as custom struct
and enum
types are value types.
Custom types
You use the struct, class, interface, enum, and record constructs to create your own custom types.
Boxing
Converting a value type into a reference type wraps up the value of intOne from the stack in to a heap object.
int intOne = 3;
object obj= intOne;
Unboxing
converting a reference type into a value type. Unwraps the value again.
int intOne = (int) obj;
Classes
class Hello
{
private string name; // private field so camelCase
private void Greet() {...}
public static void Main(string[] args) {...}
static Hello() { /* static constructor */}
}
Static fields
Static fields are Initialized before the static constructor is called and are called on the Class not the object.
Static constructor
Executed once per type before any instances of the type are created and any other static members are accessed.
Properties
Properties look like fields from the outside, but internally they contain logic, like methods do. If only get is defined then it is a read-only property.
public class Stock
{
decimal currentPrice; // The private "backing" field
public decimal CurrentPrice // The public property
{
get { return currentPrice; } // property accessors
set { currentPrice = value; }
}
}
Stock msft = new Stock();
msft.CurrentPrice = 30;
msft.CurrentPrice -= 3;
Console.WriteLine (msft.CurrentPrice);
Automatic properties
Compiler generates a private field internally and automatically does getter and setter like in java.
class Data
{
public string CreateDate{ get; set; } = DateTime.Now; // initial value
}
Abstract classes
You can not create objects of abstract classes. Abstract methods have no implementation and are implicitly virtual meaning the need to be overwritten by the subclass.
abstract class Stream
{
public abstract voidWrite(char ch);
public void WriteString(strings)
{
foreach(char ch in s)
Write(s);
}
}
class File: Stream
{
public override voidWrite(charch)
{
...
String interpolation
int x = 4;
Console.WriteLine($"{s.Name} is {s.Width:F2}m wide and is {(s.IsRed ? "red":"not red")}");
Verbatim string literals
A verbatim string literal is prefixed with @ and does not support escape sequences and can also span multiple lines.
Console.WriteLine("\\\\server\\fileshare\\helloworld.cs");
Console.WriteLine(@"\\server\fileshare\helloworld.cs");
string escaped = "First Line\r\nSecond Line";
string verbatim = @"First Line
Second Line";
// True if your text editor uses CR-LF line separators:
Console.WriteLine(escaped == verbatim);
Console.WriteLine(@$"c:\{pathname}"); // Can combine with interpolation
Parameters
By default, arguments in C# are passed by value, this means that a copy of the value is created when passed to the method.
class Test
{
static void Foo (int p)
{
p = p + 1; // Increment p by 1
Console.WriteLine (p); // Write p to screen
}
static void Main()
{
int x = 8;
Foo (x); // Make a copy of x
Console.WriteLine (x); // x will still be 8
}
}
Passing a reference-type argument by value copies the reference, but not the object.
class Test
{
static void Foo (StringBuilder fooSB)
{
fooSB.Append ("test");
fooSB = null; // copy of reference, doesn’t make sb null.
}
static void Main()
{
StringBuilder sb = new StringBuilder();
Foo (sb);
Console.WriteLine (sb.ToString()); // test
}
}
ref modifier
To pass by reference. ref modifier is required both when writing and when calling the method.
class Test
{
static void Foo (ref int p)
{
p = p + 1; // Increment p by 1
Console.WriteLine (p); // Write p to screen
}
static void Main()
{
int x = 8;
Foo (ref x); // Ask Foo to deal directly with x
Console.WriteLine (x); // x is now 9
}
}
out modifier
An out argument is like a ref argument except for the following:
- It need not be assigned before going into the function.
- It must be assigned before it comes out of the function.
Most commonly used to get multiple return values.
out _
tells the compiler a so called discard.
class Test
{
static void Split (string name, out string firstNames, out string lastName)
{
int i = name.LastIndexOf (' ');
firstNames = name.Substring (0, i);
lastName = name.Substring (i + 1);
}
static void Main()
{
string a, b;
Split ("Stevie Ray Vaughan", out a, out b);
// Or Split ("Stevie Ray Vaughan", out string a, out string b);
// Or Split ("Stevie Ray Vaughan", out string a, out _);
Console.WriteLine (a); // Stevie Ray
Console.WriteLine (b); // Vaughan
}
}
in modifier
An in parameter is similar to a ref parameter except that value cannot be modified by the method (doing so generates a compile-time error). This is most useful when passing a large value type(some big struct) to the method because it allows the compiler to avoid the overhead of copying the argument prior to passing it in while still protecting the original value from modification.
params modifier
Specify the params parameter modifier on the last parameter so that the method accepts any number of arguments of a particular type.
class Test
{
static int Sum (params int[] ints)
{
int sum = 0;
for (int i = 0; i < ints.Length; i++)
sum += ints[i]; // Increase sum by ints[i]
return sum;
}
static void Main()
{
int total = Sum (1, 2, 3, 4);
Console.WriteLine (total); // 10
}
}
Optional parameters
void Foo (int x = 23) { Console.WriteLine (x); }
Named arguments
Go very well with optional parameters.
void Foo (int x, int y) { Console.WriteLine (x + ", " + y); }
void Test()
{
Foo (x:1, y:2); // 1, 2
}
Ref locals and returns
A local variable that references an element in an array or field in an object.
int[] numbers = { 0, 1, 2, 3, 4 };
ref int numRef = ref numbers [2];
numRef *= 10;
Console.WriteLine (numRef); // 20
Console.WriteLine (numbers [2]); // 20
You can also return a ref local, this is called a ref return. If you omit the ref modifier on the calling side, it reverts to returning an ordinary value.
static string x = "Old Value";
static ref string GetX() => ref x; // This method returns a ref
static void Main()
{
ref string xRef = ref GetX(); // Assign result to a ref local
xRef = "New Value";
Console.WriteLine (x); // New Value
}
Expression-bodied methods
A method that comprises a single expression, can be written as an expression-bodied method.
int Foo (int x) { return x * 2; }
int Foo (int x) => x * 2;
void Foo (int x) => Console.WriteLine (x); // Can also have void
Interfaces
Interface = only signatures, no implementation (apart from default implementations). Interfaces may contain methods, properties, indexers, events (no fields, constants, constructors, destructors, operators or nested types). Interface members are implicitly public abstract (virtual) and can extend other interfaces and be static. Classes and structs may implement multiple interfaces
public interface IList: ICollection, IEnumerable
{
int Add(objectvalue); //methods
bool Contains(objectvalue);
bool IsReadOnly{ get; } //property
object this[intindex] { get; set; } //indexer
void Log(stringt) {Console.WriteLine(Prefix + t);} //default impl.
static string Prefix=""; //static field
}
Jump statements
break
Same as Java. The break statement ends the execution of the body of an iteration or switch statement.
continue
The continue statement forgoes the remaining statements in a loop and makes an early start on the next iteration.
goto
The goto statement transfers execution to another label within a statement block.
int i = 1;
startLoop:
if (i <= 5)
{
Console.Write (i + " ");
i++;
goto startLoop;
}
Controls
if, else if, else
Same as Java.
while and do while
Same as Java.
for
Same as Java.
foreach
The foreach statement executes a statement or a block of statements for each element in an instance of the type that implements the System.Collections.IEnumerable
or System.Collections.Generic.IEnumerable<T>
interface.
foreach (char c in "beer")
Console.WriteLine(c);
switch
When more than one value should execute the same code, you can list the common cases sequentially. Unlike in Java there is no fall-through unless the case is empty. If you need fall-through you can use goto case/default
.
switch (cardNumber)
{
case 13:
case 12:
case 11:
Console.WriteLine ("Face card");
break;
default:
Console.WriteLine ("Plain card");
break;
}
Pattern matching
Switching on a type is a special case of switching on a pattern. Each case clause specifies a type upon which to match, and a variable upon which to assign the typed value if the match succeeds (the “pattern” variable). The compiler lets us consume the pattern variables only in the when clauses.
object o = "ecnf"
switch(o)
{
case byte b:
Console .WriteLine($"I’m a byte with value {b});
break;
case string s when s == "ecnf"
Console .WriteLine("I’m THE ecnf string");
break;
case string s:
Console .WriteLine("I’m a string that contains {0}", s);
break;
default:
Console.WriteLine("Don’t know anything");
break;
}
Switch expressions
Switches can also switch on multiple values.
string module = "ecnf";
char grade = ‘B’;
string msg= (module, grade) switch
{
("ecnf",’A’) => "Congrats, regards Yves Senn",
("ecnf",’B’) => "Very good, regards Yves Senn",
("ecnf",’C’) => "Good job, regards Yves Senn",
("oop1",’A’) => "Nice one, regards Dieter Holz",
("oop1",’B’) => "Well done, regards Dieter Holz",
("oop1",’C’) => "Good job, regards Dieter Holz",
_ => throw new InvalidArgumentException() // equivalent to default
}
Type checks and conversions
Implicit conversions are allowed when both of the following are true:
- The compiler can guarantee that they will always succeed.
- No information is lost in conversion.
Otherwise you need to use an explicit conversion
int x = 12345; // int is a 32-bit integer
long y = x; // Implicit conversion to 64-bit integer
short z = (short)x; // Explicit conversion to 16-bit integer
is operator
Checks whether an object is compatible with a given type and returns a Boolean. If the object reference is null it returns false.
Object o = new Object();
Boolean b1 = (o is Object); // b1 is true.
Boolean b2 = (o is Employee); // b2 is false.
The is operator is typically used as follows. However this causes 2 checks which can have an effect on performance.
if (o is Employee) {
Employee e = (Employee) o;
}
It can also be used like this to make life easy:
if(b is D a)
{
a.Foo("blBLA");
}
as operator
if o is compatible with the type returns a Employee as non-null reference to the same object. If o is not compatible with the type, returns null.
Warning as
operator will never throw an exception!!!!
Object o = new Object(); // Creates a new Object object
Employee e = o as Employee; // Casts o to an Employee
// The cast above fails: no exception is thrown, but e is set to null.
Conditional operator (ternary operator)
Has the form q ? a : b;
thus, if condition q is true, a is evaluated, else b is evaluated:
static int Max (int a, int b)
{
return (a > b) ? a : b;
}
Null operators
Null-Coalescing Operator
The ?? operator is the null-coalescing operator. It says, “If the operand to the left is non-null, give it to me; otherwise, give me another value.”
string s1 = null;
string s2 = s1 ?? "nothing"; // s2 evaluates to "nothing"
Null-Coalescing Assignment Operator
The ??= operator is the null-coalescing assignment operator. It says, “If the operand to the left is null, assign the right operand to the left operand.”
string s1 = null;
s1 ??= "something";
Console.WriteLine (s1); // something
s1 ??= "everything";
Console.WriteLine (s1); // something
// instead of
if (myVariable == null) myVariable = someDefault;
Null-Conditional Operator
The ?. operator is the null-conditional or “Elvis” operator. if the operand on the left is null, the expression evaluates to null instead of throwing a NullReferenceException.
System.Text.StringBuilder sb = null;
string s = sb?.ToString(); // No error; s instead evaluates to null
// same as
string s = (sb == null ? null : sb.ToString());
You can also use the null-conditional operator to call a void method:
someObject?.SomeVoidMethod();
If someObject is null, this becomes a “no-operation” rather than throwing a Null ReferenceException.
Nullable value types
int? length = sb?.ToString().Length; // OK: int? can be null