4 More C# Features you didn't know about

· c#

The theory of evolution applies quite well to C#. Each new version comes out with new features. This article will explore 4 features from different versions of the language.

I expect you to know C# or any similar programming language like Java, C++.

Operator overloading:

This is a quite nice feature that C# enjoys over other programming languages. If you can name an operator then you can probably overload it. It is really simple and consistent. What’s intriguing is that you can overload for a single operand or two in some cases. That is, -a is different to a - a. All types can have operator overloading (that is structs and classes).

Overloading the - operator for a single operand. The same way you would have -a which is the same as -1 assuming a is an integer but for a type. You can define what you want. I have a type called A with string property called Hello.

public static A operator -(A a)
{
 char[] array = a.Hello.ToCharArray();
 Array.Reverse(array);
 string reversed = new string(array);
 
 return new A
 {
 Hello = reversed
 };
}

Note the syntax is self-explanatory, the method must be static. An important caveat is that you can’t return another type as a result of the operation, or else that would defeat the purpose of operator overloading.

Now will define a two operand + overload:

public static A operator +(A a, A b)
{
 return new A
 {
 Hello = $"{a.Hello}{b.Hello}"
 };
}

Here the idea is simple. Taking 2 arguments of the same type.

Now we can return any type with 2 operands. As so:

public static string operator +(A a, A b)
{
 return $"{a.Hello}{b.Hello}";
}

Not only that you can also take 2 different arguments from differing types, one must be of the same class/struct defined in. Moreover, you can mix that with returning different types and you have a strong combination.

You can also override all other operators, the likes of == != and all the good stuff. Microsoft does a better job referencing all possible things here: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/operator-overloading

Now a nice aspect is indexers. i.e. assigning the use of [] for getters and setters. Defining the following class with the indexer:

class People
{
 public string[] Persons { get; }
 
 public People(string[] persons)
 {
 Persons = persons;
 }
 
 public int this[string person]
 {
 get => Persons.Count(x => x == person);
 }
}

Using it like:

var people = new People(new []
{
 "Ajay",
 "Thomas",
 "Ajay"
});

int count = people["Ajay"];
Console.WriteLine($"Count of Ajays: {count.ToString()}");
// Output -> Count of Ajays: 2

Now I implemented the getter of the indexer. I could also implement the setter and use it to set variables just like you would in a dictionary.

Indexers are not limited to just one variable, it could take as many as you want just like a method.

Implicit conversions:

Let’s start with an example:

public class StrongType
{
 public StrongType(string value)
 {
 if (value == "Hello" || value == "World")
 {
 Value = value;
 return;
 }
 
 throw new Exception();
 }
 
 public string Value { get; }
}

Note the type above. All well and fine. It can only be constructed if string value was right otherwise would throw. This is a normal use case of object oriented programming.

This is useful when passing in strings on any primitive for instance and you don’t have to check for the value each time. It takes one time to forget this and go over the source code for hours trying to understand the bug and fix it. In one way you can check for it each time across the codebase like the below:

public void PerformAction(string value)
{
 if (value != "Hello" || value != "World") throw new Exception();

 // Code
}

// Some where else in the code base
public void PerformOtherAction(string value)
{
 if (value != "Hello" || value != "World") throw new Exception();

 // Code
}

// Some where else
public void PerformAnotherAction(string value)
{
 // Code
}

Note the third method is not checking for valid value, it is assuming it is valid and verified.

We can fix this easily if we abstract the value to it’s own type as above in the beginning of the section, we used StrongType. This promotes the separation of concerns so that no method needs to worry itself with validation. Like so:

public void PerformAction(StrongType value)
{
 // Code
}

Now what. We have done this. The meat of this section is the following, that is implicitly converting a string into the type. i.e. passing a string directly without explicitly new-ing up the object.

PerformAction("Hello");

Will now do that using an implicit operator conversion in the class:

public static implicit operator StrongType(string value) => new StrongType(value);

This will allow us to do so legally:

StrongType type = "Hello";

I was inspired by the following user group livestream for this feature: https://youtu.be/dVmVcPEOzgs?t=3225

This feature is formally known as User-defined conversion operators.

Discards:

Have you ever wished not to use all the variables returned by a method as an out, wished to use other features only reserved within variables and more. Discards have got your back.

Let’s say we are parsing an int from string and need to check whether it is parsable. Like so:

string numStr = "123";
bool parsed = int.TryParse(numStr, out int num);

This will turn into:

string numStr = "123";
bool parsed = int.TryParse(numStr, out _);

Where the _ or the underscore is the discard. That is basically it. All it does. This is by far not the only use.

Other use cases exist for discards. The Microsoft article below discusses some use cases. I don’t find any of them worth covering. https://docs.microsoft.com/en-us/dotnet/csharp/discards

Top Level Programs:

Remember the days of having a Main method:

using System;

namespace Example
{
 class Program
 {
 static void Main(string[] args)
 {
 Console.WriteLine("Hello World!");
 }
 }
}

Now with C# 9.0, the following will do:

using System;
Console.WriteLine("Hello");

This is allows for easy scripting. You can run the program like you would Javascript. Internally the compiler puts things back into classes.

Now the following is completely valid:

using System;

Console.WriteLine("Starting program");

if (args.Length == 0)
{
 Console.WriteLine("Please input some arguments for this program to run");
}
else
{
 string x = args[0];
 Console.WriteLine($"You inputted: {x}");
}

Now look everything is valid. Plus, args is already assigned by the compiler.

Conclusion:

C# is quite an evolving language. There are plenty of features pumping each new version.

Thanks for reading this.