Use Mono's csharp Shell for Safe C# Experiments and Scripts
You will finish with a repeatable way to evaluate C# expressions, test a small script, pass arguments through Args, and recognise the shell's main traps. This guide uses the installed csharp from Mono 6.8.0.105, provided by mono-csharp-shell; the command reports compiler version 4.0.0.0.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes. You need a shell and the mono-csharp-shell package. The examples are ordinary user commands and do not need sudo. They evaluate code in the current user's process, so do not paste code you have not inspected.
1. Check the installed command
Confirm the executable and package before relying on an option. This is read-only:
$ command -v csharp
/usr/bin/csharp
$ mono --version | head -1
Mono JIT compiler version 6.8.0.105 (Debian 6.8.0.105+dfsg-3.6ubuntu2 Sun Mar 31 02:55:28 UTC 2024)
$ dpkg-query -W -f='${Package} ${Version}\n' mono-csharp-shell
mono-csharp-shell 6.8.0.105+dfsg-3.6ubuntu2
Your package revision may differ. If command -v prints nothing, stop here and install the package through your normal system administration process. This guide does not change package state.
Checkpoint
You have confirmed which csharp binary will run. A shell found earlier in PATH can otherwise make version-specific behaviour confusing.
2. Evaluate one expression and exit
Use -e when you have one expression or statement and do not need an interactive session:
$ csharp -e 'Math.Sin (Math.PI / 4)'
0.707106781186547
The output is the returned value, formatted by the shell. The exact last digits can vary with the runtime and platform. A non-zero exit status means the expression did not complete successfully, so capture it immediately when a script depends on the result:
$ csharp -e '1 + 1'
2
$ printf 'status: %s\n' "$?"
status: 0
Quote the expression for the shell as well as for C#. Single quotes keep characters such as parentheses, dollar signs and semicolons from being interpreted by the surrounding shell. Do not place an untrusted value directly inside a C# expression.
3. Start an interactive session
Run csharp with no script file when you want to inspect values across several commands:
$ csharp
Mono C# Shell, type "help;" for help
Enter statements below.
csharp> 1 + 1;
2
csharp> Math.PI;
3.14159265358979
csharp> Control-D
Type help; at the prompt to see the interactive helpers. The shell imports System, System.Linq, System.Collections and System.Collections.Generic at startup. Add another namespace when needed:
csharp> using System.IO;
csharp> Directory.GetFiles ("/tmp").Length;
3
The number of files is host-specific. A multi-line statement shows a continuation prompt until its braces or other syntax are complete. Press Control-C to cancel the current line or interrupt a long-running computation. Press Control-D to terminate the session.
4. Put a small script in a file
A file passed on the command line is loaded and executed as a script:
$ cat > /tmp/csharp-check.cs <<'CS'
using System;
Console.WriteLine ("runtime check: " + Environment.Version);
CS
$ csharp /tmp/csharp-check.cs
runtime check: 4.0.30319.42000
The shell script file contains statements and expressions, not a normal compiled class definition. Keep temporary experiments in a directory you control and remove them when they are no longer needed:
$ rm -- /tmp/csharp-check.cs
Warning
rm is irreversible unless you have another copy. Check the pathname before running it, and never replace it with a broad pattern such as /tmp/*. This example changes only the temporary file you just created.
5. Pass arguments with the script boundary
Mono 5.0 and later support -- to stop option processing and make following words available through the global Args array:
$ printf '%s\n' 'foreach (var arg in Args) Console.WriteLine (arg);' | csharp -- alpha 'two words'
alpha
two words
Everything after -- is data for the C# shell. Quote an argument containing whitespace. This boundary also prevents a value beginning with a hyphen from being mistaken for a compiler option.
For a reusable executable script, put -s in its shebang:
#!/usr/bin/csharp -s
foreach (var arg in Args)
Console.WriteLine (arg);
Save that as a file, add the Unix execute bit with chmod u+x FILE, then invoke it with arguments. Test the file as your ordinary user first. Do not run it as root merely to work around a permission problem; fix the file ownership or permissions after inspecting the path.
6. Diagnose the result without changing state
When a name is missing, add the namespace or assembly explicitly. For example, XmlDocument is unavailable until System.Xml is imported:
csharp> new XmlDocument ();
<interactive>(1,5): error CS0246: The type or namespace name `XmlDocument' could not be found
csharp> using System.Xml;
csharp> new XmlDocument ();
System.Xml.XmlDocument
For an assembly, call LoadAssembly ("PATH") or pass the corresponding compiler reference option. Use a real path that you have inspected. LoadPackage is the package-oriented equivalent.
Inspect the session state with ShowVars (); and active imports with ShowUsing ();. A declaration can shadow an earlier variable in this shell, and its type can appear to change:
csharp> var value = 1;
csharp> var value = "text";
csharp> ShowVars ();
string value = "text"
That is interactive-shell behaviour, not a rule to carry into compiled C#. Restart the session when you need a clean scope.
7. Know the state and security boundaries
The shell loads Mono assemblies and .cs startup scripts from ~/.config/csharp on Unix. Treat that directory as executable configuration: review its contents when a session behaves unexpectedly, and do not place untrusted scripts or assemblies there. To recover from an unwanted startup change, exit the shell, identify the specific file, and move it aside using a reviewed, reversible rename. Do not delete the whole directory without checking what else depends on it.
The --attach PID option injects the shell into another process on a separate thread. It can corrupt or crash the target if its locks and dispatch rules are not understood. Do not use it for routine inspection, against a production service, or against a process you do not own and have explicit authority to debug. Ordinary expression evaluation and script execution do not require it.
Done means
- You confirmed the installed binary and Mono package version.
- You evaluated an expression with
-eand checked its status. - You can enter namespaces, multi-line statements and interactive helpers.
- You can run a temporary script and pass arguments through
Args. - You know where startup files are loaded from and treat them as executable configuration.
- You have avoided
--attachunless you have a controlled, authorised debugging case.