ruk·si

C# Guide

Updated at 2014-09-28 13:42

This guide contains basic syntax and tips how to write C# code.

C# is an object-oriented programming language that has major influence on Windows environments, even though 3rd party compiler implementations exist for other environments.

Architecture

Specify what namespaces you are utilizing with using.

using System;
using System.Collections.Generic;

C# files end with .cs. Each file should contain one public class that has the same name as the file. It is not enforced, only a good practice.

// ClassName.cs
public class ClassName
{
    // ...
}

Applications start from the public static Main method.

class MyProgram
{
    public static void Main(string[] args)
    {
        // Program starts here.
    }
}

Treat all compiler warnings as errors. Fix them as you encounter them, they are there for a reason.

class Account
{
    int myId;
    int Id;                 // warning

    Account(int id) {
        this.myId = Id;     // OOPS!
    }
}

Code Layout

Prefer indention of 4 spaces. No tabs.

// good
public string GetSomething()
{
    return something;
}

Control statement keyword is followed by one space.

// bad
if(true) {
  //...
}

// good
if (true) {
    //...
}

Opening curly brace starts a new row. Applies to all keywords and control structures.

// bad
if (secret == true) {
    //..
}

// good
if (secret == true)
{
    //..
}

Use namespaces and indent them.

namespace Company.Project.Section
{
    public class MyClass
    {
        // ...
    }
}

Comments

Prefer // for the most comments.

// single-line comment

Use /* */ for comments that go multiple lines.

/*
 * Multi-line comments look like this. They work in C89 as well.
 */

Use /// for formal comments.

/// <summary>
/// This is an XML documentation comment.
/// </summary>

Variables

Prefer var over specifying data types. You do not need to specify data type on variable if compiler can figure it out from the context.

var i = 10; // implicitly typed
int i = 10; // explicitly typed

Use null-coalescing operator ??. Used to define default values for variables.

// y = x, but if (x == null), then y = -1.
int y = x ?? -1;

Use const. You can make fields immutable with const.

const int ELITE_CODE = 1337;

Use readonly. You can make fields changeable only on constructor or field initializer with readonly.

public class Age
{
    public readonly int year;
    public Age(int year)
    {
        this.year = year;
    }
    public void ChangeYear()
    {
        //this.year = 1967; // Compile error if uncommented.
    }
}

Use volatile. You can specify that a field might be modified by multiple threads with volatile. Disables some compiler optimizations that are meant only for single thread access.

class VolatileTest
{
    public volatile int number;
    public void Test(int number)
    {
        this.number = number;
    }
}

Keep an eye out for variables passed by reference. In C#, the decision if a variable passed by value or reference is made by the programmer who wrote the object.

// Passed by value, a struct.
Point point1 = new Point(20, 30);
Point point2 = point1;
point2.X = 50;
Console.WriteLine(point1.X);       // 20 (does this surprise you?)
Console.WriteLine(point2.X);       // 50

// Passed by reference, a class.
Pen pen1 = new Pen(Color.Black);
Pen pen2 = pen1;
pen2.Color = Color.Blue;
Console.WriteLine(pen1.Color);     // Blue (or does this surprise you?)
Console.WriteLine(pen2.Color);     // Blue

// Value types can't be null, they initialize to their default value.
class Program {
    static Point point1;
    static Pen pen1;
    static void Main(string[] args) {
        Console.WriteLine(pen1 == null);      // True
        Console.WriteLine(point1 == null);    // False
    }
}

Data Types

// Signed byte is between -128 and 127, like byte in Java. (8-bit)
sbyte mySbyte = -10;
// Byte is between 0 and 255. (8-bit)
byte myByte = 133;

// Short is between -32,768 and 32,767. (16-bit)
short myShort = -10000;
// Unsigned short is between 0 and 65,535. (16-bit)
ushort myUshort = 10000;

// Integer is between -2,147,483,648 and 2,147,483,647. (32-bit)
int myInt = -1;
// Unsigned integer is between 0 and 4,294,967,295. (32-bit)
uint myUint = 1U;

// Long is between -9,223,372,036,854,775,808 and 9,223,372,036,854,775,807.
long myLong = 100000L;
// Unsigned long is between 0 and 18,446,744,073,709,551,615. (64-bit)
ulong myUlong = 100000UL;

// Float is a single-precision value with precision of 7 digits.
// If number is not followed by `f`, it is treated as a double.
float myFloat = 234.5F;
// Double is a double-precision value with precision of 15-16 digits.
double myDouble = 123.4;
// Decimal is high-precision value used to financial calculations.
decimal myDecimal = 150.3M;

// Boolean
bool myBoolean = true;

// Object, all types inherit directly or indirectly from Object.
object myObject = new object();

// Char is a single 16-bit Unicode character.
char myChar = 'A';
// String is a reference type so it can be null.
// You can access string characters with an index.
string myString = "\"escape\" quotes and add \n (newlines) and \t (tabs)";

All data types have default value a.k.a. "zero value".

// T myValue = default(T);
int myValue = default(int); // => 0.
bool myValue = default(bool); // => false

You can make most data types nullable with ?.

int? number = null;

Verbatim string @ allows ignoring escape characters in a string.

string myString = @"cat
             dog
             fish";
Console.Write(myString);

Checking for empty string.

if ( String.IsNullOrEmpty(str) )
    // str is null, emtpy string or only contains whitespaces
else
    // str has content
}

Use string formatting.

string myString = String.Format("Check Check, {0}-{1}, {0}-{1:0.0}", 1, 2);
Console.Write(myString);
// => Check Check, 1-2, 1-2.0

