Javascript Clean Code Principles

One of the books that has most influenced my life is The Elements of Style by Strunk and White. I took a technical writing class in college where we closely studied its recommendations. The book is short and contains over 100 side-by-side comparisons of less effective and more effective writing.

Reading it made me realize that I learn well by example and comparison. I’ve long wanted to write an article that shows less effective and more effective programming approaches by comparison for those who also learn well by comparison.

Today I’m going to lay out what I’ve found to be the most important principles for writing clean code. In the first section, the examples are written in JavaScript, but they apply to almost every language. In the second section, the examples are specific to React.

Before we start the side-by-side comparisons, I want to make a recommendation that needs no side-by-side view.

Use prettier

If you have not heard of it, prettier is an automated code formatting tool. The idea is that you add a prettier config file to your project, and request all your teammates or contributors to enable an IDE plugin that re-formats code on save.

Never again will my team have an argument about tabs vs spaces or 80-column wrap vs 120-column wrap. It will also settle disputes about what types of quotes to use, whether to use semicolons or what spacing to use around brackets.

Prettier was created for JavaScript, JSX, and JSON, but it has plugins for HTML, CSS, md, XML, YAML, toml, PHP, python, ruby, java, shell and many more

My favorite thing is that I concentrate on code and not on formatting. I can quickly add code without proper newlines or spacing and then watch prettier magically format the new code.

Let’s start

Each recommendation below has a very short description and a code example so you can compare more effective vs. less-effective approaches.

Exit early when possible

When writing a function, consider the negative outcomes that would allow you to exit early from the function. You’ll find your code has fewer indentations and is easier to read.

Be expressive, not clever

Of the two functions below, which would you rather come across in a project? Maybe the first one is clever and concise, but how much time does it take you to tweak the functionality?

Make variable names descriptive

When you write code, you may have only one thing on your mind. But when you come back later to look at code, descriptive variable names are very helpful.

Prefer for-of loops

for-of loops have some advantages over for-i, forEach and for-in loops:

Fewer characters
Ability to continue, return or break from the loop
Easier to read and follow

Prefix booleans with verbs such as “is” and “has”

Verbs help set a boolean apart from other types.

Avoid double negatives.

Sometimes they’re subtle and lead to cheeky bugs.

Avoid using “!” with “else”

Instead, use the positive form in the if condition.

Prefer string interpolation over concatenation

It’s almost always more readable to interpolate.

Avoid using the ternary operator to a return boolean value

In return statements, ternary operators are redundant.

Use try-catch with await

async await makes code more readable than a tree of .then() calls. But don’t forget that you need to catch rejections that await-ed values might throw.

Avoid using “magic” numbers

Any number or string that has a non-obvious meaning should be declared as a separate, descriptively named variable.

Avoid declaring functions with more than 2 arguments

Arguments should have a logical order. When you have 3 or more arguments, the order is often not obvious. Yes, we have intellisense in our IDE, but save some thought cycles by accepting “named” arguments if appropriate.

Prefer objects to boolean arguments

Code that calls the function will be cleaner and more obvious.

A Section on React

JSX and React have their own challenges that deserve some extra attention.

Declare DOM only once per function

You can take one of three approaches to avoid it:

Break components into smaller units
Use && as a stand-in for if blocks
Use the ternary operator for if-else blocks

Make your own wrappers on top of UI libraries

Your project might rely on MUI or another UI library for all it’s components. But keeping your UI consistent can be challenging if you have to remember sizes, colors and variants. In the example below, the project wants to always use medium outlined buttons in MUI.

Mind the guard operator

In Javascript, && is the guard operator not a boolean operator; it returns the first operand that is truthy.

A final word

Writing clean code takes practice. Be a code craftsman and take the time to learn good principles and make good habits.

The post Javascript Clean Code Principles appeared first on Flatlogic Blog.

Flatlogic Admin Templates banner

A pattern for dealing with #legacy code in c#

static string legacy_code(int input)
{
// some magic process
const int magicNumber = 7;

var intermediaryValue = input + magicNumber;

return “The answer is ” + intermediaryValue;
}

When dealing with a project more than a few years old, the issue of legacy code crops up time and time again. In this case, you have a function that’s called from lots of different client applications, so you can’t change it without breaking the client apps.

I’m using the code example above to keep the illustration simple, but you have to imagine that this function “legacy_code(int)”, in reality, could be hundreds of lines long, with lots of quirks and complexities. So you really don’t want to duplicate it.

