I'll go over the features one by one, stating the C# version where it got introduced in. Noting that the current version of C# as of writing the article is C# 10.0, with a preview version of C# 11.0, as a new version is being released every year since C# 7.0 (2017). On the other hand, Java's current latest release is Java 17, with two releases per year for almost 3 years.
1. Object Initializer (C# 3.0)
This feature gives you the freedom of initializing the value of an object's fields upon instantiation without having to pass them to the constructor. You are free to initialize the fields you want and leave the ones unnecessary, and the best part is that you can still pass parameters to the constructor at the same time. Without further ado, let's jump to the code:
public class Employee {
private const int DEFAULT_SALARY = 1000;
public string Name { get; set; }
public string Department { get; set; }
public float Salary { get; }
public Employee(float salaryFactor)
{
Salary = DEFAULT_SALARY * salaryFactor;
}
public Employee()
{
Salary = DEFAULT_SALARY;
}
}
Take the above class into consideration. It has a field that gets initialized in the constructor based on the value of the parameter. Eventhough we didn't need two constructors, where we could've taken advantage of the optional parameters features, but this would feed our case, so here it is.
var employee = new Employee(1.2) { Name = "Joe Rico", Department = "DevOps" };
var defaultEmployee = new Employee() { Name = "David Hill", Department = "QA" };
Look how elegant and readable this is! I actually prefer it over having to pass the values to the constructor, as adding and removing fields won't be troublesome. In Java, on the other hand, it is very common to use the builder pattern, which is not specific to the language by the way. However, Object Initializer, in addition to being cleaner than the builder pattern, is natively supported by the language.
2. Properties (C# 1.0)
This must be the coolest feature for the language to ever have. We have seen properties in our previous feature's code, however, I called them "fields" to avoid confusion. Before explaining what a property is, let's see a simple example of it:
public class TimePeriod
{
public double Hours { get; set; }
}
In the above code, the class TimePeriod has a property named Hours. A property is basically a wrapper for a field with its getter and setter. The awesome part is that the behavior of these functions is set by default since, in most cases, their logic is the same. However, you can override this behavior as follows:
public class TimePeriod
{
private double seconds;
public double Hours
{
get { return seconds / 3600; }
set {
if (value < 0 || value > 24)
throw new ArgumentOutOfRangeException(
$"{nameof(value)} must be between 0 and 24.");
seconds = value * 3600;
}
}
}
If you thought this feature can't get cooler, you couldn't be more wrong, because you can also:
public class TimePeriod
{
public double Hours { get; set; } = 10;
}
Provide a property with a default value.
public class TimePeriod
{
public double Hours { get; private set; }
}
Add an access modifier to either modifier (getter or setter) and define its scope of accessibility
public class TimePeriod
{
public double Hours { get; }
}
Make a property read-only, where you can only give it a value in the constructor.
public class TimePeriod
{
public double Hours { get; } = 2;
}
Or you can also make it read-only with a default value!
Of course, Java has some "equivalents" to C# properties, like the annotations @Getter, @Setter, or @Data, however, these are not natively supported by the language, and they add an ugly wrapper to your class/fields.
3. Structs (C# 1.0)
Have you ever asked yourself why you need to use the class Integer (with the capital I) in Java when creating a list of integers and not simply List<int>? The answer is because primitive data types in Java cannot be used alongside classes this way. To solve this issue, C# went with structs: value types that can have methods. The difference between value and reference types is that they cannot be reassigned to a new value. That's why in C# we can do the following:
var numbers = new List<int>();
var one = int.Parse("1");
4. Operators! (C# 1.0)
Overloading operators is one of the basic features any language should have. However, I still don't understand why Java doesn't support it! To overload an operator in C#, you do it like this:
public class TimePeriod
{
public double Hours { get; set; }
public static TimePeriod operator +(TimePeriod operandOne, TimePeriod operandTwo)
{
return new TimePeriod() { Hours = operandOne.Hours + operandTwo.Hours };
}
}
var timePeriod1 = new TimePeriod() { Hours = 2 };
var timePeriod2 = new TimePeriod() { Hours = 6 };
var timePeriod3 = timePeriod1 + timePeriod2;
5. Nullable Value Types (C# 2.0)
If you're looking for the index of an object in a list, what should you return if the object was not found? -1? throw an exception? well, both options are commonly used, but doesn't it make more sense to return null?! C# introduced the feature where value types (int, bool, etc.) can have a null value! For a value type to be nullable, it needs to have a question mark after its type:
int? index = null;
6. Nullable Reference Types! (C# 8.0)
Don't you hate it when you're mid-way developing your application, and you get a null pointer exception out of nowhere? It would be a lot easier if there were type-safety. Java's solution to such a problem is with using Optionals. As much as this does the job, it really gets messy with Optionals all over the place. To tackle this, C# adopted the nullable reference type feature like in some other languages such as Kotlin. It also denotes the usage of the question mark to refer to nullability. However, breaking this rule is not yet considered an error, where only warnings will be output as there's a need to keep the language backward compatible.
string? nullableString = null;
string nonNullableString = null; // works for now but creates a warning
7. Null Propagators (C# 6.0)
Another cool feature to avoid null pointer exceptions without creating a mess in the code is using the null propagators. Of course, this feature is not C# specific, but having it adopted by the language is pretty awesome!
int? stringLength = nullableString?.length; // gets assigned to null if nullableString is null
8. Extension Methods (C# 3.0)
This is by far, the most underrated feature in C#. It gives you the ability to extend a class from a different or the same codebase. The best part is that the class doesn't have to be owned by you! For example, imagine I want to extend the int struct and add to it a method that checks if the integer is even. To do using this feature, you need to follow the example:
// the class has to be static. The name is irrelevant
public static class IntegerUtilities {
// the method has to be static, and the keyword `this`
// should precede the variable whose class we are extending
public static bool IsEven(this int number)
{
return number % 2 == 0;
}
}
var counter = 10;
Cosnole.WriteLine(counter.IsEven()); // true
9. Named and Optional Arguments (C# 4.0)
This is another feature that I don't understand why Java hasn't adopted yet. In C#, you can do it like this:
public void ExampleMethod(int required, string optionalString = "default value", int optionalInt = 2)
{
}
ExampleMethod(4, optionalInt: 0);