Cast using helper methods. You can convert variables from one data type to other using helper methods.

// This will throw System.FormatException on failure.
int.Parse("123");

// This will return an error boolean.
int tryInt;
if (int.TryParse("123", out tryInt))
{
    Console.WriteLine(tryInt);
}
else
{
    Console.WriteLine("Parsing Failed!");
}

// System.Convert has methods for all everyday casting.
Convert.ToString(123);

// Throws exceptions on failures e.g. System.OverflowException here.
Convert.ToByte(555);

Prefer (T)x class casting syntax over x as T. Crash as fast as you detect a problem and crash hard.

// This will throw exception if cannot cast.
MyClass myObject = (MyClass) obj;

// This will just return null if cannot cast.
MyClass myObject = obj as MyClass;

Data Structures

Array size must be specified on declaration. Use System.Collections.List<T> if you do not know the array size.

int[] myArray = new int[10];

// Instant initialization.
int[] myInstantArray = {9000, 1000, 1337};
// myInstantArray[0] == 9000

// Lists
List<int> intList = new List<int>();
intList.Add(1);

Control Structures

If-statement.

int x = 0;
if (x > 0)
{
    Console.WriteLine("It is positive!");
}
else if (x < 0)
{
    Console.WriteLine("It is negative!");
}
else
{
    Console.WriteLine("It is zero!");
}

Ternary operator.

int score = 11;
string result = (score > 10) ? "good" : "bad";
Console.WriteLine(result);

Switch-statement.

int count = 3;
switch (count) {
    case 1:
        Console.WriteLine("One!");
        break;
    case 2:
        Console.WriteLine("Two!");
        break;
    case 3:
    case 4:
    case 5:
        Console.WriteLine("More Than Two!");
        break;
    default:
        Console.WriteLine("No Idea!");
        break;
}

For-loop.

for (int i = 0; i < 11; i++)
{
    Console.WriteLine(i); // => 0-10
}

Foreach-loop. foreach-loop goes through anything that implements IEnumerable interface.

foreach (char character in "Hello World".ToCharArray())
{
    Console.WriteLine(character);
}

While-loop.

int myWhile = 0;
while (myWhile < 100)
{
    Console.WriteLine(myWhile);
    myWhile++;
}

Do-while-loop.

int myDoWhile = 0;
do
{
    Console.WriteLine(myDoWhile);
    myDoWhile++;
} while (myDoWhile < 100);

Functions

Functions are usually associated with a class.

using System;

class MyProgram
{
    public static void Log(string text = "Nothing!")
    {
        Console.WriteLine(text);
    }
    static void Main()
    {
        Log("Hello!");
    }
}

You can define extension methods to existing data types and classes. Notice this keyword in the argument list.

using System;

public static class StringExtensions
{
    public static void Print(this string str)
    {
        Console.WriteLine(str);
    }
}

class MyProgram
{
    static void Main()
    {
        string str = "watwat";
        str.Print();
    }
}

Classes

Class usage.

MyClass stuff = new MyClass();
stuff.MethodName();
stuff.PropertyName = 10;

Access modifiers specify who can access an aspect of the class:

  • Private: access from inside this class.
  • Protected: access from inside this class or subclass.
  • Internal: access from within the assembly.
  • Public: access from anywhere.
  • If not visibility is specified, defaults to private.

Class definition.

public class Person
{
    // Static aspects are shared between all the instances of this class.
    // e.g. Person.PersonCount.
    public static int PersonCount = 0;

    // Fields are usually kept private and accessed through properties.
    private string name;

    // Properties overwrite set and get syntax.
    // (joe.Name = "Joe") and (Console.WriteLine(joe.Name) here.
    // `value` variable in setter is the value given to setter.
    public string Name {
        get { return name; }
        set { name = value; }
    }

    // Constructor.
    public Person()
    {
        Name = "Placeholder Name";
        PersonCount++;
    }

    // Alternative constructor, calls base constructor first.
    public Person(string name) : base()
    {
        Name = name;
    }
}

Define access modifier before other modifiers.

// bad
static public int PersonCount = 0;
// good
public static int PersonCount = 0;

Use the automatic properties. Consider adding private setter to properties that should not be changed from outside.

public bool IsBroken { get; set;}
public bool IsBroken { get; private set; }

Interfaces

Interfaces only define what must be implemented to fulfil the interface.

interface IListener
{
    public void ListenTo(NoiseMaker maker);
    public void StopListeningTo(NoiseMaker maker);
}

There is not multiple inheritance. But you can implement multiple interfaces.

class ListeningList : IListener, IEnumerable
{
    // ...
}

Error Handling

C# has two main ways to handle errors: exceptions and try error returns.

// Exception
try
{
    throw new ArgumentException("You cannot do that!");
}
catch (ArgumentException e)
{
    // do something to fix the situation or throw it forward.
    throw (e);
}

// Try Error Returns
if (int.TryParse(myString, out myInt)) {
    // use myInt
} else {
    // use default value
}

Prefer exceptions over error returns. Exceptions stop the execution as soon as an error is encountered, error returns default to continuing the execution with an invalid value. But error returns have their place.

int.Parse();     // throws exception if argument can't be parsed
int.TryParse();  // returns a bool to denote whether parse succeeded

IEnumerable.First();          // throws exception if sequence is empty
IEnumerable.FirstOrDefault(); // returns default value if sequence is empty

Debugging

Prints statements are most trusty ways of debugging. But you should really get an IDE with debugging tools like break points.

using System;
Console.WriteLine("Hello World!");
Console.Write("My name is Ruksi!\n");

Sources