Now, imagine, that as an output, I want to have just the intermediary value, not the string “The answer is …”. My client could parse the number out of the string, but that’s a horrible extra step to put on the client.

Otherwise you could create “legacy_code_internal()” that returns the int, and legacy_code() calls legacy_code_internal() and adds the string. This is the most common approach, but can end up with a rat’s nest of _internal() functions.

Here’s another approach – you can tell me what you think :

static string legacy_code(int input, Action<int> intermediary = null)
{
// some magic process
const int magicNumber = 7;

var intermediaryValue = input + magicNumber;

if (intermediary != null) intermediary(intermediaryValue);

return “The answer is ” + intermediaryValue;
}

Here, we can pass an optional function into the legacy_code function, that if present, will return the intermediaryValue as an int, without interfering with how the code is called by existing clients.

A new client looking to use the new functionality could call;

int intermediaryValue = 0;
var answer = legacy_code(4, i => intermediaryValue = i);
Console.WriteLine(answer);
Console.WriteLine(intermediaryValue);

This approach could return more than one object, but this could get very messy.

Flatlogic Admin Templates banner

Welcome to C# 10

Today, we are happy to announce the release of C# 10 as part of .NET 6 and Visual Studio 2022. In this post, we’re covering a lot of the new C# 10 features that make your code prettier, more expressive, and faster.

Read the Visual Studio 2022 announcement and the .NET 6 announcement to find out more, including how to install.

Global and implicit usings

using directives simplify how you work with namespaces. C# 10 includes a new global using directive and implicit usings to reduce the number of usings you need to specify at the top of each file.

Global using directives

If the keyword global appears prior to a using directive, that using applies to the entire project:

global using System;

You can use any feature of using within a global using directive. For example, adding static imports a type and makes the type’s members and nested types available throughout your project. If you use an alias in your using directive, that alias will also affect your entire project:

global using static System.Console;
global using Env = System.Environment;

You can put global usings in any .cs file, including Program.cs or a specifically named file like globalusings.cs. The scope of global usings is the current compilation, which generally corresponds to the current project.

For more information, see global using directives.

Implicit usings

The Implicit usings feature automatically adds common global using directives for the type of project you are building. To enable implicit usings set the ImplicitUsings property in your .csproj file:

<PropertyGroup>
<!– Other properties like OutputType and TargetFramework –>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

Implicit usings are enabled in the new .NET 6 templates. Read more about the changes to the .NET 6 templates at this blog post.

The specific set of global using directives included depend on the type of application you are building. For example, implicit usings for a console application or a class library are different than those for an ASP.NET application.

For more information, see this implicit usings article.

Combining using features

Traditional using directives at the top of your files, global using directives, and implicit usings work well together. Implicit usings let you include the .NET namespaces appropriate to the kind of project you’re building with a single line in your project file. global using directives let you include additional namespaces to make them available throughout your project. The using directives at the top of your code files let you include namespaces used by just a few files in your project.

Regardless of how they are defined, extra using directives increase the possibility of ambiguity in name resolution. If you encounter this, consider adding an alias or reducing the number of namespaces you are importing. For example, you can replace global using directives with explicit using directives at the top of a subset of files.

If you need to remove namespaces that have been included via implicit usings, you can specify them in your project file:

<ItemGroup>
<Using Remove=”System.Threading.Tasks” />
</ItemGroup>

You can also add namespace that behave as though they were global using directives, you can add Using items to your project file, for example:

<ItemGroup>
<Using Include=”System.IO.Pipes” />
</ItemGroup>

File-scoped namespaces

Many files contain code for a single namespace. Starting in C# 10, you can include a namespace as a statement, followed by a semi-colon and without the curly brackets:

namespace MyCompany.MyNamespace;

class MyClass // Note: no indentation
{ … }

This simplifies the code and removes a level of nesting. Only one file-scoped namespace declaration is allowed, and it must come before any types are declared.

For more information about file-scoped namespaces, see the namespace keyword article.

Improvements for lambda expressions and method groups

We’ve made several improvements to both the types and the syntax surrounding lambdas. We expect these to be widely useful, and one of the driving scenarios has been to make ASP.NET Minimal APIs even more straightforward.

Natural types for lambdas

Lambda expressions now sometimes have a “natural” type. This means that the compiler can often infer the type of the lambda expression.

