Have you ever needed the results of a method in an instance, but not have the required dependencies to instantiate the object? If you answered yes, let me start by saying you’re a weirdo, a good weirdo, but still a weirdo. Secondly, do I have a solution for you! In this post, we will explore a little-known feature of .NET’s compiler services that will allow you to create an “uninitialized” object instance without calling any of its constructors or initializers. Let’s get started.

How I Found Out about RuntimeHelpers?

I recently experimented with Fast Endpoints, a lightweight REST API framework for ASP.NET Core 6. It offers an alternative way of writing and registering endpoints within your backend host application. In my opinion, it’s elegant, as all your related information can exist in a single file.

public class Root : Endpoint<Root.Request, Root.Result>
{
    private readonly ILogger<Root> logger;

    public Root(ILogger<Root> logger)
    {
        this.logger = logger;
    }
    
    public override void Configure()
    {
        Verbs(Http.GET, Http.POST);
        Routes("/", "/{Name}");
        AllowAnonymous();
    }

    public override async Task HandleAsync(Request req, CancellationToken ct)
    {
        await SendAsync(new Result { Message = $"Hello {req.Name}" }, 
            cancellation: ct);
    }

    public class Result
    {
        public string Message { get; set; } = "";
    }

    public class Request
    {
        public string Name { get; set; } = "World";
    }
}

It’s neat, but I immediately thought about constructor injection and route registration. The very thought being, “won’t every route’s constructor need to be invoked to register it?”. Well, not based on the internal implementation of Fast Endpoints. In the preceding code, you likely notice a Configure method. This method allows you to initialize the endpoint data without a dependency on external services. When the web application starts, Fast Endpoints finds all instances of Endpoint, creates uninitialized instances of each endpoint, calls the Configure method, then accesses the resulting EndpointDefinition instance to register with ASP.NET Core’s endpoints.

What was surprising to me is that you could create an instance of any class but not call any of the initialization steps during the creation of the object. That includes constructors and initializers.

How To Create Uninitialized Object Instances

First, you’ll need to reference the System.Runtime.CompilerServices namespace. This namespace contains the RuntimeHelpers class, which has the static method of GetUnitializedObject. Let’s see how it works in practice.

using System.Runtime.CompilerServices;

var o = RuntimeHelpers.GetUninitializedObject(typeof(Something));

if (o is Something something)
{
    Console.WriteLine($"Hello, {something.GetName()}");
    Console.WriteLine($"Hello, {something.Name ?? "(null)"}");
}

public class Something
{
    public string? Name { get; } = "Nicole";

    public Something(string? name)
    {
        this.Name = name;
    }

    public string GetName()
    {
        return Name ?? "Khalid";
    }
}

What would you expect the application output to be in the preceding code? Keep in mind the theme of this blog post. If you guessed the following, then you’re right!

Hello, Khalid
Hello, (null)

So a few things happened in the code above: I created an instance of Something without invoking the object’s constructor. I did not invoke the property initializer, so the Name property is still null. I was able to call both the GetName method and the Name property with varying results.

So cool!

What does this mean?

For me, this approach opens up an opportunity for framework builders to use inheritance and interface implementations to configure elements in a framework. C# code might be more discoverable and less error-prone than remembering which attributes to put on a class definition. That said, this approach can come with misunderstandings that could be difficult to diagnose. For example, the code above initializes the Name property, but that initialization never occurs, resulting in a (null) result in the output.

Conclusion

While I recommend you use this technique sparingly, it’s always great to know what options framework developers have at their disposal. Some caveats may make this difficult to use, but this approach has additional benefits. Let me know if you’ve used this approach before or are just learning about it on Twitter, at @buhakmeh. As always, thanks for reading.