Referencing other .linq files

Summary

In LINQPad 6, queries can reference other queries with the #load directive:

#load "SomeOtherQuery.linq"   // The #load directive must appear be at the top of the query

...

This works for all types of C# query (Expression, Statements and Program).

Referenced queries can also define Hook Methods (see below) for additional versatility.

How does it work?

When you #load a query of type Expression or Statements, LINQPad lexically injects its source text into the target (or the target's Main method), so it behaves rather like you did a copy-and-paste. To avoid compilation errors, any namespaces that the #load-ed query imported via the Query Properties dialog, are also implicitly imported.

When you #load a query of type Program, LINQPad loads the query into a separate syntax tree. This allows for larger inclusions without hurting editor performance, and enables each loaded query to have it own using directives. This makes it suitable for creating mini-libraries.

Does the #load directive allow relative paths?

Yes. There are four ways to specify a path:

  • Absolute, e.g.,
    #load "c:\temp\query.linq"
  • Relative to the My Queries folder, e.g.,
    #load "azure.linq"
    #load "libs\azure.linq"
  • Relative to where the query is saved, e.g.,
    #load ".\azure.linq"
    #load "..\libs\azure.linq"
  • Relative to where the query is saved using 3 dots, e.g.,
    #load "...\azure.linq"
    #load "...\libs\azure.linq"
    This tells LINQPad to keep looking up the directory hierarchy - right up to the root - until the file is found.

LINQPad will automatically correct relative #load references when you save a query to a different folder.

Can the loaded query reference assemblies and NuGet packages?

Yes - and these will be implicitly included in the main query.

Can a loaded query of type 'Program' still define a Main method?

Yes, and it gets ignored when loaded. So if you're writing a mini-library, it's a great place test your mini-library.

What happens when there's an error in a loaded query?

LINQPad will report the file and line number of the error. It also prompts you with a shortcut (Ctrl+F11) to jump to the error.

Because expression- and statements-based queries are injected, LINQPad takes the additional precaution of syntax-checking your code prior to injection, so that syntax errors don't virally infect the main query and mess with tooling.

The F12 shortcut ("go to definition") also works with symbols defined in a referenced query (as well as symbols defined in 'My Extensions').

Can a loaded query have a connection?

Yes, but for now, the main query will share that connection.

Are there any other limitations?

Right now, transitive references are not supported, so any nested #load directives are ignored (the workaround is to repeat them in the main query). This is to provide the best experience in terms of performance and tooling. Also, loaded queries can consume My Extensions, but not vice-versa (this is to avoid a circular reference).

Can loaded queries define extension methods?

Yes. It's a good idea to define your Extensions class as partial - this allows multiple #load-ed queries to share the same class name.

Tell me about the new Hook Methods

Queries can now define the following methods, which are treated specially:

  • OnStart - executes just before the query is started
  • OnFinish - executes after the main query thread finishes
  • OnInit - executes once when the process initializes (like a static constructor)
  • Hijack - runs instead of the Main method

You can define these in any query, but they're most useful in loaded queries. If you load multiple queries, each can have its own hook methods, and they'll all get executed in turn (with the exception of Hijack, which can occur only once). Hook methods must be private and parameterless.

Hijack can itself call the Main method, which leads to interesting uses, such as performance testing. To give an example, the following counts how many times your Main method code executes in one second:

#LINQPad optimize+
void Main()
{
    // The code we want to performance-test 
    System.Security.Cryptography.ProtectedData.Protect (
        new byte[] { 1, 2, 3}, null, System.Security.Cryptography.DataProtectionScope.CurrentUser);
}

void Hijack()
{
    var sw = Stopwatch.StartNew();
    int count = 0;
    using (ExecutionEngine.SuspendDump())
    {
        sw.Restart();
        do
        {
            Main();
            count++;
        }
        while (sw.ElapsedMilliseconds < 1000);
        sw.Stop();
    }
    $"{count:N0} iterations per second".Dump();
}

What makes this query useful is that after saving it, you can #load it into any other query to performance-test the other query. This also works with selected code: You can select just a line of code in the editor, hit F5, and it will performance-test just that line of code.

Some other points to note:

  • A loaded query can include the #LINQPad optimize+ directive. This can be overridden in the main query with #LINQPad optimize-
  • ExecutionEngine.SuspendDump() is a new method to suppress any Dump output, so that it doesn't interfere with performance-testing
  • You can also put code into Hijack to reflect on the query, or to analyze the source (use Util.CurrentQuery to access the source).

There's a better example of using Hijack for performance-testing here.

In that previous example, you said I can highlight a line of code to performance-test it. What if that code calls other methods?

This has been fixed in LINQPad 6. The following now works, with the call to Foo(); selected in the editor:

void Main()
{
    Foo();
    ...
}

void Foo() { }

How can I give feedback?

Please give feedback in the thread in the LINQPad Forum.