Up until now a lambda expression had to be converted to a delegate or an expression type. For most purposes you’d use one of the overloaded Func<…> or Action<…> delegate types in the BCL:

Func<string, int> parse = (string s) => int.Parse(s);

Starting with C# 10, however, if a lambda does not have such a “target type” we will try to compute one for you:

var parse = (string s) => int.Parse(s);

You can hover over var parse in your favorite editor and see that the type is still Func<string, int>. In general, the compiler will use an available Func or Action delegate, if a suitable one exists. Otherwise, it will synthesize a delegate type (for example, when you have ref parameters or have a large number of parameters).

Not all lambdas have natural types – some just don’t have enough type information. For instance, leaving off parameter types will leave the compiler unable to decide which delegate type to use:

var parse = s => int.Parse(s); // ERROR: Not enough type info in the lambda

The natural type of lambdas means that they can be assigned to a weaker type, such as object or Delegate:

object parse = (string s) => int.Parse(s); // Func<string, int>
Delegate parse = (string s) => int.Parse(s); // Func<string, int>

When it comes to expression trees we do a combination of “target” and “natural” typing. If the target type is LambdaExpression or the non-generic Expression (base type for all expression trees) and the lambda has a natural delegate type D we will instead produce an Expression<D>:

LambdaExpression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>
Expression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>

Natural types for method groups

Method groups (that is, method names without argument lists) now also sometimes have a natural type. You have always been able to convert a method group to a compatible delegate type:

Func<int> read = Console.Read;
Action<string> write = Console.Write;

Now, if the method group has just one overload it will have a natural type:

var read = Console.Read; // Just one overload; Func<int> inferred
var write = Console.Write; // ERROR: Multiple overloads, can’t choose

Return types for lambdas

In the previous examples, the return type of the lambda expression was obvious and was just being inferred. That isn’t always the case:

var choose = (bool b) => b ? 1 : “two”; // ERROR: Can’t infer return type

In C# 10, you can specify an explicit return type on a lambda expression, just like you do on a method or a local function. The return type goes right before the parameters. When you specify an explicit return type, the parameters must be parenthesized, so that it’s not too confusing to the compiler or other developers:

var choose = object (bool b) => b ? 1 : “two”; // Func<bool, object>

Attributes on lambdas

Starting in C# 10, you can put attributes on lambda expressions in the same way you do for methods and local functions. They go right where you expect; at the beginning. Once again, the lambda’s parameter list must be parenthesized when there are attributes:

Func<string, int> parse = [Example(1)] (s) => int.Parse(s);
var choose = [Example(2)][Example(3)] object (bool b) => b ? 1 : “two”;

Just like local functions, attributes can be applied to lambdas if they are valid on AttributeTargets.Method.

Lambdas are invoked differently than methods and local functions, and as a result attributes do not have any effect when the lambda is invoked. However, attributes on lambdas are still useful for code analysis, and they are also emitted on the methods that the compiler generates under the hood for lambdas, so they can be discovered via reflection.

Improvements to structs

C# 10 introduces features for structs that provide better parity between structs and classes. These new features include parameterless constructors, field initializers, record structs and with expressions.

Parameterless struct constructors and field initializers

Prior to C# 10, every struct had an implicit public parameterless constructor that set the struct’s fields to default. It was an error for you to create a parameterless constructor on a struct.

Starting in C# 10, you can include your own parameterless struct constructors. If you don’t supply one, the implicit parameterless constructor will be supplied to set all fields to their default. Parameterless constructors you create in structs must be public and cannot be partial:

public struct Address
{
public Address()
{
City = “<unknown>”;
}
public string City { get; init; }
}

You can initialize fields in a parameterless constructor as above, or you can initialize them via field or property initializers:

public struct Address
{
public string City { get; init; } = “<unknown>”;
}

Structs that are created via default or as part of array allocation ignore explicit parameterless constructors, and always set struct members to their default values. For more information about parameterless constructors in structs, see the struct type.

record structs

Starting in C# 10, records can now be defined with record struct. These are similar to record classes that were introduced in C# 9:

public record struct Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}

You can continue to define record classes with record, or you can use record class for clarity.

Structs already had value equality – when you compare them it is by value. Record structs add IEquatable<T> support and the == operator. Record structs provide a custom implementation of IEquatable<T> to avoid the performance issues of reflection, and they include record features like a ToString() override.

Record structs can be positional, with a primary constructor implicitly declaring public members:

public record struct Person(string FirstName, string LastName);

