Understanding C# Arrays


An array is a set of contiguous data points of the same type. When declaring a C# array using this syntax, the number used in the array declaration represents the total number of items, not the upper bound:

static void SimpleArrays()
{
  Console.WriteLine("=> Simple Array Creation.");
  // Create and fill an array of 3 integers
  int[] myInts = new int[3];
  // Create a 100 item string array, indexed 0 - 99
  string[] booksOnDotNet = new string[100];
  Console.WriteLine();
}

Note also that the lower bound of the array always begins at 0.

myInts[0] = 100;
myInts[1] = 200;

Note also that if you declare an array but do not explicitly fill each index, each item will be set to the default value of the data type. For bools, that is false, and for ints, it is 0.

Looking at the C# Array Initialization Syntax


The C# array initialization syntax specifies each array item within the scope of curly brackets. This is good for when you are creating an array of a known size and want to quickly specify the initial values.

string[] stringArray = new string[] { "one", "two", "three" };
bool[] boolArray = { false, false, false };
int[] intArray = new int[4] { 20, 22, 23, 0 };

Understanding Implicitly Typed Local Arrays


The var keyword can also be used to define implicitly typed local arrays.

var a = new[] { 1, 10, 100, 1000 };

Defining an Array of Objects


System.Object is the ultimate base class to every type, including the fundamental data types. If you were to define an array of System.Object data types, then the subitems can be anything.

Working with Multidimensional Arrays


A rectangular array is simply an array of multiple dimensions where each row is of the same length. A jagged array contains some number of inner arrays but each may have a different upper limit.

int[,] myMatrix;
int[][] myJagArray = new int[5][];

Using Arrays As Arguments or Return Values


Arrays can be passed as an argument or received as a member return value.

Using the System.Array Base Class




Many members of System.Array are defined as static members (Array.Sort(), Array.Reverse()). Others such as Length are bound at the object level.

Using Indices and Ranges


System.Index represents an index into a sequence. System.Range represents a subrange of indices.

^ is the index from end operator and specifies that the index is relative to the end of the sequence. The range operator .. specifies the start and end of a range as its operands.

Understanding Methods


Methods are defined by an access modifier and return type (or void for no return type) and may or may not take parameters. A method that returns a value to the caller is commonly referred to as a function. Methods that do not return a value are commonly referred to as methods.

// Recall that static methods can be called directly
// without creating a class instance.
// static returnType MethodName(parameter list) { /* Implementation */ }
static int Add(int x, int y)
{
  return x + y;
}

Understanding Expression-Bodied Members


C# 6 introduced expression-bodied members that shorten the syntax for single-line methods:

static int Add(int x, int y) => x + y;

This is an example of syntactic sugar (the generated IL is no different). The => operator is a lambda operation but for now can be thought of as a shortcut to writing single-line statements.

Understanding Local Functions


C# 7.0 introduced being able to create methods within methods. These are officially referred to as local functions. A local function is a function declared inside another function that must be private and does not support overloading. With C# 8, they can be static. Local functions also support nesting, so a local function can have a local function inside it.

Understanding Method Parameters


Method parameters are used to pass data into a method call.

Understanding Method Parameter Modifiers


The default way a parameter is sent into a function is by value. This means that a copy of the data is passed into the function. Exactly what is copied will depend on if the parameter is a value type or reference type. Parameter modifiers can be used to control how arguments are passed to a method.



Understanding the Default Parameter-Passing Behavior


The default behavior for value types it to pass in the parameter by value and the default for reference types is to pass in the parameter by reference.

The Default Behavior for Value Types


The default way a value type parameter is sent into a function is by value. A copy of the data is passed into the function. Numerical data types are value types.

The Default Behavior for Reference Types


The default way a reference type parameter is sent into a function is by reference for its properties, but by value for itself. The string type is a special case. It is technically a reference type but it is still passed by value when the parameter does not have a modifier.

