Referencing other .linq files

Summary

In LINQPad 6 and later, 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). You can also #load .cs files.

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

Can I see a simple example?

Util.linq:

void Main()
{
}
                
void OpenWithAssociatedApp (string file)
{
    Process.Start (new ProcessStartInfo (file) { UseShellExecute = true });
}

class ConnectionStrings
{
    public static string Test = "Data Source=.;Integrated Security=true;Database=test";
}

Some other query:

#load "Util.linq"

void Main()
{
    File.WriteAllText ("foo.txt", "test");

    OpenWithAssociatedApp ("foo.txt");  // Calls OpenWithAssociatedApp in Util.linq
    ConnectionStrings.Test.Dump();      // Reads ConnectionStrings.Test in Util.linq
}

How does it work?

When you #load a query of type Program, it's rather like adding a file to a project in Visual Studio. Any namespaces that the #load-ed query imported via the Query Properties dialog will remain private to that query. This makes it suitable for creating mini-libraries. LINQPad parses scripts that you #load into separate syntax trees to maintain good editor performance.

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 into the main query.

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 merged into the main query. Should conflicts arise, LINQPad will pick the highest-versioned package or assembly.

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.

LINQPad also ignores anything inside a region whose name starts with private:: when loading:

void Main()   // The Main method is stripped out when the query is #loaded.
{
    Test();
}

#region private::tests   // Everything in this region also gets stripped out when #load-ed

void Test()
{
    ...
}

#endregion

Can I use C#'s file modifier in scripts that I #load?

For scripts in 'C# Program' mode, you can apply the file modifier to types to prevent them from being directly accessed from outside the script:

file class InternalToQuery
{
    ...
}

Can #load-ed queries define namespaces?

You can put your types into namespace(s) just as you would normally:

void Main()
{
}

namespace Benchmarking
{
    class Foo
    {
        // ...
    }
}

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.

Can I #load .cs files?

Yes - this is useful for sharing code between LINQPad and Visual Studio projects. When #load-ing .cs files, you can specify a wildcard:

#load "..\..\source\myutil\*.cs"

LINQPad can handle a maximum of 250 .cs files. Subject to this, you can also walk subdirectories:

#load "..\..\source\myutil\*.cs /s"

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

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.

Note that LINQPad now has built-in support for performance-testing via BenchmarkDotNet.
To get started, press Ctrl+Shift+B, or read the online help (Ctrl+F1, "benchmark").

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

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