Book Image

Metaprogramming in C#

By : Einar Ingebrigtsen
Book Image

Metaprogramming in C#

By: Einar Ingebrigtsen

Overview of this book

Metaprogramming is an advanced technique that helps developers to automate repetitive tasks, generate scalable code, and enhance productivity in software development. Metaprogramming in C# is a comprehensive guide that will help you reap the full potential of metaprogramming in .NET runtime. You’ll start by learning about the .NET runtime environment and how you can use it to become a more productive developer. You'll learn how to infer types using reflection, use attributes, and create dynamic proxies. You’ll also explore the use of expressions to create and execute code and how to take advantage of Dynamic Language Runtime. But that's not all! You’ll also learn to go beyond inheritance and use method signature conventions to create easily maintainable code. Finally, you’ll dive into the world of compiler magic with Roslyn, where you'll discover how to use Roslyn to generate code, perform static code analysis, and write your own compiler extensions. By the end of this book, you’ll have a deep understanding of metaprogramming concepts and how to apply them to your C# code. You’ll be able to think about types, use attributes and expressions to generate code, and apply crosscutting concerns to improve code quality.
Table of Contents (25 chapters)
1
Part 1:Why Metaprogramming?
5
Part 2:Leveraging the Runtime
12
Part 3:Increasing Productivity, Consistency, and Quality
18
Part 4:Compiler Magic Using Roslyn

Reasoning about your code

The software industry is very young. In the last 20-30 years, we’ve seen a dramatic increase in its usage.

Today, software is engraved into every aspect of our lives – our work, our transportation, and in our homes, down to the smart light bulbs many of us have installed.

With the breadth of applications and users using the software we make, there are expectations from the software. Users today expect far more from software than they did 20 years ago. And since software is so engraved in our lives, we are far more vulnerable, which makes it a risk factor.

In this section, we’ll discuss why you, as a developer, should care about metaprogramming. We’ll go through the developer concerns, how we can do some nifty automation, and cover some of the basics around metaprogramming.

Developer concerns

For us developers, we have a lot of different aspects to cover to guarantee the success of our software.

End users have high expectations of great user experiences, and they expect to be put in the pit of success. We also need to be empathetic to the different types of users our system will have and make sure it is accessible to everyone.

Our systems need to maintain the integrity of their data and help the end users do the right thing. To do this, we need to validate all the input according to what we expect or what our data model requires. In addition, we need to have business rules that enforce the integrity of the system.

The data is also something we want to protect, so security plays an important role. Users need to be authenticated and we also want to make sure the user has the correct authorization to perform the different tasks of the system.

We must also make sure that our systems don’t have security flaws that would allow hackers to breach the system. The input from users also needs to be sanitized, to prevent malicious attacks through things such as SQL injection.

For our software to be available to our users, we need to have it running somewhere, on an on-premises server or servers with a hosting partner, or in the cloud, be it physical or virtual. This means we need to think about how we package the software and then how we get it onto the running environment.

Once it is running, we have to make sure it runs all the time and doesn’t have any downtime; our users rely on it. For this, we want to consider having more than one instance of the system running so that it can fail over to a second instance if the primary one goes down.

We also need to make sure that the environment it is running in can handle the number of users it is targeting.

Instead of just having a fail-over instance, we can scale out horizontally and have a load balancing mechanism spreading the users across the different instances we have. This makes our system a distributed system.

Those are a lot of different concerns. Ideally, you want to have different people doing different aspects of the job, but this is not always the case (depending on the size of the organization, as well as its culture). Today, you’ll often see in job ads that companies are looking for full stack developers. In many cases, this could mean the expectations are that you need to work with all of the following aspects:

  • User experience: This is all about the interaction, flows, and how it all feels
  • Accessibility: This involves creating empathetic software that is accessible to those with disabilities
  • Frontend code: This is the layout, styling, and necessary logic to make the user experience come to life
  • Backend code: This is for creating the glue that represents the domain we’re working in
  • Data modeling: This is how we store the data and model it for the usage we need
  • Authentication and authorization: This is for making sure users are authenticated and the proper authorization policies are applied to the different features
  • Security: This makes the application robust from any attacks and protects the data
  • DevOps: This involves delivering features to production in a timely fashion without any ceremony

Automation

Being humans, we make mistakes and we forget things. Sometimes, this has some really bad outcomes, such as systems going down, or worse, such as being breached by hackers.

Luckily, computers are good at doing what they’re told and repeating it endlessly. They never complain and don’t make mistakes. This means there are great opportunities for streamlining a lot of our work. As the industry has matured over the years, we have seen improved workflows and tooling that can help us achieve what we aim to achieve, often taking away tedious and time-consuming tasks.

A great example of automation is what has happened in the last decade in cloud computing. Before this, we had to set up physical hardware and often had manual routines for getting our software onto that hardware. This has completely changed into us being able to spin up anything our hearts desire with a few clicks, connect it to some continuous deployment software that will build our software, and automatically get it onto the running environment. All this can be achieved in minutes, rather than hours or days.

Metaprogramming

Where am I going with all this? Wasn’t this book supposed to be about something called metaprogramming?

Metaprogramming is all about additional information surrounding your code. This information is sometimes implicit – that is, it’s already there. Sometimes, however, it needs to be added explicitly or deliberately by you, as a developer.

The computer that runs the software only understands the machine language instructions laid out in memory for the CPU to execute. For us humans, this is less than intuitive. Early on, we came up with languages that would help us write something more friendly and we could reason about more easily. This started with the assembly language and, later, higher-level languages that would compile down to assembly language.

With this tooling in place, we gained the ability to not just translate from one language into another, but also to reason about what was going on with our code. In 1978, Stephen C. Johnson, from Bell Labs, came up with what he called lint – a static code analysis tool that could be used to reason about C code and detect potential issues with it. Today, this is common with most programming languages. For web development in JavaScript or TypeScript, we could typically add tools such as ESLint to our build pipelines to do this. With C#, we have this built into the compiler, and with the Roslyn compiler, it is completely extensible with our own custom rules, something we will cover in Chapter 17, Static Code Analysis.

For programming languages such as C/C++ that compile down to something that runs natively on the CPU, we’re limited to what we can reason about at the compile level. However, with programming languages such as Java or C#, often referred to as managed languages, we’re now running code in a managed environment. The code we write compiles down to an intermediate language that will be translated on the fly while running. These languages then carry information with them about the code we wrote – this is known as metadata. This lets us treat our programs or others as data at runtime and allows us to reason about the code; we can even discover code at runtime.

With C#, from version 1, we could add additional information and more metadata. Through the use of C# attributes, we could adorn things such as types, properties on types, and methods with additional information. This information would carry through to the running program and is something we can use to reason about our software.

For instance, with attributes, we can now add additional information that we can reason about both at compile time and runtime. We can do things such as marking properties on an object with validation information, such as [Required]:

public class RegisterPerson
{
    [Required]
    public string FirstName { get; set; }
    [Required]
    public string LastName { get; set; }
    [Required]
    public string SocialSecurityNumber { get; set; }
}

This code represents what is needed to register a person. All the properties that we required have the [Required] attribute as metadata.

Now that we have added metadata to the code, we can take concrete actions based on it.