Using the out Modifier


Methods that have been defined to take output parameters (via the out keyword) are under obligation to assign them to an appropriate value before exiting the method scope.

static void AddUsingOutParam(int x, int y, out int ans)
{
  ans = x + y;
}

int ans;
AddUsingOutParam(90, 90, out ans);

This can be useful for allowing the caller to obtain multiple outputs from a single method invocation. Tuples are another way to return multiple values out of a method call.

Discarding out Parameters


You can use a discard as a placeholder if you do now care about the value of an out parameter. Discards are temporary, dummy variables that are intentionally unused. They are unassigned, do not have a value, and might not even allocate any memory. This can provide a performance benefit and make the code more readable.

FillTheseValues(out int a, out _, out _);

The out Modifier in Constructors and Initializers


Parameters for constructors, field and property initializers, and query clauses can all be decorated with the out modifier.

Using the ref Modifier


Reference parameters are necessary when you want to allow a method to operate on (and usually change the values of) various data points declared in the caller's scope (such as a sorting or swapping routine).

public static void SwapStrings(ref string s1, ref string s2)
{
  string tempStr = s1;
  s1 = s2;
  s2 = tempStr;
}

Using the in Modifier


The in modifier passes a value by reference and prevents the called method from modifying the values. This may be used to reduce memory pressure.

Using the params Modifier


C# supports the use of parameter arrays using the params keyword. The params keyword allows you to pass into a method a variable number of identically typed parameters (or classes related by inheritance) as a single logical parameter. Arguments marked with the params keyword can also be processed if the caller sends in a strongly typed array or a comma-delimited list of items.

C# demands a method support only a single params argument which must be the final argument in the parameter list.

Defining Optional Parameters


Optional parameters cannot be listed before nonoptional parameters. Optional arguments involve specifying default arguments.

static void EnterLogData(string message, string owner = "Programmer")
{
  Console.WriteLine("Error: {0}", message);
  Console.WriteLine("Owner of Error: {0}", owner);
}

Using Named Arguments


Another language feature found in C# is support for named arguments. Named arguments allow you to invoke a method by specifying parameter values in any order you choose.

Understanding Method Overloading


C# allows a method to be overloaded. A method is overloaded when there are a number of identically named methods that differ by the number or type or parameters.

Checking Parameters for Null


If a method parameter is nullable and required by the method body, it is considered good programming practice to check that the parameter is not null before using it. If it is null, the method should throw an ArgumentNullException.

static void EnterLogData(string message, string owner = "Programmer")
{
  if (message == null)
  {
    throw new ArgumentNullException(message);
  }
  ...
}

