Interactive showdown: csi.exe verses fsi.exe

Visual Studio 2015 Update 1 brings with it a nice little utility called C# Interactive which you can access from the View menu, but we’re going to have a look at the command line version which you can run from the Developer Command Prompt for VS2015.

Using this tool you can quickly run C# using a REPL environment – which is a fast and convenient way to explorer APIs, frameworks, and script existing code from .NET assemblies in a non-compiled way.

While this is new to Visual Studio and C#, we have enjoyed this functionality for a long time with F#.

(Technically this has been around for a while, but it’s officially shipping now!)

I decided to take a look at the new csi.exe application, and compare it to how I already use fsi.exe and see if it’s enough to make me switch my default command line tool.

C# Interactive

For me the most important way I’d use C# Interactive is via the command line, so it’s important to know what it’s capable of, even though you may not need to use the advanced features right away.

To find out the current version and get a list of the command line options in C# Interactive, just add the /? switch and read the output:

PS> csi /?
Microsoft (R) Visual C# Interactive Compiler version 1.2.0.51106
Copyright (C) Microsoft Corporation. All rights reserved.

Usage: csi [option] ... [script-file.csx] [script-argument] ...

Options

/help Display this usage message (alternative form: /?)
/i Drop to REPL after executing the specified script.
/r:<file> Reference metadata from the specified assembly file (alternative form: /reference)
/r:<file list> Reference metadata from the specified assembly files (alternative form: /reference)
/lib:<path list> List of directories where to look for libraries specified by #r directive. (alternative forms: /libPath /libPaths)
/u:<namespace> Define global namespace using (alternative forms: /using, /usings, /import, /imports)
@<file> Read response file for more options
-- Indicates that the remaining arguments should not be treated as options.

Form a first look, I can see that csi.exe has all of the command line options I really want in normal use – I especially find /i to be useful – but we’ll come to that shortly.

F# Interactive

F# Interactive has been around for a lot longer, and is built on different technology under the hood – so there are a more options going on here, but we can take a look by providing a similar -? switch:

PS> fsi -?
Microsoft (R) F# Interactive version 14.0.23413.0
Copyright (c) Microsoft Corporation. All Rights Reserved.

Usage: fsi.exe <options> [script.fsx [<arguments>]]...

Input Files

--use:<file> Use the given file on startup as initial input
--load:<file> #load the given file on startup
--reference:<file> Reference an assembly (Short form: -r)
-- ... Treat remaining arguments as command line arguments, accessed using fsi.CommandLineArgs

Code Generation

--debug[+|-] Emit debug information (Short form: -g)
--debug:{full|pdbonly} Specify debugging type: full, pdbonly. ('full' is the default and enables attaching a debugger to a running program).
--optimize[+|-] Enable optimizations (Short form: -O)
--tailcalls[+|-] Enable or disable tailcalls
--crossoptimize[+|-] Enable or disable cross-module optimizations

Errors and Warnings

--warnaserror[+|-] Report all warnings as errors
--warnaserror[+|-]:<warn;...> Report specific warnings as errors
--warn:<n> Set a warning level (0-5)
--nowarn:<warn;...> Disable specific warning messages
--warnon:<warn;...> Enable specific warnings that may be off by default
--consolecolors[+|-] Output warning and error messages in color

Language

--checked[+|-]Generate overflow checks

--define:<string> Define conditional compilation symbols (Short form: -d)
--mlcompatibility Ignore ML compatibility warnings

Miscellaneous

--nologo Suppress compiler copyright message
--help Display this usage message (Short form: -?)

Advanced

--codepage:<n> Specify the codepage used to read source files
--utf8output Output messages in UTF-8 encoding
--fullpaths Output messages with fully qualified paths
--lib:<dir;...> Specify a directory for the include path which is used to resolve source files and assemblies (Short form: -I)
--noframework Do not reference the default CLI assemblies by default
--exec Exit fsi after loading the files or running the .fsx script given on the command line
--gui[+|-] Execute interactions on a Windows Forms event loop (on by default)
--quiet Suppress fsi writing to stdout
--readline[+|-] Support TAB completion in console (on by default)
--quotations-debug[+|-] Emit debug information in quotations
--shadowcopyreferences[+|-] Prevents references from being locked by the F# Interactive process

As you can see there’s a lot more options for F#, but many of them are not needed for every day use.

Quick Interactive Use

It’s fairly common that I use F# Interactive just to test out how part of the Framework behaves.

In this instance, I’ll use HttpUtility.HtmlEncode method to see see what output I get when one of my emoticons is encoded into HTML-friendly characters.

PS> fsi

Microsoft (R) F# Interactive version 14.0.23413.0
Copyright (c) Microsoft Corporation. All Rights Reserved.

For help type #help;;

> open System.Web;;
> let encode s = HttpUtility.HtmlEncode(s);;

