These allow you to specify
null as a potential value on any struct variable (where
null would otherwise be invalid):
class MyClass {
public int MyInt { get; }
public int? MyNullableInt { get; } // This property can be null even though it's of type 'int'
public MyClass(int? input) { // input can be null
MyInt = input != null ? input.Value : 0; // .Value throws an exception when accessed if input is null
MyNullableInt = input;
}
}
// ..
static void Test() {
var mc = new MyClass(null);
if (mc.MyNullableInt == null) Console.WriteLine("Was null!");
}
View Code Fullscreen • "Nullable Value Types"
Technically the type of any nullable value object is
Nullable<T> where
T is the actual encompassed type (i.e.
int? is the same as
Nullable<int>).
Nullable<T> is itself a struct, so in theory it does not make sense to check instances of that type for
null, but a combination of the compiler and overridden
Equals() implementation on
Nullable<T> allow us to treat instances the type as though it
does make sense. You can also determine whether a
Nullable<T> instance is "not null" by using its
HasValue property.
This feature allows spreading the implementation of a large type (class, interface, or struct) across multiple files.
// File: ExampleClass.Alpha.cs
public partial class ExampleClass {
public void DoAlphaOne() { ... }
public void DoAlphaTwo() { ... }
}
// File: ExampleClass.Beta.cs
public partial class ExampleClass {
public void DoBetaOne() { ... }
public void DoBetaTwo() { ... }
}
// Elsewhere
static void Test() {
var ec = new ExampleClass();
ec.DoAlphaOne();
ec.DoBetaTwo();
// etc
}
View Code Fullscreen • "Partial Classes"
This feature allows creating expressions that evaluate to the first non-null value in a chain:
var neverNullString = _stringField ?? stringParameter ?? "Default";
View Code Fullscreen • "Null Coalesceing"
This code will set
neverNullString to
_stringField, unless
_stringField is null; in which case it will set it to
stringParameter, unless
stringParameter is also null, in which case it will set it to the literal value
"Default".
This functionality allows you to create an
IEnumerable<T> or
IEnumerator<T> by 'yielding' elements in the enumerable. The following example demonstrates how we can create a sequence of three or six integers:
public IEnumerable<int> GetOneTwoThree(bool includeNegative = false) {
yield return 1;
yield return 2;
yield return 3;
if (!includeNegative) yield break;
yield return -1;
yield return -2;
yield return -3;
}
// ..
Console.WriteLine(String.Join(",", GetOneTwoThree())); // Prints "1,2,3" on console
Console.WriteLine(String.Join(",", GetOneTwoThree(true))); // Prints "1,2,3,-1,-2,-3" on console
View Code Fullscreen • "Yield Return and Break"
This feature allows defining new methods on pre-existing types. This is useful for adding functionality to types you don't control. The following example shows
how to add an overload of
ToString() to the
double type:
public static class DoubleExtensions {
public static string ToString(this double @this, int numDecimalPlaces) {
return @this.ToString("N" + numDecimalPlaces.ToString(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture);
}
}
// ... Elsewhere ...
// Will write something like "3.5"; assuming YearsWorkedAtCompany is a double:
Console.WriteLine(user.YearsWorkedAtCompany.ToString(1));
View Code Fullscreen • "Double ToString Extension"
This feature allows instantiation of various collection types while at the same time adding initial values to them:
var myArray = new[] { 1, 2, 3, 4, 5 }; // myArray is an int[] of length 5
var myList = new List<int> { 1, 2, 3, 4, 5 }; // myList is a List<int> with 5 elements
var myDict = new Dictionary<int, int> { { 1, 100 }, { 2, 200 } }; // myDict is a Dictionary with 2 key-value pairs
View Code Fullscreen • "Collection Initializers"
This feature allows setting an object's properties inline at its instantiation-point:
class Person {
public string Name { get; set; }
public int Age { get; set; }
}
static void Test() {
var person = new Person {
Name = "Ben",
Age = 30
};
}
View Code Fullscreen • "Object Initializers"
Like partial classes, this allows you to write two or more parts of a method in different files. There is no guarantee on the order of execution of parts. The method must have a
void return type, and must be private.
// File: ExampleClass.Alpha.cs
public partial class ExampleClass {
public void Print() => DoThing();
partial void DoThing() {
Console.WriteLine("AAA");
}
}
// File: ExampleClass.Beta.cs
public partial class ExampleClass {
partial void DoThing() {
Console.WriteLine("BBB");
}
}
// Elsewhere
static void Test() {
var ec = new ExampleClass();
ec.Print(); // Prints AAA and BBB to console (order unspecified).
}
View Code Fullscreen • "Partial Methods"
Auto-generated code can use empty partial method declarations to allow users to manually insert custom logic if desired:
// File: ExampleClass.AutoGenerated.cs
public partial class ExampleClass {
public void SomeAutoGeneratedMethod() {
DoSomethingOnAutoGenMethodCall();
// do other stuff
}
partial void DoSomethingOnAutoGenMethodCall(); // If user does not supply implementation in ExampleClass.User.cs this method will not even be compiled and calls to it will be removed
}
// File: ExampleClass.User.cs
public partial class ExampleClass {
partial void DoSomethingOnAutoGenMethodCall() {
Console.WriteLine("SomeAutoGeneratedMethod Invoked");
}
}
View Code Fullscreen • "Partial Methods for Auto-Generated Code"
Dynamic typing was introduced to allow "late bound" type resolution. One thing I use
dynamic for occasionally is as a more succinct form of reflection:
class GenericClass<T1, T2> {
public T1 SomeComplexMethod<T3>(T2 inputA, T3 inputB) {
// ...
}
}
class Program {
static void Main() {
var gc = new GenericClass<int, string>();
var resultA = InvokeComplexMethodReflectively(gc, "Hi", 3f);
var resultB = InvokeComplexMethodDynamically(gc, "Hi", 3f);
Console.WriteLine(resultA);
Console.WriteLine(resultB);
}
static object InvokeComplexMethodReflectively(object genericClassInstance, string inputA, float inputB) {
var openMethodDefinition = genericClassInstance.GetType().GetMethod("SomeComplexMethod");
var genericMethodDefinition = openMethodDefinition.MakeGenericMethod(typeof(float));
return genericMethodDefinition.Invoke(genericClassInstance, new object[] { inputA, inputB });
}
static object InvokeComplexMethodDynamically(object genericClassInstance, string inputA, float inputB) {
return ((dynamic) genericClassInstance).SomeComplexMethod(inputA, inputB);
}
}
View Code Fullscreen • "Dynamic as an Alternative to Reflection"
A lot of people have reservations about using
dynamic at all, but the late-binding is resolved internally using reflection anyway, so it can really be thought of as a syntax sugar for reflection. Additionally, due to caching of binding information
dynamic can actually often outperfom the same approach using 'pure' reflection.
C# also added a new type called
ExpandoObject which is like a dictionary but the keys are members added dynamically:
static void Test() {
dynamic user = new ExpandoObject();
user.Name = "Ben";
user.Age = 30;
// Prints "User Name is Ben" and "User Age is 30"
foreach (var kvp in (IDictionary<String, Object>) user) {
Console.WriteLine($"User {kvp.Key} is {kvp.Value}");
}
}
View Code Fullscreen • "ExpandoObject"
Optional arguments are parameters of a method that have default values specified, and therefore do not need to be specified by the caller:
// 'nickname' is optional here
public static void MethodWithOptionalArgs(string name, int age, string nickname = null) {
// ...
}
static void Test() {
MethodWithOptionalArgs("Ben", 30); // No nickname specified, 'null' will be passed in
}
View Code Fullscreen • "Optional Arguments"
Optional arguments must always come last in the parameter list.
Named arguments in C# 4.0 allow specifying specific optional arugments when there are more than one:
public static void MethodWithOptionalArgs(string name, int age, string nickname = null, bool married = false, string address = null) {
// ...
}
static void Test() {
MethodWithOptionalArgs("Ben", 30, address: "Noveria"); // 'nickname' and 'married' are left unspecified
}
View Code Fullscreen • "Optional Arguments"
As of C# 7.2, arguments can also be named even when they're not optional. I use this occasionally when specifying a boolean parameter that is otherwise cryptic to understand:
static void Test() {
CreateTables(true); // What is true?
CreateTables(deletePreviousData: true); // Ahh... Much better.
}
View Code Fullscreen • "Optional Arguments"
When creating interfaces or delegate types with generic type parameters, we can specify that those type parameters are
covariant or
contravariant:
interface ICovariant<out T> { }
interface IContravariant<in T> { }
static void Test() {
ICovariant<object> covariantObj;
ICovariant<int> covariantInt = GetCovariant<int>();
covariantObj = covariantInt; // "out T" in ICovariant allows this
IContravariant<object> contravariantObj = GetContravariant<object>();
IContravariant<int> contravariantInt;
contravariantInt = contravariantObj; // "in T" in IContravariant allows this
}
View Code Fullscreen • "Covariant and Contravariant Generic Parameters in Interfaces"
delegate T Covariant<out T>();
delegate void Contravariant<in T>(T value);
static void Test() {
Covariant<object> covariantObj;
Covariant<int> covariantInt = () => 3;
covariantObj = covariantInt;
Contravariant<object> contravariantObj = myObj => Console.WriteLine(myObj);
Contravariant<int> contravariantInt;
contravariantInt = contravariantObj;
}
View Code Fullscreen • "Covariant and Contravariant Generic Parameters in Delegates"
Covariance is useful when specifying a type of object that will generally be an
output from the interface; because we generally don't mind if the actual output type is a child of the specified type.
Contravariance is useful when specifying a type of object that will generally be an
input to the interface; because we generally don't mind if the expected input type is a parent of the given type.
Rather than remembering the difference between "contra" and "co" variance, I find it useful to just remember that
in is for inputs and
out is for outputs (generally, anyway).