A newer way (C# 10):

ArgumentNullException.ThrowIfNull(message);

Understanding the enum Type


An enum is a custom data type of name-value pairs. Do not confuse it with enumerator. An enumerator is a class or structure that implements the IEnumerable interface. Objects that support IEnumerable can work with the foreach loop.

// A custom enumeration
enum EmpTypeEnum
{
  Manager,
  Grunt,
  Contractor,
  VicePresident
}

By convention, the suffix of the enum type name is usually Enum. In the above, the first element is set to the value 0 and the rest follow an n+1 progression.

enum EmpTypeEnum
{
  Manager = 102,
  Grunt,
  Contractor,
  VicePresident
}

Controlling the Underlying Storage for an enum


By default, the storage type used to hold the values of an enumeration is System.Int32 (the C# int). You can change this:

enum EmpTypeEnum : byte
{
  Manager = 10,
  Grunt = 1,
  Contractor = 100,
  VicePresident = 9
}

Changing the underlying type can be useful when building a .NET Core app that will be deployed to a low-memory device.

Declaring enum Variables


EmpTypeEnum emp = EmpTypeEnum.Contractor;

Using the System.Enum Type


.NET Core enumerations get functionality from the System.Enum class type which defines several methods to interrogate and transform the enumeration. For example, the Enum.GetUnderlyingType() method takes a System.Type argument and in this case returns the data type used to store the values of the enumerated type.

Enum.GetUnderlyingType(emp.GetType()));

Dynamically Discovering an enum's Name-Value Pairs


Al C# enumerations support a method named ToString() which returns the string name of the enumeration's value.

Using Enums, Flags, and Bitwise Operations




Understanding the Structure


Structure types are well suited for modeling mathematical, geometrical, and other "atomic" entities in your application. Structures are types that can contain any number of data fields and members that operate on these fields. They can be thought of as a "lightweight class type" given that they provide a way to define a type that supports encapsulation but cannot be used to build a family of related types. They cannot be inherited and cannot be the base of a class.

struct Point
{
  public int X;
  public int Y;

  public void Increment()
  {
    X++; Y++;
  }
}

Note that in the above, it is typically considered bad style to define public data within a class or structure. You want to define private data that can be accessed and changed using public properties.

Creating Structure Variables


Point p2;
p2.X = 10;
p2.Y = 10;
p2.Display();

As an alternative, you can create structure variables using the C# new keyword which will invoke the structure's default constructor.

Structure Constructors


A structure can be designed with a custom constructor.

struct Point
{
  public int X;
  public int Y;

  public Point(int xPos, int yPos)
  {
    X = xPos;
    Y = yPos;
  }
  ...
}

Point p2 = new Point(50, 60);

With C# 10, you can also declare a parameterless (i.e. default) constuctor on a structure.

struct Point
{
  ...
  public Point()
  {
    X = 0;
    Y = 0;
  }
}

Using Field Initializers


New in C# 10, structure fields can be initialized when declared.

struct Point
{
  public int X = 5;
  public int Y = 7;
}

With this, the parameterless constructor would not need to initialize the X and Y fields.

Using Read-Only Structs


Structs can be marked as read-only if there is a need to make them immutable. Immutable objects must be set up at their initial construction, and they can be more performant because they cannot be changed.

readonly struct ReadOnlyPoint
{
...
}

Using Read-Only Members


The individual fields of a struct can be declared as readonly.

Using ref Structs


The ref modifier can be used when defining a struct to require all instances of the struct to be stack allocated and unable to be assigned as a property of another class. There are other limitations: they cannot be assigned to a variable of type object or dynamic and cannot be an interface type. They cannot implement interfaces. They cannot be used as a property of a non-ref struct. They cannot be used in async methods, iterators, lambda expressions, or local functions.

Using Disposable ref Structs


ref structs and read-only ref structs can be made disposable by adding a public void Dispose() method. This is needed since they cannot implement the IDisposable interface given they cannot implement interfaces.

Understanding Value Types and Reference Types


Unlike arrays, strings, and enumerations, C# structures do not have an identically named representation in the .NET Core library (there is not a System.Structure class). Structures are implicitly derived from System.ValueType.

The role of System.ValueType is to ensure that the derived type is allocated on the stack. Data allocated on the stack can be created and destroyed quickly as its lifetime is determined by the defining scope. Heap-allocated data is monitored by the .NET Core garbage collector and has a lifetime that is determined by many factors.

Functionally, the only purpose of System.ValueType is to override the virtual methods defined by System.Object to use value-based versus reference-based semantics.

Using Value Types, Reference Types, and the Assignment Operator


When you assign one value type to another, a member-by-member copy of the field data is achieved. When you apply the assignment operator to reference types, you are redirecting what the reference variable points to in memory. So if you have two references pointing to the same object on the managed heap, when the value is changed using one reference, the other reference reports the same changed value.

Using Value Types Containing Reference Types


When a value type contains other reference types, assignment results in a copy of the references. So you have two independent structures for example, each of which containing a reference to the same object in memory. To perform a deep copy, one approach is to implement the ICloneable interface.

Passing Reference Types by Value


If a reference type is passed into a method, it is really a copy of the reference getting passed in, and it is possible to therefore alter the object's state data.

Passing Reference Types by Reference


If you use the ref parameter modifier, you can pass a reference type by reference, which means not only can you change the state of the object, but you can also reassign the reference to a new object.

Final Details Regarding Value Types and Reference Types


Value Type
  • Allocated on the stack
  • Extends System.ValueType
  • Cannot be inherited from
  • Passed by value
  • Die when falling out of scope

Reference Type
  • Allocated on the managed heap
  • Can derive from any type except System.ValueType
  • Reference is passed by value
  • Dies when garbage collected

Understanding C# Nullable Types


Value types can never be assigned the value of null.

C# supports the concept of nullable data types. A nullable type can present all the values of its underlying type, plus null. Thus a nullable bool could be true, false, or null. This can be helpful when working with relational databases.

To define a nullable variable type, ? is suffixed to the underlying data type.

static void LocalNullableVariables()
{
  int? nullableInt = 10;
  double? nullableDouble = 3.14;
}

Using Nullable Value Types


The ? suffix is shorthand for creating an instance of the generic System.Nullable<T> structure type. System.Nullable<T> provides a set of members that all nullable types can make use of.

The ? is just shorthand for this:

Nullable<int> nullableInt = 10;

Using Nullable Reference Types


Nullable reference types use ? as well but it is not a shorthand for using System.Nullable<T> as only value types can be used in place of T.

Opting in for Nullable Reference Types


Support for nullable reference types is a significant feature added with C# 8. This was a big change and one of the reasons that C# 8 is only supported in .NET Core 3.0 and above.

Support for nullable reference types is controlled by setting a nullable context. This can be done for the entire project using the project file or just a file lines using compiler directives. With C# 10/.NET 6, nullable reference types are enabled by default.

Nullable Reference Types in Action


Warnings will be generated when trying to assign null to a non-nullable type in a nullable context. The nullable context allows the declarations of reference types to use the nullable annotation (?). Fine control of where the nullable contexts are in the project can be controlled using compiler directives.

Migration Considerations


Change Nullable Warnings to Errors


When you are ready to commit to nullable reference types, you can configure the nullable warnings as errors with the project file.

It is also possible to change the severity of any warning to an error.

Operating on Nullable Types


Several operators are provided for working with nullable types: the null-coalescing operator, the null-coalescing assignment operator, and the null conditional operator.

The Null-Coalescing Operator


A variable that might have a null value can make use of the ?? operator, which is the null-coalescing operator. This allows assigning a value to a nullable type if the retrieved value is null.

int myData = dr.GetIntFromDatabase() ?? 100;

This provides a more compact version of a traditional if/else condition.

The Null-Coalescing Assignment Operator


??= is the null-coalescing assignment operator. This operator assigns the left-hand side to the right-hand side only if the left-hand side is null.

int? nullableInt = null;
nullableInt ??= 12;
nullableInt ??= 14; // does not assign 14 to the variable since it is not null

The Null Conditional Operator


args?.Length

If args is null, the call to the Length property will not throw a runtime error.

Console.WriteLine($"You sent me {args?.Length ?? 0} arguments."); 

Understanding Tuples


A tuple is a light construct that can be used to retrieve more than one value from a method call, as an alternative to using the out method parameter.

Tuples are lightweight data structures that contain multiple fields. They were introduced in a limited way in C# 6, and as of C# 7, tuples use the new ValueTuple data type instead of reference types. This potentially saves a lot of memory. The ValueType type creates different structs based on the number of properties for a tuple. Each property in a tuple can also be assigned a specific name.

The two important considerations for tuples: the field are no validated and you cannot define your own methods. They really are intended to just be a lightweight data transport mechanism.

Getting Started with Tuples


To create a tuple, simply enclose the values to the tuple in parentheses. They do not need to be the same data type.

(string, int, string) values = ("a", 5' "c");

They are accessed by properties with names like ItemX.

Console.WriteLine($"First item: {values.Item1}");

Specific names can also be added to each property in the tuple on either the right or left side, but if you do both at the same time, the left side will be used only.

(string FirstLetter, int TheNumber, string SecondLetter) valuesWithNames = ("a", 5, "c");
var valuesWithNames2 = (FirstLetter: "a", TheNumber: 5, SecondLetter: "c")

Now the properties can be accessed with these names and the ItemX notation.

When setting the names on the right, the var keyword must be used to declare the variable. Setting the data types specifically causes the compiler to use the left side and ignore any custom names on the right.

Note also that the custom field names exist only at compile time and can not be inspected at runtime using reflection.

Also note: tuples can be nested inside other tuples.

var nt = (5, 4, ("a", "b"));

Using Inferred Variable Names


Understanding Tuple Equality/Inequality


The comparison operators will perform implicit conversions on data types within the tuples, including comparing nullable and non-nullable tuples and/or properties. Tuples that contain tuples can also be compared, if they have the same shape.

Understanding Tuples as Method Return Values


out parameters can be used to return more than one value from a method call. A class or structure could be created specifically to do this as well. But it's probably not worth doing so if it is going to be used as a data transport mechanism for only one method. Tuples are perfectly suited for this task.

Understanding Discards with Tuples


Understanding Tuple Pattern Matching switch Expressions


Deconstructing Tuples


Deconstructing is the term for separating out the properties of a tuple to be used individually.

(int x1, int y1) = myTuple;

Another use for this pattern is deconstructing custom types by having the individual properties of the type be returned by some method as a tuple. This method is conventionally named Deconstruct().

Deconstructing Tuples with Positional Pattern Matching


Summary


This chapter began with an examination of arrays. Then, we discussed the C# keywords that allow you to build custom methods. Recall that by default parameters are passed by value; however, you may pass a parameter by reference if you mark it with ref or out. You also learned about the role of optional or named parameters and how to define and invoke methods taking parameter arrays. After you investigated the topic of method overloading, the bulk of this chapter examined several details regarding how enumerations and structures are defined in C# and represented within the .NET Core base class libraries. Along the way, you examined several details regarding value types and reference types, including how they respond when passing them as parameters to methods and how to interact with nullable data types and variables that might be null (e.g., reference type variables and nullable value type variables) using the ?, ??, and ??= operators. The final section of the chapter investigated a long-anticipated feature in C#, tuples. After getting an understanding of what they are and how they work, you used them to return multiple values from methods as well as to deconstruct custom types. 

Article notes

What is defined as a set of contiguous data points of the same type?
When an array is declared in C# and each index is not explicitly filled with a value, what is the value for the unset indexes?
What is the default value for a bool in C#?
What is an example of declaring an array in C# by specifying the total number of items in the array? (Declare an array of 3 ints)
What is an example of declaring an array using the C# array initialization syntax which specifies each array item? (Declare an array with three bools--each false)
What is an example of declaring an implicitly typed array in C#? (Declare an array with 1, 10, 100, and 1000)
What is the base class to every type in C#, including the fundamental data types?
What is the data type to declare an array with in C# if you want the subitems of the array to be any type?
What is the term for an array of multiple dimensions where each row is of the same length?
What is the term for an array of multiple dimensions where the inner arrays each may have a different length?
What is the property to return the number of items in an array in C#?
What is the static Array method in C# to reverse the contents of a one-dimensional array?
What is the difference between a method and function in C#?
What are methods within methods called in C#?
What in C# is a function declared inside another function that must be private and does not support overloading?
Do local functions support nesting in C#?
What does it mean for a parameter to be sent into a function by value?
What method can be called on a List<string> object in C# to convert it to an instance of string[] (an array of strings)?
Which of ? or : comes first in a ternary statement in C#?
What is the VS Code IDE recommended syntax for declaring an empty array of strings in C#?
What is the default way a parameter is sent into a function in C#?
In C#, what are the two cases that relate to exactly what is copied when a parameter is sent into a function by value?
What is an enumerator in C#?
By convention, the suffix of an enum type name in C# is usually what?
What is the parameter modifier in C# to mark a parameter as an output parameter?
What is the well-known special case of a reference type in C# that is still passed by value?
What is a term for a temporary, dummy variable that is intentionally unused?
What is the parameter modifier you can use in C# if you want a method to be able to change the value of a passed in argument?
What is the C# keyword that allows you to pass into a method a variable number of identically typed parameters as a single logical parameter?
What is a method called when there are multiple methods with the identical name but differ in the number or type of parameters?
What is the C# keyword for the custom data type of name-value pairs?
What is the C# interface that objects must support to work with the foreach loop (and objects that do can be called enumerators)?
What is the bitwise AND operator in C#?
What is the bitwise OR operator in C#?
What is the bitwise XOR operator in C#?
What is the ones' compliment operator in C# (that flips the bits)?
What did C# 6 introduce that can be used to shorten the syntax of defining single-line methods?
What is one line of code showing a method that adds two ints together and returns an int using the expression-bodied member feature of C#?
What is the left shift operator in C#?
What is the right shift operator in C#?
What does System.ValueType ensure about types derived from it in C#?
What is a nullable type in C#?
When does a value type variable die in C#?
What types in C# are allocated on the stack?
What C# type has the purpose of overriding the virtual methods defined by System.Object to use value-based instead of reference-based semantics?
What is the result of this (C#)? 0110 << 1
What is the result of this (C#)? 0110 >> 1
What is the result of this (C#)? 1100 & 0100
What is the result of this (C#)? 0110 | 0100
What is the result of this (C#)? 0110 ^ 0100
What is a one-line way to throw an ArgumentNullException if the variable message was null in C#?
What is the operator in C# to flip the bits of a binary number?
What is the result of this (C#)? 0011 ^ 1100
What is the result of this (C#)? 0111 ^ 1011
What is the ones' compliment of a binary number?
Why can you not reassign a reference type argument to be a new object inside a method call in C#?
If you want to reassign a reference to point to a new object inside a method call in C#, what parameter modifier must you use?
What types in C# are allocated on the managed heap?
A reference type in C# can inherit from any type except for what?
When does a reference type variable die in C#?
What values can a nullable bool have in C#?
When using the ? suffix to use a nullable value type, this is really just shorthand for what type?
With the System.Nullable<T> structure type, what kind of types can be used as T?
What is an example of declaring a variable of a nullable int type in C#?
What is the null-coalescing operator in C# (for the benefit of a more compact version of a traditional if/else condition)?
What is the null-coalescing assignment operator in C# (assigns the left-hand side to the right-hand side only if the left-hand side is null)?
What is an example using the null conditional operator in C# (Length property of args but do not throw an error if args is null)?
What light construct in C# provides a convenient way to return more than one value from a method?
When a type in C# returns a tuple from a method in order to return out the class instance's individual properties, what is the method conventionally called?
What does C# support for the purpose of creating a set of symbolic names that map to known numerical values?
What does it look like to declare an enum EmpTypeEnum that uses byte as the storage value rather than an int in C#?
What class type do .NET Core enumerations get functionality from?
What is the process of changing the implementation of a virtual (or abstract) method of a base class in a derived class in C# called?
The <Nullable> node in a C# project file listing can be used to opt in or configure what?
With C# 10, are nullable reference types enabled or disabled by default?
How to declare a tuple called values in C# that has a string "a", an int 5, and a string "c" using the var keyword?
What type is this (C#)? var values = ("a", 5, "c");
What type of method parameters in C# must be assigned by the method being called (a compiler error occurs if the called method fails to assign it)?
Previous Next