The basics - izznogooood/dotnet-wiki GitHub Wiki

Variables

Variables represent storage locations. Every variable has a type that determines what values can be stored in the variable. C# defines seven categories of variables: static variables, instance variables, array elements, value parameters, reference parameters, output parameters, and local variables. See Variables for more information.

Data types

C# is a strongly typed language. Every variable and constant has a type, as does every expression that evaluates to a value.

// variable declared (string) and initialized (given a value)
string firstName = "Steve";

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, see Built-in types.

Types are defined as either a value type or a reference type. Types that you define by using the struct keyword are value types; all the built-in numeric types are structs. Types that you define by using the class or record keyword are reference types. Reference types and value types have different compile-time rules, and different run-time behaviour.

Value types

Value type variables directly contain their values. The built-in numeric types are structs, and they have fields and methods that you can access, e.g., a variable declared as a double:

double radius = 5.2;
int res = radius.CompareTo(Math.PI);

See overview of built-in value types.

Value types are allocated either on the stack or inline in containing types and deallocated when the stack unwinds or when their containing type gets deallocated. Therefore, allocations and deallocations of value types are in general cheaper than allocations and deallocations of reference types.This makes value types very efficient for e.g., calculations.

Value types get boxed when cast to a reference type or one of the interfaces they implement. They get unboxed when cast back to the value type. Because boxes are objects that are allocated on the heap and are garbage-collected, too much boxing and unboxing can have a negative impact on the heap, the garbage collector, and ultimately the performance of the application. In contrast, no such boxing occurs as reference types are cast.

Next, reference type assignments copy the reference, whereas value type assignments copy the entire value. Therefore, assignments of large reference types are cheaper than assignments of large value types.

Reference types are passed by reference, whereas value types are passed by value. Changes to an instance of a reference type affect all references pointing to the instance. Value type instances are copied when they are passed by value. When an instance of a value type is changed, it of course does not affect any of its copies. Because the copies are not created explicitly by the user but are implicitly created when arguments are passed or return values are returned, value types that can be changed can be confusing to many users. Therefore, value types should be immutable.

CONSIDER defining a struct (value type), rather than a class (reference type), when:

  • Instances of the type are small and commonly short-lived or are commonly embedded in other objects.

  • The type has all of the following characteristics:

    • It logically represents a single value, similar to primitive types (int, double, etc.).
    • It has an instance size under 16 bytes.
    • It is immutable.
    • It will not have to be boxed frequently.

Check the Choosing Between Class and Struct section for more information on when to chose either.

Take a closer look at the Nullable value type.

Reference types

A type that is defined as a class, record, delegate, array, or interface is a reference type. When declaring a variable of a reference type, it contains the value null until you assign it with an instance of that type or create one using the new operator.

var fi = new FileInfo(@"c:\temp\sample.json");

Take a closer look at the FileInfo reference type.

When the object is created, the memory is allocated on the managed heap. The variable holds only a reference to the location of the object. Types on the managed heap require overhead both when they're allocated and when they're reclaimed.

Reference types are passed by reference. Changes to an instance of a reference type affect all references pointing to the instance.

Reference type assignments copy the reference, whereas value type assignments copy the entire value. Therefore, assignments of large reference types are cheaper than assignments of large value types.

As a rule of thumb, the majority of types you create should be classes. There are, however, some situations in which the characteristics of a value type make it more appropriate to use structs, see Choosing Between Class and Struct section for more information on when to chose either.

There are only a few built-in reference types, most notable built-in reference types are the

  • object

    All types, predefined and user-defined, reference types and value types, inherit directly or indirectly from object.

  • string

    The string type represents a sequence of zero or more Unicode characters. Strings are immutable--the contents of a string object cannot be changed after the object is created. See Working with strings to get started working with strings.

Control statements

The execution flow of your code can be controlled by a number of statements: conditional, loop or jump.

Conditional statements

  • The if statement: selects a statement to execute based on the value of a Boolean expression.
  • The switch statement: selects a statement list to execute based on a pattern match with an expression. Note the patterns supported by the switch statement.
Switching on enums allows the IDE to autofill cases for you!

Loop statements

  • The for statement: executes its body while a specified Boolean expression evaluates to true.
  • The foreach statement: enumerates the elements of a collection and executes its body for each element of the collection.
  • The do statement: conditionally executes its body one or more times.
  • The while statement: conditionally executes its body zero or more times.

