C# 6, the latest version of the C# programming language, is here and is (almost) feature complete.
In this post, we'll explore the new language features interactively. Let's start with the most fun feature.
Using Static
using static System.Console;
using static System.Tuple;
WriteLine(Create("C#", 6));
//Hit Go to run this code
Using static lets you import static members of types directly into scope.
In the above example, WriteLine
is imported from the System.Console
type and Create
is imported from the System.Tuple
type.
This feature is useful if you frequently use a group of static methods.
Using static also lets you import static members of non static types, including structs and enums.
using static System.String;
using static System.DateTime;
using static System.DayOfWeek;
Concat("Is today a ", Monday, " or a ", Tuesday, " ? : ", Now.DayOfWeek == Monday || Now.DayOfWeek == Tuesday)
In the example above, Concat
is imported from the non-static System.String
class, Monday
and Tuesday
are imported from the System.DayOfWeek
enum and Now
is imported from System.DateTime
.
When you import a namespace, you bring into scope all visible types and all extension methods in the namespace.
With using static, you can now import extension methods from a single class.
using static System.Xml.Linq.XDocument;
using static System.Xml.Linq.Extensions;
var cities = Parse(
@"<Root>
<State name='California'>
<City name='Los Angeles' />
<City name='San Francisco' />
</State>
<State name='New York'>
<City name='New York City' />
</State>
</Root>").Descendants("City");
cities.AncestorsAndSelf()
In the example above, Parse
is imported from the XDocument
class and AncestorsAndSelf
is imported from the System.Linq.Extensions
class.
This code is able to use the extension methods in the System.Linq.Extensions
class without bringing all types in the System.Xml.Linq
namespace into scope.
nameof expressions
Occasionally, you need to supply the name of a variable, type, type member or other symbol in your code. A common example is when throwing an ArgumentNullException
and the invalid parameter needs to be identified.
In the past, you would have to use reflection to find the name, which is a bit tedious, or hardcode the name, which is error prone.
nameof expressions validate and extract the name of the symbol as a string literal at compile time.
void MyMethod(string input)
{
Console.WriteLine(nameof(input));
if (input == null) throw new ArgumentNullException(nameof(input));
}
MyMethod(null);
In the above example we're able to throw an ArgumentNullException
specifying the name of the erring parameter without hardcoding its name.
If the parameter name is changed in the future via a refactoring operation, the new name will be reflected in the thrown exception.
nameof expressions work on different kinds of code symbols.
string s = "a string";
//On an instance variable of a type
Console.WriteLine( nameof(s) );
//On a dotted member of the instance
Console.WriteLine( nameof(s.Length.ToString) );
//On a dotted member of a type
Console.WriteLine( nameof(Console.Write) );
In each case in the example above, the name of the last identifier in the expression is extracted.
Null-conditional operators
Null-conditional operators let you evaluate members only if a null resolution was not reached in the evaluation chain.
string s = null;
int? length = s?.Length;
Console.WriteLine(length == null);
//In an indexer/element access
char? firstChar = s?[0];
Console.WriteLine(firstChar == null);
As seen in the example above, this eliminates the need to write code that checks for null on each member access.
Null-conditional operators can be chained.
string GetFirstItemInLowerCase(IEnumerable<string> collection)
{
return collection?.FirstOrDefault()?.ToLower();
/*
//Pre C# 6 code:
if(collection == null || collection.FirstOrDefault() == null) return null;
return collection.First().ToLower();
*/
}
GetFirstItemInLowerCase(new string[0])
The method above returns the first item in a collection of strings in lower case.
It returns null if the collection is null or the collection is empty or the first item in the collection is null.
Null-conditional operators make the code more succinct than the pre-C# 6 code.
The null-conditional operators work with the null coalescing operator ( ?? ) to provide a default result when the expression evaluates to null.
string[] arr = null; //new string[]{"paper","pen"};
int length = arr?[0]?.Length ?? -1;
length
In the example above, length is -1 because the null-coalescing operator provides a default value.
Replace arr = null;
with arr = new string[]{"paper","pen"}
to get the length of the first element.
String Interpolation
String interpolation lets you format strings in an easier and safer manner.
var now = DateTime.Now;
var msg = $"Have a happy {now.DayOfWeek}! Today is day {now.DayOfYear} of {now.Year}";
/* //Same as:
var msg = String.Format("Have a happy {0}! Today is day {1} of {2}", now.DayOfWeek, now.DayOfYear, now.Year);
*/
msg
In the example above, the now.DayOfWeek
, now.DayOfYear
and now.Year
expressions are placed exactly where they will appear.
You can now safely modify the string without worrying if the arguments at the end line up properly as is the case when String.Format
is used.
To insert braces in an interpolated string, specify two opening braces ( {{ ) for an opening brace or two closing braces ( }} ) for a closing brace.
Interpolated strings can span multiple lines if the $ symbol is followed by an @ symbol, keeping in style with regular C# multiline string literals.
var val1 = "interpolated";
var val2 = "multiple";
var str = $@"
This is an {val1} string
spanning {val2} lines.
Braces can be escaped like {{this}}.
";
str
Expressions in interpolated holes can be complex. Alignment and format specifiers can also be specified, just as with String.Format
, as shown in the following example.
//Standard currency numeric format
var amount = $"{-123.456:C2}";
//Guid, left aligned
var guid1 = $"|{Guid.NewGuid(),-50}|";
//Guid, right aligned with format specifier
var guid2 = $"|{Guid.NewGuid(),50:N}|";
//Custom DateTime format
var str = $"Yesterday was {DateTime.Now - TimeSpan.FromDays(1):dddd, d-MMM-yyyy.}";
Console.WriteLine(amount);
Console.WriteLine(guid1);
Console.WriteLine(guid2);
Console.WriteLine(str);
Interpolated strings are formatted using the current culture.
In a later release, interpolated strings will implement the IFormattable
interface (but will be implicitly convertible to a string). This will allow interpolated strings to be formatted in other cultures.
var invariant = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:C}", 20);
var current = string.Format(System.Globalization.CultureInfo.CurrentCulture, "{0:C}", 20);
var interpolated = $"{20:C}";
Console.WriteLine(invariant);
Console.WriteLine(current);
Console.WriteLine(interpolated);
/*
//NOTE: Will work in a future update.
//Formats interpolated string in Japanese culture
public string InJapaneseCulture(IFormattable formattable)
{
return formattable.ToString(null, new System.Globalization.CultureInfo("ja-JP"));
}
InJapaneseCulture($"{20:C}") //Displays "¥20"
*/
Parameterless constructors in structs
Structs can now have parameterless constructors.
struct Point
{
public int X {get; set;}
public int Y {get; set;}
public int Z {get; set;}
public Point()
{
X = -1;
Y = -2;
Z = -3;
}
}
new Point()
This feature is useful if you need to create a new instance of a struct with a parameterless constructor that initializes fields to values that are not the default type value.
Consider a generic method that returns a list of a generic type.
public List<T> CreateList<T>(int count) where T:new()
{
List<T> list = new List<T>();
for(int i = 0; i < count; i++)
{
list.Add(new T());
}
return list;
}
struct Point
{
public int X {get; set;}
public int Y {get; set;}
public int Z {get; set;}
public Point()
{
X = -1; Y = -2; Z = -3;
}
}
CreateList<Point>(3)
Calling CreateList<Point>(50)
in older versions of C# will return a list of 50 Point
objects, however, each point will be invalid since the X,Y and Z properties will default to zero.
With C# 6, all points in the list will have the desired initial values for X, Y and Z.
Expression bodied methods and properties
This feature lets methods and properties have bodies that are expressions instead of statement blocks.
public string Hexify(int i) => i.ToString("X");
/* //Same as:
public string Hexify(int i)
{
return i.ToString("X");
}
*/
Hexify(7012)
The body of the Hexify
method above is expressed as an expression, like a lambda expression, and is equivalent to the commented out regular style body.
Bodies of getter-only properties and indexers can also be expressed as expressions.
class Order
{
private Guid _id = Guid.NewGuid();
public string Id => _id.ToString();
/* //Same as:
public string Id
{
get
{
return _id.ToString();
}
} */
}
new Order().Id
Auto-Property Initializers
Auto properties can now be initialized with an initial value.
class Order
{
public string Instructions {get; set;} = "None";
public string Id {get;} = Guid.NewGuid().ToString();
}
new Order()
A new instance of the Order
class above will have the Instructions
property set to "None" and the Id
property set to a new Guid string.
It's important to note that the default values are initialized once on the underlying backing field. This means each call to the Id
property will return the value on the backing field and not a brand new Guid.
Getter-only auto-properties can also be assigned an initial value in the type's constructor.
class Order
{
public string Instructions {get; set;} // Okay in C# 5
public string Id {get;} // Only okay in C# 5 if class is abstract or extern
public Order()
{
Instructions = "None"; // Okay in C# 5
Id = Guid.NewGuid().ToString(); // New in C# 6
}
}
new Order()
Index Initializers
This feature lets you initialize the indices of a newly created object as part of the object initializer.
var birthdays = new Dictionary<string, DateTime> {
["Turing"] = new DateTime(1912, 6, 23),
["Lovelace"] = new DateTime(1815, 12, 10),
["Neumann"] = new DateTime(1903, 12, 28)
};
birthdays
This feature makes it easy to compose hierarchical objects, suitable for JSON serialization.
var locations = new Dictionary<string, object>
{
["Canada"] = null,
["Mexico"] = "Distrito Federal",
["United States"] = new Dictionary<string, object>
{
["Illinois"] = null,
["New York"] = "New York City",
["California"] = new string[]{"Los Angeles", "San Jose"}
}
};
locations
The object above is easier to read and compose than having to assign values to indices in separate statements after each new dictionary is created.
Exception filters
This feature allows a catch block to execute only when some criteria is met.
int state = 3;
try
{
throw new InvalidOperationException();
}
catch (InvalidOperationException) when (state == 2)
{
Console.WriteLine("Exception thrown! State = 2");
}
catch (InvalidOperationException) when (state == 3)
{
Console.WriteLine("Exception thrown! State = 3");
}
catch
{
Console.WriteLine("Exception thrown!");
}
In the example above, the first catch block is not executed because state
isn't 2, even though that catch block handles the InvalidOperationException
.
The exception processing logic then moves to the next catch block which is executed because it handles the exception type and the filter evaluates to true.
This is cleaner than having a big catch block that checks the criteria within the block. An added benefit of using exception filters instead of catching and rethrowing exceptions is that it doesn't change the stack -- useful when debugging the source of an exception.
Conclusion
C# 6 is a nice improvement that addresses many of the annoyances C# developers face daily.
I applaud the direction in which the C# team is taking the language. I really like using static, nameof expressions, null conditional operators and string interpolation.
These features will undoubtedly make life easier for many developers.