val encode : s:string -> string

> encode "<(>_<)>";;
val it : string = "&lt;(&gt;_&lt;)&gt;"
>

This is how I’d do it in F# – we could call the HtmlEncode function directly, but creating functions is so easy with F# that we might as well shorten the name to make it nice and easy if we need to run it multiple times.

The function encode actually returns a string rather than printing it to the screen, but F# is setting that output to a special value called it – a special identifier which is used for displaying the value of the last expression on the screen. It’s handy, and you’ll see why.

Alright so here’s my first attempt to do something similar in C# Interactive.

PS> csi
Microsoft (R) Visual C# Interactive Compiler version 1.1.0.51109
Copyright (C) Microsoft Corporation. All rights reserved.

Type "#help" for more information.
> using System.Web;
> HttpUtility.HtmlEncode("<(>_<)>");
(1,1): error CS0103: The name 'HttpUtility' does not exist in the current context
>

Ah. HttpUtility is missing because it hasn’t loaded the clases from the System.Web.dll assembly. I didn’t notice on the first line becuase of the way namespaces work – the namespace exists, but not the class we want. No problem, we just reference it using #r – you reference assemblies this way in F# too!

> #r "System.Web"
> HttpUtility.HtmlEncode("<(>_<)>");
>

This worked and we have access to the static HttpUtility class and the HtmlEncode method – however the output has not been displayed to the screen because C# Interactive doesn’t have that the specal it value F# had.

I didn’t realise this at first but in the absense of the it value F# has, the C# Interactive prompt introduces a slightly different syntax for when you want to see the value.

> HttpUtility.HtmlEncode("<(>_<)>");
> HttpUtility.HtmlEncode("<(>_<)>")
"&lt;(&gt;_&lt;)&gt;"
>

Notice the difference a semicolon makes? This is important, and something I missed when first trying out C# Interactive. Avoiding the semicolon would normally result in invalid C#, but this is a great way to view the output as if you’re typing it into the Immediate Window in Visual Studio.

Let’s also create a function using normal C# syntax so that we don’t have so much typing to do. Notice that I’m going to call this function without the semicolon so that I can see the output.

> string encode(string s) { return HttpUtility.HtmlEncode(s); }
> encode("<(>_<)>")
"&lt;(&gt;_&lt;)&gt;"
>

Loading Scripts

Let’s keep things simple, we’ll take the functions we just created in each langauge, and create a script file so that they can be loaded up when we start an interactive session.

First of all, let’s do it with F#. Here’s the content of encode.fsx:

open System.Web
 
let encode s =
    HttpUtility.HtmlEncode(s)

And then we can run it from the command line using the --use switch. This will drop us into an interactive prompt after the code file has been loaded.

PS> fsi --use:.\encode.fsx

Microsoft (R) F# Interactive version 14.0.23413.0
Copyright (c) Microsoft Corporation. All Rights Reserved.

For help type #help;;

>
val encode : s:string -> string

> encode "<(>_<)>";;
val it : string = "&lt;(&gt;_&lt;)&gt;"
> encode "<(^o^)>";;
val it : string = "&lt;(^o^)&gt;"
> encode "<(T_T)>";;
val it : string = "&lt;(T_T)&gt;"
>

Not bad at all. So let’s do the same thing with the C# interactive, using a file called encode.csx:

#r "System.Web"
using System.Web;
 
string encode(string s)
{
    return HttpUtility.HtmlEncode(s);
}

I love that they used a similar extension! And again, we can run the code file and then get an interactive prompt as above using the /i switch.

PS> csi /i .\encode.csx
> encode("<(>_<)>");
&lt;(&gt;_&lt;)&gt;
> encode("<(^o^)>");
&lt;(^o^)&gt;
> encode("<(T_T)>");
&lt;(T_T)&gt;
>

We have the same end result, though like before the actual functions behave slightly differently. C# Interactive gives a cleaner output here, though you can always clean up the F# Interactive prompt a little bit by using the --nologo switch.

Use Inside PowerShell

Because I want to get access to both of these utilities as fast as possible, I have added a few lines to my PowerShell profile which will ease their use.

I’ve mentioned doing this kind of thing before – and I highly that developers using Windows spend a good amount of time learning PowerShell – but here’s a little snippet that may be useful.

$PROGFILES32 = "C:\Program Files (x86)\"
 
# create an alias to the full path of the executable
Set-Alias fsi "$PROGFILES32\Microsoft SDKs\F#\4.0\Framework\v4.0\fsi.exe"
Set-Alias csi "$PROGFILES32\MSBuild\14.0\Bin\amd64\csi.exe"
 
# add helpers which include common switches
function fsu ($fsx) { fsi --nologo --use:$fsx }
function csu ($csx) { csi /i $csx }

Adding this to my profile means I can just run them using fsu encode.fsx or csu encode.csx respectively. Very easy.