Referencing other .linq files

Summary

In LINQPad 6 and later, scripts can reference other scripts with the #load directive:

#load "SomeOtherScript.linq"   // The #load directive must appear be at the top of the script

...

This works for all types of C# script (Expression, Statements and Program). You can also #load .cs files.

Referenced scripts 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 script:

#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 script of type Program, it's rather like adding a file to a project in Visual Studio. All of the the types and methods in the #load-ed script become available for consumption (except for the Main method which LINQPad removes so that you don't end up with two Main methods). Any namespaces that the #load-ed script imported via the Script Properties dialog will remain private to that script. 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 script 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 script imported via the Script Properties dialog, are also implicitly imported into the main script.

Does the #load directive allow relative paths?

Yes. There are four ways to specify a path:

  • Absolute, e.g.,
    #load "c:\temp\script.linq"
  • Relative to the My Scripts folder, e.g.,
    #load "azure.linq"
    #load "libs\azure.linq"
  • Relative to where the script is saved, e.g.,
    #load ".\azure.linq"
    #load "..\libs\azure.linq"
  • Relative to where the script 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 script to a different folder.

Can the loaded script reference assemblies and NuGet packages?

Yes - and these will be implicitly merged into the main script. Should conflicts arise, LINQPad will pick the highest-versioned package or assembly.

Can a loaded script 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 script 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 InternalToScript
{
    ...
}

Can #load-ed scripts define namespaces?

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

void Main()
{
}

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

Can loaded scripts define extension methods?

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

Can I #load .cs files?

Yes - this is useful for sharing code between LINQPad and Visual Studio projects, particularly when you 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 script?

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 scripts 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 script and mess with tooling.

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

Can a loaded script have a connection?

Yes, but the main script 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 script). This is to provide the best experience in terms of performance and tooling. Also, loaded scripts can consume My Extensions, but not vice-versa (this is to avoid a circular reference).

Tell me about the new Hook Methods

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

  • OnStart - executes just before the script is started
  • OnFinish - executes after the main script 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 script, but they're most useful in loaded scripts. If you load multiple scripts, 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 script useful is that after saving it, you can #load it into any other script to performance-test the other script. 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 script can include the #LINQPad optimize+ directive. This can be overridden in the main script 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 script, or to analyze the source (use Util.CurrentScript 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.