DEV Community

Cover image for Improving C# with TypeScript. KeyOf<T>
Stanislav Silin
Stanislav Silin

Posted on

Improving C# with TypeScript. KeyOf<T>

In my previous article 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:

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'
Enter fullscreen mode Exit fullscreen mode

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. It looks like that;

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; }
}
Enter fullscreen mode Exit fullscreen mode

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 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.

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; }
}
Enter fullscreen mode Exit fullscreen mode

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:
DoIt(user, "FirstName"); // no error
DoIt(user, "Test"); // compilation error
Enter fullscreen mode Exit fullscreen mode
  • nameof as keyof:
DoIt(user, nameof(User.FirstName)); // no error
DoIt(user, nameof(User)); // compilation error
Enter fullscreen mode Exit fullscreen mode
  • variable as keyof:
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
}
Enter fullscreen mode Exit fullscreen mode
  • const as keyof:
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
}
Enter fullscreen mode Exit fullscreen mode

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 TypeScriptwas selected for a reason. More TypeScript features are coming to C# soon.

Links

Github, Nuget

The end

Discussion (0)