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?
An array
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?
The default value of the data type
What is the default value for a bool in C#?
false
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)
int[] myInts = new int[3];
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)
bool[] boolArray = { false, false, false };
What is an example of declaring an implicitly typed array in C#?
(Declare an array with 1, 10, 100, and 1000)
var a = new[] { 1, 10, 100, 1000 };
What is the base class to every type in C#, including the fundamental data types?
System.Object
What is the data type to declare an array with in C# if you want the subitems of the array to be any type?
System.Object
What is the term for an array of multiple dimensions where each row is of the same length?
A rectangular array
What is the term for an array of multiple dimensions where the inner arrays each may have a different length?
A jagged array
What is the property to return the number of items in an array in C#?
Length
What is the static Array method in C# to reverse the contents of a one-dimensional array?
Array.Reverse()
What is the difference between a method and function in C#?
A function returns a value to the caller
What are methods within methods called in C#?
Local functions
What in C# is a function declared inside another function that must be private and does not support overloading?
Local functions
Do local functions support nesting in C#?
Yes
What does it mean for a parameter to be sent into a function by value?
A copy of the data is passed in
What method can be called on a List<string> object in C# to convert it to an instance of string[] (an array of strings)?
ToArray()
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#?
Array.Empty<string>()
What is the default way a parameter is sent into a function in C#?
By value
(The reference is passed by value for reference types)
In C#, what are the two cases that relate to exactly what is copied when a parameter is sent into a function by value?
If the parameter is a value type or reference type
What is an enumerator in C#?
A class or structure that implements the IEnumerable interface
By convention, the suffix of an enum type name in C# is usually what?
Enum
What is the parameter modifier in C# to mark a parameter as an output parameter?
out
What is the well-known special case of a reference type in C# that is still passed by value?
string
What is a term for a temporary, dummy variable that is intentionally unused?
A discard
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?
ref
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?
params
What is a method called when there are multiple methods with the identical name but differ in the number or type of parameters?
Overloaded
What is the C# keyword for the custom data type of name-value pairs?
enum
What is the C# interface that objects must support to work with the foreach loop (and objects that do can be called enumerators)?
IEnumerable
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?
Expression-bodied members
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#?
static int Add(int x, int y) => x + y;
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#?
That they are allocated on the stack
What is a nullable type in C#?
A type that can be all of the values of its underlying type, and null
When does a value type variable die in C#?
When it falls out of scope
What types in C# are allocated on the stack?
Value types
What C# type has the purpose of overriding the virtual methods defined by System.Object to use value-based instead of reference-based semantics?
System.ValueType
What is the result of this (C#)?
0110 << 1
1100
What is the result of this (C#)?
0110 >> 1
0011
What is the result of this (C#)?
1100 & 0100
0100
What is the result of this (C#)?
0110 | 0100
0110
What is the result of this (C#)?
0110 ^ 0100
0010
What is a one-line way to throw an ArgumentNullException if the variable message was null in C#?
ArgumentNullException.ThrowIfNull(message);
What is the operator in C# to flip the bits of a binary number?
~
What is the result of this (C#)?
0011 ^ 1100
1111
What is the result of this (C#)?
0111 ^ 1011
1100
What is the ones' compliment of a binary number?
The value you get by flipping all the bits
Why can you not reassign a reference type argument to be a new object inside a method call in C#?
The reference is passed by value (it is a copy of the reference to the object)
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?
ref
What types in C# are allocated on the managed heap?
Reference types
A reference type in C# can inherit from any type except for what?
System.ValueType
When does a reference type variable die in C#?
When it is garbage collected
What values can a nullable bool have in C#?
true, false, or null
When using the ? suffix to use a nullable value type, this is really just shorthand for what type?
System.Nullable<T>
With the System.Nullable<T> structure type, what kind of types can be used as T?
Value types
What is an example of declaring a variable of a nullable int type in C#?
int? nullableInt = 10;
What is the null-coalescing operator in C# (for the benefit of a more compact version of a traditional if/else condition)?
??
Example:
int myData = dr.GetIntFromDatabase() ?? 100;
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)?
??=
Example:
int? nullableInt = null;
nullableInt ??= 12;
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)?
args?.Length
What light construct in C# provides a convenient way to return more than one value from a method?
Tuples
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?
Deconstruct()
What does C# support for the purpose of creating a set of symbolic names that map to known numerical values?
Enumerations
What does it look like to declare an enum EmpTypeEnum that uses byte as the storage value rather than an int in C#?
enum EmpTypeEnum : byte
What class type do .NET Core enumerations get functionality from?
System.Enum
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?
Overriding
The <Nullable> node in a C# project file listing can be used to opt in or configure what?
Nullable reference types
With C# 10, are nullable reference types enabled or disabled by default?
They are enabled
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?
var values = ("a", 5, "c");
What type is this (C#)?
var values = ("a", 5, "c");
ValueTuple
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)?
Output parameters