Jump statements

  • The break statement: terminates the closest enclosing iteration statement or switch statement.
  • The continue statement: starts a new iteration of the closest enclosing iteration statement.
  • The return statement: terminates execution of the function in which it appears and returns control to the caller.
  • The goto statement: transfers control to a statement that is marked by a label. (Not commonly used)

Operators

C# provides a number of operators, see the article on C# operators for a complete list. Many of them are supported by the built-in types and allow you to perform basic operations with values of those types.

Useful operators are listed in the following sections.

Null-coalescing operator

The null-coalescing operator ?? returns the value of its left-hand operand if it isn't null; otherwise, it evaluates the right-hand operand and returns its result. The ?? operator doesn't evaluate its right-hand operand if the left-hand operand evaluates to non-null.

int? a = null;
int b = a ?? -1;

Use the ??= operator to replace the code of the form

if (variable is null)
{
    variable = expression;
}

with the following code:

variable ??= expression;

Null-conditional operators

The null-conditional operator applies a member access, ?., or element access, ?[], operation to its operand only if that operand evaluates to non-null; otherwise, it returns null

The null-conditional operators are short-circuiting. That is, if one operation in a chain of conditional member or element access operations returns null, the rest of the chain doesn't execute. In the following example, B is not evaluated if A evaluates to null and C is not evaluated if A or B evaluates to null:

A?.B?.Do(C);

Ternary conditional operator

The conditional operator ?:, also known as the ternary conditional operator, evaluates a Boolean expression and returns the result of one of the two expressions, depending on whether the Boolean expression evaluates to true or false.

var investmentColor = balance < 0 ? Color.Red : Color.Black;

Lambda operator

In lambda expressions, the lambda operator => separates the input parameters on the left side from the lambda body on the right side.

int[] numbers = { 4, 7, 10 };
int product = numbers.Aggregate(1, (int interim, int next) => interim * next);
Console.WriteLine(product);    // output: 280

await operator

See section on Asynchronous programming.

Miscellaneous

Enumerations

An enumeration type (or enum type) is a value type defined by a set of named constants.

There are many advantages to using enums in your code:

  • Readability Easier to understand code referring to Days.Weekend, rather than the value 96.

  • Maintainability Easier to perform search / replace.

  • Safer Limited set of values to choose from. Allows the IntelliSense of the IDE to autofill cases for switch statements.

public enum Day
{
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
}

The System.Enum type is the abstract base class of all enumeration types. It provides a number of methods to get information about an enumeration type and its values. For more information and examples see System.Enum.

If you want an enumeration type to represent a combination of choices, define enum members for those choices such that an individual choice is a bit field, and apply the Flags attribute.

[Flags]
public enum Days
{
    None      = 0b_0000_0000,  // 0
    Monday    = 0b_0000_0001,  // 1
    Tuesday   = 0b_0000_0010,  // 2
    Wednesday = 0b_0000_0100,  // 4
    Thursday  = 0b_0000_1000,  // 8
    Friday    = 0b_0001_0000,  // 16
    Saturday  = 0b_0010_0000,  // 32
    Sunday    = 0b_0100_0000,  // 64
    Weekend   = Saturday | Sunday
}

Namespaces

Namespaces are logical grouping of functionality, .NET uses namespaces to organize its many classes.

System.Collections.Generic.List

System.Collections.Generic is a namespace and List is a class in that namespace.

When creating namespaces for your own code, do follow these namespace naming guidelines.

Instead of prefixing classes with the namespace, the namespace can be declared with the using directive.

Attributes

Attributes provide a powerful method of associating metadata, or declarative information, with code. One example of an attribute is the Flags attribute, described for enums above.

You specify an attribute by placing the name of the attribute enclosed in square brackets ([]) above the declaration of the entity to which it applies:

[Flags]
public enum Days
{
  // more code
}

For more information on use and declaration of attributes, see the Attributes section.

Expression body definition

An expression body definition has the following general syntax:

member => expression;

The following example shows an expression body definition for a Person.ToString method:

public override string ToString() => $"{fname} {lname}".Trim();

It's a shorthand version of the following method definition:

public override string ToString()
{
   return $"{fname} {lname}".Trim();
}