Improving C# with TypeScript. KeyOf<T>
Tags: csharp, typescript, roslyn, dotnet
Published: 2021-10-02 13:59:19

In my previous [article](https://dev.to/byme8/aot-reflection-4ijb) was presented a way how you can improve reflection performance in some scenarios. Now I want to show how you can make it safer. The source of inspiration will be TypeScript. The `` keyof `` keyword to be precise.

## What `` keyof `` does in TypeScript

The `` keyof `` keyword allows you to declare a special enum-like type containing only the associated type's property names. Let's have a look at the folloving example:
``` ts
interface User {
    firstName: string,
    lastName: string
}

function dotIt<T>(value: T, propertyName: keyof T) : any {
    return value[propertyName]
}

let user: User = { firstName: 'Jon', lastName: 'Smith' }
let firstName = dotIt(user, 'firstName') // no error
let lastName = dotIt(user, 'lastName') // no error
let missingProperty = dotIt(user, 'test') // error because there is not property 'test'
```

It is a convenient way to make sure that nothing breaks when trying to get property value by property name.

## The `` keyof `` in `` C# ``

Now let's build an identical sample for C# utilizing the AOT.Reflection library from the previous [article](https://dev.to/byme8/aot-reflection-4ijb). It looks like that;
``` cs
using Apparatus.AOT.Reflection;

var user = new User { FirstName = "Jon", LastName = "Smith" };
var firstName = DoIt(user, "FirstName");
var lastName = DoIt(user, "LastName");
var missingProperty = DoIt(user, "Test");

object DoIt<T>(T value, string propertyName)
{
    var property = value.GetProperties()[propertyName];
    if (property.TryGetValue(value, out var propertyValue))
    {
        return propertyValue;
    }

    return null;
}

class User
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
```
As you may guess, everything will compile and we will get an exception at runtime. If we investigate further, we will find out that C# doesn't have any way to express the idea like that. It wasn't new for me. First of all the TypeScript is the script language that works on top of JavaScript. The `` keyof `` looks wonderful feature in this context. At the same time, the C# is the strongly typed language where you can't just take a value by property name so easily. But if you take to account libraries like the AOT.Reflection then you can start wondering 'what if?' and 'why not?'

## Meet `` KeyOf<T> ``

After a brief investigation, I found that adding such a concept to C# will be relatively easy. So, I started working on it because it would be a nice addition for AOT.Reflection in multiple ways. 
Firstly, it makes your code safer because it allows you to validate it during the compilation stage.
Secondly, if you read the [article](https://dev.to/byme8/aot-reflection-4ijb) about AOT.Reflection, you know that it has some limitations while interacting with the generics. The `` KeyOf<T> `` allows us to overcome some of them. For example, we have no idea what type to use in the generic method because it is just a `` T ``. Also, there is no indication in the method signature that can help figure out a reflection inside. With `` KeyOf<T> `` we have such indication. Let's have a look at how to use it by rewriting the previous sample.
``` cs
using Apparatus.AOT.Reflection;

var user = new User { FirstName = "Jon", LastName = "Smith" };
var firstName = DoIt(user, "FirstName"); // no error
var lastName = DoIt(user, "LastName"); // no error
var missingProperty = DoIt(user, "Test"); // compilation error


object DoIt<T>(T value, KeyOf<T> propertyName)
{
    var property = value.GetProperties()[propertyName];
    if (property.TryGetValue(value, out var propertyValue))
    {
        return propertyValue;
    }

    return null;
}

class User
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
```
The only part that changed is the signature of the method. Now it expects `` KeyOf<T> `` as a property name. This change allows us to add indications about the intention to use the reflection inside. So, we can run the source generator on it, and, at the same, it is the obvious place to analyze with the Roslyn analyzer. As a result, we can find all invocations and report a compilation error if you pass the wrong name.

Right now, the analyzer checks of the next case:
- string as keyof: 
``` cs 
DoIt(user, "FirstName"); // no error
DoIt(user, "Test"); // compilation error
```
- nameof as keyof: 
``` cs 
DoIt(user, nameof(User.FirstName)); // no error
DoIt(user, nameof(User)); // compilation error
```
- variable as keyof: 
``` cs 
var propertyName = "FirstName";
DoIt(user, propertyName); // compilation error, can't be validated at compile time

if(KeyOf<User>.TryParse(propertyName, out var key))
{
    DoIt(user, key); // no error
}
```
- const as keyof: 
``` cs 
const string propertyName = "FirstName";
DoIt(user, propertyName); // no error, can be validated at compile time
if(KeyOf.TryParse(propertyName, out var key))
{
    DoIt(user, key); // no error
}
```
It is possible that I forgot about something. So, if you know how to break it, please create a ticket!

## How to use it in your project

To make it work, just install the NuGet package `` Apparatus.AOT.Reflection ``:
``` dotnet add package Apparatus.AOT.Reflection ```

Then you will have access to aot reflection and `` KeyOf<T> `` itself. The reflection is not the only place where you can use it. For example, it works nicely when you need to generate some sql for tools like `` Dapper ``, when you need to serialize something, managing bindings in WPF, Xamarin, and AvaloniaUI. 

## Conclusion
When I started working on the `` KeyOf<T> `` I was fascinated by the abilities that Roslyn analyzers and source generators can give you and how easily you can add new concepts with them. Stay tuned. The name `` Improving C# with TypeScript ``was selected for a reason. More TypeScript features are coming to C# soon.

## Links
[Github](https://github.com/byme8/Apparatus.AOT.Reflection), [Nuget](https://www.nuget.org/packages/Apparatus.AOT.Reflection)

## The end