The parameters of the primary constructor become public auto-implemented properties of the record struct. Unlike record classes, the implicitly created properties are read/write. This makes it easier to convert tuples to named types. Changing return types from a tuple like (string FirstName, string LastName) to a named type of Person can clean up your code and guarantee consistent member names. Declaring the positional record struct is easy and keeps the mutable semantics.

If you declare a property or field with the same name as a primary constructor parameter, no auto-property will be synthesized and yours will be used.

To create an immutable record struct, add readonly to the struct (as you can to any struct) or apply readonly to individual properties. Object initializers are part of the construction phase where readonly properties can be set. Here is just one of the ways you can work with immutable record structs:

var person = new Person { FirstName = “Mads”, LastName = “Torgersen”};

public readonly record struct Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}

Find out more about record structs in this article.

sealed modifier on ToString() in record classes

Record classes have also been improved. Starting in C# 10 the ToString() method can include the sealed modifier, which prevents the compiler from synthesizing a ToString implementation for any derived records.

Find out more about ToString() in records in this article.

with expressions on structs and anonymous types

C# 10 supports with expressions for all structs, including record structs, as well as for anonymous types:

var person2 = person with { LastName = “Kristensen” };

This returns a new instance with the new value. You can update any number of values. Values you do not set will retain the same value as the initial instance.

Learn more about with in this article

Interpolated string improvements

When we added interpolated strings to C# we always felt that there was more that could be done with that syntax down the line, both for performance and expressiveness. With C# 10 that time has come!

Interpolated string handlers

Today the compiler turns interpolated strings into a call to string.Format. This can lead to a lot of allocations – the boxing of arguments, allocation of an argument array, and of course the resulting string itself. Also, it leaves no wiggle room in the meaning of the actual interpolation.

In C# 10 we’ve added a library pattern that allows an API to “take over” the handling of an interpolated string argument expression. As an example, consider StringBuilder.Append:

var sb = new StringBuilder();
sb.Append($”Hello {args[0]}, how are you?”);

Up until now, this would call the Append(string? value) overload with a newly allocated and computed string, appending that to the StringBuilder in one chunk. However, Append now has a new overload Append(ref StringBuilder.AppendInterpolatedStringHandler handler) which takes precedence over the string overload when an interpolated string is used as argument.

In general, when you see parameter types of the form SomethingInterpolatedStringHandler the API author has done some work behind the scenes to handle interpolated strings more appropriately for their purposes. In the case of our Append example, the strings “Hello “, args[0] and “, how are you?” will be individually appended to the StringBuilder, which is much more efficient and has the same outcome.

Sometimes you want to do the work of building the string only under certain conditions. An example is Debug.Assert:

Debug.Assert(condition, $”{SomethingExpensiveHappensHere()”);

In most cases, the condition will be true and the second parameter is unused. However, all of the arguments are computed on every call, needlessly slowing down execution. Debug.Assert now has an overload with a custom interpolated string builder, which ensures that the second argument isn’t even evaluated unless the condition is false.

Finally, here’s an example of actually changing the behavior of string interpolation in a given call: String.Create() lets you specify the IFormatProvider used to format the expressions in the holes of the interpolated string argument itself:

String.Create(CultureInfo.InvariantCulture, $”The result is {result}”);

You can learn more about interpolated string handlers, in this article and this tutorial on creating a custom handler.

Constant interpolated strings

If all the holes of an interpolated string are constant strings, then the resulting string is now also constant. This lets you use string interpolation syntax in more places, like attributes:

[Obsolete($”Call {nameof(Discard)} instead”)]

Note that the holes must be filled with constant strings. Other types, like numeric or date values, cannot be used because they are sensitive to Culture, and can’t be computed at compile time.

Other improvements

C# 10 has a number of smaller improvements across the language. Some of these just make C# work in the way you expect.

Mix declarations and variables in deconstruction

Prior to C# 10, deconstruction required all variables to be new, or all of them to be previously declared. In C# 10, you can mix:

int x2;
int y2;
(x2, y2) = (0, 1); // Works in C# 9
(var x, var y) = (0, 1); // Works in C# 9
(x2, var y3) = (0, 1); // Works in C# 10 onwards

Find out more in the article on deconstruction.

Improved definite assignment

C# produces errors if you use a value that has not been definitely assigned. C# 10 understands your code better and produces less spurious errors. These same improvements also mean you’ll see less spurious errors and warnings for null references.

Find out more about C# definite assignment in the what’s new in C# 10 article.

Extended property patterns

C# 10 adds extended property patterns to make it easier to access nested property values in patterns. For example, if we add an address to the Person record above, we can pattern match in both of the ways shown here:

object obj = new Person
{
FirstName = “Kathleen”,
LastName = “Dollard”,
Address = new Address { City = “Seattle” }
};

if (obj is Person { Address: { City: “Seattle” } })
Console.WriteLine(“Seattle”);

if (obj is Person { Address.City: “Seattle” }) // Extended property pattern
Console.WriteLine(“Seattle”);

The extended property pattern simplifies the code and makes it easier to read, particularly when matching against multiple properties.

Find out more about extended property patterns in the pattern matching article.

Caller expression attribute

CallerArgumentExpressionAttribute supplies information about the context of a method call. Like the other CompilerServices attributes, this attribute is applied to an optional parameter. In this case, a string:

void CheckExpression(bool condition,
[CallerArgumentExpression(“condition”)] string? message = null )
{
Console.WriteLine($”Condition: {message}”);
}

The parameter name passed to CallerArgumentExpression is the name of a different parameter. The expression passed as the argument to that parameter will be contained in the string. For example,

var a = 6;
var b = true;
CheckExpression(true);
CheckExpression(b);
CheckExpression(a > 5);

// Output:
// Condition: true
// Condition: b
// Condition: a > 5

A good example of how this attribute can be used is ArgumentNullException.ThrowIfNull(). It avoids have to pass in the parameter name by defaulting it from the provided value:

void MyMethod(object value)
{
ArgumentNullException.ThrowIfNull(value);
}

Find out more about CallerArgumentExpressionAttribute

Preview features

C# 10 GA includes static abstract members in interfaces as a preview feature. Rolling out a preview feature in GA allows us to get feedback on a feature that will take longer than a single release to create. Static abstract members in interfaces is the basis for a new set of generic math constraints that allow you to abstract over which operators are available. You can read more about generic math constraints in this article.

Closing

Install .NET 6 or Visual Studio 2022, enjoy C# 10, and tell us what you think!

Kathleen Dollard (PM for the .NET Languages) and Mads Torgersen (C# Lead Designer)

The post Welcome to C# 10 appeared first on .NET Blog.

Understanding the cost of C# delegates

Delegates are widely used in C# (and .NET, in general). Either as event handlers, callbacks, or as logic to be used by other code (as in LINQ).

Despite their wide usage, it’s not always obvious to the developer what delegate instantiation will look like. In this post, I’m going to show various usages of delegates and what code they generate so that you can see the costs associated with using them in your code.

Explicit instantiation

Throughout the evolution of the C# language, delegate invocation has evolved with new patterns without breaking the previously existing patterns.

Initially (versions 1.0 and 1.2), the only instantiation pattern available was the explicit invocation of the delegate type constructor with a method group:

delegate void D(int x);

class C
{
public static void M1(int i) {…}
public void M2(int i) {…}
}

class Test
{
static void Main() {
D cd1 = new D(C.M1); // static method
C t = new C();
D cd2 = new D(t.M2); // instance method
D cd3 = new D(cd2); // another delegate
}
}

Implicit conversion

C# 2.0 introduced method group conversions where an implicit conversion (Implicit conversions) exists from a method group (Expression classifications) to a compatible delegate type.

This allowed for short-hand instantiation of delegates:

delegate string D1(object o);

delegate object D2(string s);

delegate object D3();

delegate string D4(object o, params object[] a);

delegate string D5(int i);

class Test
{
static string F(object o) {…}

static void G() {
D1 d1 = F; // Ok
D2 d2 = F; // Ok
D3 d3 = F; // Error — not applicable
D4 d4 = F; // Error — not applicable in normal form
D5 d5 = F; // Error — applicable but not compatible

}
}

The assignment to d1 implicitly converts the method group F to a value of type D1.

The assignment to d2 shows how it is possible to create a delegate to a method that has less derived (contravariant) parameter types and a more derived (covariant) return type.

The assignment to d3 shows how no conversion exists if the method is not applicable.

The assignment to d4 shows how the method must be applicable in its normal form.

The assignment to d5 shows how parameter and return types of the delegate and method are allowed to differ only for reference types.

The compiler will translate the above code to:

delegate string D1(object o);

delegate object D2(string s);

delegate object D3();

delegate string D4(object o, params object[] a);

delegate string D5(int i);

class Test
{
static string F(object o) {…}

static void G() {
D1 d1 = new D1(F); // Ok
D2 d2 = new D2(F); // Ok
D3 d3 = new D3(F); // Error — not applicable
D4 d4 = new D4(F); // Error — not applicable in normal form
D5 d5 = new D5(F); // Error — applicable but not compatible

}
}

As with all other implicit and explicit conversions, the cast operator can be used to explicitly perform a method group conversion. Thus, this code:

object obj = (EventHandler)myDialog.OkClick;

will be converted by the compiler to:

object obj = new EventHandler(myDialog.OkClick);

This instantiation pattern might create a performance issue in loops or frequently invoke code.

This innocent looking code:

static void Sort(string[] lines, Comparison<string> comparison)
{
Array.Sort(lines, comparison);
}


Sort(lines, StringComparer.OrdinalIgnoreCase.Compare);

Will be translated to:

static void Sort(string[] lines, Comparison<string> comparison)
{
Array.Sort(lines, comparison);
}


Sort(lines, new Comparison<string>(StringComparer.OrdinalIgnoreCase.Compare));

Which means that an instance of the delegate will be created on every invocation. A delegate instance that will have to be later collected by the garbage collector (GC).

One way to avoid this repeated instantiation of delegates is to pre-instantiate it:

static void Sort(string[] lines, Comparison<string> comparison)
{
Array.Sort(lines, comparison);
}


private static Comparison<string> OrdinalIgnoreCaseComparison = StringComparer.OrdinalIgnoreCase.Compare;


Sort(lines, OrdinalIgnoreCaseComparison);

Which will be translated by the compiler to:

static void Sort(string[] lines, Comparison<string> comparison)
{
Array.Sort(lines, comparison);
}


private static Comparison<string> OrdinalIgnoreCaseComparison = new Comparison<string>(StringComparer.OrdinalIgnoreCase.Compare);


Sort(lines, OrdinalIgnoreCaseComparison);

Now, only one instance of the delegate will be created.

Anonymous functions

C# 2.0 also introduced the concept of anonymous method expressions as a way to write unnamed inline statement blocks that can be executed in a delegate invocation.

Like a method group, an anonymous function expression can be implicitly converted to a compatible delegate.

C# 3.0 introduced the possibility of declaring anonymous functions using lambda expressions.

Being a new language concept allowed the compiler designers to interpret the expressions in new ways.

The compiler can generate a static method and optimize the delegate creation if the expression has no external dependencies:

This code:

int ExecuteOperation(int a, int b, Func<int, int, int> operation)
{
return operation(a, b);
}


var r = ExecuteOperation(2, 3, (a, b) => a + b);

Will be translated to:

[Serializable]
[CompilerGenerated]
private sealed class <>c
{
public static readonly <>c <>9 = new <>c();

public static Func<int, int, int> <>9__4_0;

internal int <M>b__4_0(int a, int b)
{
return a + b;
}
}

int ExecuteOperation(int a, int b, Func<int, int, int> operation)
{
return operation(a, b);
}


var r = ExecuteOperation(2, 3, <>c.<>9__4_0 ?? (<>c.<>9__4_0 = new Func<int, int, int>(<>c.<>9.<M>b__4_0)));

The compiler is, now, “smart” enough to instantiate the delegate only on the first use.

As you can see, the member names generated by the C# compiler are not valid C# identifiers. They are valid IL identifiers, though. The reason the compiler generates names like this is to avoid name collisions with user code. There is no way to write C# source code that will have identifiers with < or >.

This optimization is only possible because the operation is a static function. If, instead, the code was like this:

int ExecuteOperation(int a, int b, Func<int, int, int> operation)
{
return operation(a, b);
}

int Add(int a, int b) => a + b;


var r = ExecuteOperation(2, 3, (a, b) => Add(a, b));

We’d be back to a delegate instantiation for every invocation:

int ExecuteOperation(int a, int b, Func<int, int, int> operation)
{
return operation(a, b);
}

int Add(int a, int b) => a + b;

int <M>b__4_0(int a, int b) => Add(a, b);


var r = ExecuteOperation (2, 3, new Func<int, int, int> (<M>b__4_0));

This is due to the operation being dependent on the instance invoking the operation.

On the other hand, if the operation is a static function:

int ExecuteOperation(int a, int b, Func<int, int, int> operation)
{
return operation(a, b);
}

static int Add(int a, int b) => a + b;


var r = ExecuteOperation(2, 3, (a, b) => Add(a, b));

The compiler is clever enough to optimize the code:

[Serializable]
[CompilerGenerated]
private sealed class <>c
{
public static readonly <>c <>9 = new <>c();

public static Func<int, int, int> <>9__4_0;

internal int <M>b__4_0(int a, int b)
{
return Add(a, b);
}
}

int ExecuteOperation(int a, int b, Func<int, int, int> operation)
{
return operation(a, b);
}

static int Add(int a, int b) => a + b;


var r = ExecuteOperation(2, 3, <>c.<>9__4_0 ?? (<>c.<>9__4_0 = new Func<int, int, int>(<>c.<>9.<M>b__4_0)));

Closures

Whenever a lambda (or anonymous) expression references a value outside of the expression, a closure class will always be created to hold that value, even if the expression would, otherwise, be static.

This code:

int ExecuteOperation(int a, int b, Func<int, int, int> operation)
{
return operation(a, b);
}

static int Add(int a, int b) => a + b;


var o = GetOffset();
var r = ExecuteOperation(2, 3, (a, b) => Add(a, b) + o);

Wiil cause the compiler to generate this code:

[CompilerGenerated]
private sealed class <>c__DisplayClass4_0
{
public int o;

internal int <N>b__0(int a, int b)
{
return Add(a, b) + o;
}
}

int ExecuteOperation(int a, int b, Func<int, int, int> operation)
{
return operation(a, b);
}

static int Add(int a, int b) => a + b;


<>c__DisplayClass4_0 <>c__DisplayClass4_ = new <>c__DisplayClass4_0();
<>c__DisplayClass4_.o = GetOffset();
ExecuteOperation(2, 3, new Func<int, int, int>(<>c__DisplayClass4_.<M>b__0));

Now, not only a new delegate will be instantiated, an instance of class to hold the dependent value. This compiler generated field to capture the variables is what is called in computer science a closure.

Closures allow the generated function to access the variables in the scope where they were defined.

However, by capturing the local environment or context, closure can unexpectedly hold a reference to resources that would otherwise be collected sooner causing them to be promoted to highier generations and, thus, incur in more CPU load because of the work the garbage collector (GC) needs to perform to reclaim that memory.

Static anonymous functions

Because it’s very easy to write a lambda expression that starts with the intention of being static and ends up being not static, C# 9.0 introduces static anonymous functions by allowing the static modifier to be applied to a lambda (or anonymous) expression to ensure that the expression is static:

var r = ExecuteOperation(2, 3, static (a, b) => Add(a, b));

If the same changes above are made, now the compiler will “complain”:

var o = GetOffset();
var r = ExecuteOperation(2, 3, static (a, b) => Add(a, b) + o); // Error CS8820: A static anonymous function cannot contain a reference to ‘o’

Workarounds

What can a developer do to avoid these unwanted instantiations?

We’ve seen what the compiler does, so, we can do the same.

With this small change to the code:

int ExecuteOperation(int a, int b, Func<int, int, int> operation)
{
return operation(a, b);
}

int Add(int a, int b) => a + b;
Func<int, int, int> addDelegate;


var r = ExecuteOperation(2, 3, addDelegate ?? (addDelegate = (a, b) => Add(a, b));

The only thing the compiler will have to do now is add the delegate instantiation, but the same instance of the delegate will be used throughout the lifetime of the enclosing type.

int ExecuteOperation(int a, int b, Func<int, int, int> operation)
{
return operation(a, b);
}

int Add(int a, int b) => a + b;
Func<int, int, int> addDelegate;


var r = ExecuteOperation(2, 3, addDelegate ?? (addDelegate = new Func<int, int, int>((a, b) => Add(a, b)));

Closing

We’ve seen the different ways of using delegates and the code generated by the compiler and its side effects.

Delegates have powerful features such as capturing local variables. And while these features can make you more productive, they aren’t free. Being aware of the differences in the generated code allows making informed decisions on what you value more for a given part of your application.

Instantiating a delegate more frequently can incur performance penalties by allocating more memory which also increases CPU load because of the work the garbage collector (GC) needs to perform to reclaim that memory.

For that reason, we’ve seen how we can control the code generated by the compiler in a way the best suits our performance needs.

The post Understanding the cost of C# delegates appeared first on .NET Blog.